Title: Design and Implementation of Generics for the 'NET CLR
1Design and Implementation of Generics for the
.NET CLR
- Andrew Kennedy and Don Syme
Presented by Arthur Wong
27th November, 2002
2Introduction
- This paper describes the design and
implementation of support for parametric
polymorphism in the .NET Common Language Runtime.
3What are Generics?
- Generics is another name of parametric
polymorphism and templates - Generics allows classes, structs, interfaces,
methods to be parameterized by other types, e.g.
Stackltintgt, Stackltlonggt,Stackltstringgt,etc. - Attempts to support C, Oberon, Java, VB .NET,
Haskell, Ada, Modula-3, C, Eiffel, etc. - Achieves high level of code reuse, increase type
safety, better performance, etc. - Differs slightly in syntax and greatly in
implementation from templates as found in C and
generics as proposed for the Java language
4Object-based code in C - Today
- class Stack private Object items
private int nitems Stack() nitems 0
items new Object50 Object Pop()
if (nitems 0) throw new EmptyException()
return items--nitems - void Push(Object item) ... return
itemsnitems
5Object-based code in C - Today
- Stack s new Stack()
- s.Push(1)
- s.Push(2) //boxing
- int n (int)(s.Pop()) (int)(s.Pop())
//unboxing
- Inexpressive. Type system doesnt document what
should be in a particular stack. - Unsafe. Type errors (s.Push(2) ) lead to
runtime exceptions instead of being caught by
compiler. - Ugly. Casts everywhere,
- Slow. Casts are slow, and converting base types
(e.g. ints) to Objects involves expensive boxing
and unboxing.
6Generic code in C - Future
- class StackltTgt private T items private
int nitems Stack() nitems 0 items
new T50 T Pop() if (nitems 0)
throw new EmptyException() return
items--nitems - void Push(T item) if (items.Length
nitems) T temp items items
new Tnitems2 Array.CopyltTgt(temp,
items, nitems) itemsnitems
item
7Generic code in C - Future
Stackltintgt s new Stackltintgt() s.Push(1) s.Push
(2) int n s.Pop() s.Pop()
- Expressive. Type system documents what should be
in a particular stack. - Safe. Type errors (s.Push(2) ) lead to compile
time error which is caught by compiler. - Clear. No Casts
- Fast. Eliminating boxing and unboxing.
8IL for Object-based and Generic
Object-based Stack
Generic Stack
- .class Stack
- .field private class System.Object store
- .field private int32 size
- .method public void .ctor()
- ldarg.0
- call void System.Object.ctor()
- ldarg.0
- ldc.i4 10
- newarr System.Object
- stfld class System.Object Stackstore
- ldarg.0
- ldc.i4 0
- stfld int32 Stacksize
- ret
-
- .method public void Push(class System.Object x)
- ret
-
- .method public class System.Object Pop()
- .class StackltTgt
- .field private !0 store
- .field private int32 size
- .method public void .ctor()
- ldarg.0
- call void System.Object.ctor()
- ldarg.0
- ldc.i4 10
- newarr !0
- stfld !0 Stacklt!0gtstore
- ldarg.0
- ldc.i4 0
- stfld int32 Stacklt!0gtsize
- ret
-
- .method public void Push(!0 x)
- ret
-
- .method public !0 Pop()
9IL for Object-based and Generic
Object-based Stack cont
Generic Stack cont
- .
- .
- .
- .method public static void Main()
- .entrypoint
- .maxstack 3
- .locals (class Stack)
- newobj void Stack.ctor()
- stloc.0
- ldloc.0
- ldc.i4 17
- box System.Int32
- call instance void StackPush(class
System.Object) - ldloc.0
- call instance class System.Object StackPop()
- unbox System.Int32
- ldind.i4
- ldc.i4 17
- ceq
- .
- .
- .
- .method public static void Main()
- .entrypoint
- .maxstack 3
- .locals (class Stackltint32gt)
- newobj void Stackltint32gt.ctor()
- stloc.0
- ldloc.0
- ldc.i4 17
-
- call instance void Stackltint32gtPush(!0)
- ldloc.0
- call instance !0 Stackltint32gtPop()
-
-
- ldc.i4 17
- ceq
10Design of Generics
11Implementation of Generics
- Two traditional implementation routes of
parametric polymorphism - Representation and code specialization
- Relatively easy implementation
- Code explosion, especially in polymorphism
recursion ? very simple class may create hundred
or thousand times of code in compile time. - E.g. C
- Representation and code sharing
- Decrease code size
- Introduce box/unboxing (ex. object based Stack)
- E.g. Todays C
12Implementation of Generics
- Introduction of Generics Implementation
- Generics take advantage of dynamic loading
Just-in-Time compilation of CLR - Code generation at runtime on demand
- CLR can choose mix-and-match specialization and
sharing - The current challenge is to implement
- Support exact runtime types
- Code sharing as much as possible
13Implementation of Generics
- 1. Dynamic code expansion and sharing
- Instantiating generic classes and methods is
handled by the CLR. - Generate code on demand using Just-in-Time
compiler - Sharing code for compatible instantiations
- Avoid wasting time and space in generating
specialized code that never gets executed - Primitive (Value) types are mutually
incompatible - All reference types are compatible with each
other
14Implementation of Generics
- 2. Implementation of runtime types for objects
- Objects carry runtime type information
- Objects are represented by a vtable pointer
followed by the objects contents - Contents such as fields or array elements
- vtables main role is virtual method dispatcher
- It contains a code pointer for each methods
15Implementation of Generics
- If polymorphic objects are many, they extra word
(type info) is very expansive (1 and 2 OUT!) - of instantiations ltlt of objects (3 and 4
still OK) - Virtual method calls (vtable visits) are
extremely frequent in OO code (3 OUT)
Inline
Share the original vtable via an indirection
- For compatible instances, they share same class
code. - But we need to keep the exact run time type for
different instances - There is a conflict for code sharing and type
specialization - There are four ways to implement the runtime
types for objects
3
1
4
2
Example (DictltT,Ugt class) Dictltint,objectgt and
Dictltbool,stringgt
Via an direction to a hash-consed instantiation
Duplicate it per instantiation
16Implementation of Generics
- No boxing and unboxing
- Due to type specialization the implementation
never needs to box values of primitive type - Provides simplicity and performance
- Optimized use of runtime type information
- Efficient in the presence of code sharing and
with minimal overhead (allocate memory for
instance and indirection method call thru vtable)
for programs that make no use of them
17How does it work?
1a. Look for Stackltintgt1b. Nothing exists yet,
so create structures for Stackltintgt
Stackltintgt intStack1 new Stackltintgt()Stackltin
tgt intStack2 new Stackltintgt()Stackltstringgt
stringStack new Stackltstringgt()Stackltobjectgt
objStack new StackltCustomergt()intStack1.Push(
15)stringStack.Push(xxx)customerStack.Push
(anyCustomer)
2a. Look for Stackltintgt2b. Type already loaded!
3a. Look for Stackltstringgt3b. ltstringgt not
compatible with ltintgt, and it is reference type,
so create structures for Stackltstringgt
4a. Look for StackltCustomergt4b. Reference type
is unlike with value types, another specialized
version of Stack class is not created for the
Customer type. Instead, an instance of the
specialized version of Stack is created and the
Customer type is allocated memory and a pointer
is set to reference it.
18How does it work?
Stackltintgt intStack1 new Stackltintgt()Stackltin
tgt intStack2 new Stackltintgt()Stackltstringgt
stringStack new Stackltstringgt()Stackltobjectgt
objStack new StackltCustomergt()intStack1.Push(
15)stringStack.Push(xxx)customerStack.Push
(anyCustomer)
Compile code for Stackltintgt.Push
Compile code for Stackltstringgt.Push
Re-use code from Stackltstringgt.Push
19Performance of Generics
- Performance test
- Stack with repeated push and pops of 0...10000
elements - Test code
- S new stack
- c constant
- for m 1 ... 10000 do
- Spush(c) m times
- Spop() m times
- Test cases
- (a) Object-based (Todays C) Stack
- (b) Generics (Future C) Stackltintgt
- (c) Specialized (Mono) Stack_int
- 5X speedup Generics vs. Object-based
- A bit speedup (in string and int cases) Generics
vs. Specialized
20Performance of Generics
- Runtime type computations
- Generic allocation (vtable and other structures
setup) 10 to 20 slower (overhead) in generic
code, which consider acceptable
21Performance of Generics
- Costing runtime type creation
- Runtime type computations
- Generic allocation (vtable and other structures
setup) 10 to 20 slower (overhead) in generic
code, which consider acceptable
22Design Comparison of Generics
23Comparison with C templates
- What C gets wrong
- Efficiency
- nearly all implementations code-expand
- Safety
- not checked at declaration use, but at link
time - Simplicity
- very complicated (usages)
24Comparison with C templates
- What .NET Generics get right
- Efficiency
- code expansion managed by the CLR
- Safety
- check at declaration use
- Simplicity
- there are some corners, but the mechanism is
simple to explain use
25Summary
- .NET generics is a world first cross-language
generics in a high-level virtual machine design. - Implementation
- The runtime level is the right place to implement
generics for .NET CLR - Reduced code bloat (typed collections)
- More readable code
- Good performance ? no boxing, no downcasts
At OOPSLA in November 2002, it is announced that
Generics will be in next major release .NET CLR
26