|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThe current implementation of .NET generics is used mainly to make type-safe collections faster and more easy to use. Doing calculations on generic types is not as straightforward. The problemAn example for doing calculations on generic types would be a generic method to calculate the sum of all elements in a Somebody coming from a C++ background might implement such a method like this: public class Lists {
...
public static T Sum(List<T> list)
{
T sum=0;
for(int i=0;i<list.Count;i++)
sum+=list[i];
return sum;
}
...
}
This is not possible in C# because unconstrained type parameters are assumed to be of type To constrain type parameters in C#/.NET, you specify interfaces that the type has to implement. The problem is that interfaces may not contain any static methods, and operator methods are static methods. So with the current constraint system, it is not possible to define operator constraints. A clean way to enable numerical computations would be to let the basic data types like I tried to convince the people at Microsoft to implement such an interface, but apparently they won't be able to do it in time for Whidbey. We are on our ownMany people have been thinking about this problem, among them Eric Gunnerson and even Anders Hejlsberg. The solution proposed by Anders Hejlsberg was to have an abstract class Here is the code (copied from Eric Gunnerson's Blog): First define the abstract base class: public abstract class Calculator<T>
{
public abstract T Add(T a, T b);
}
Then specialize for the types you want to perform calculations on: namespace Int32
{
public class Calculator: Calculator<int>
{
public override int Add(int a, int b)
{
return a + b;
}
}
}
Then use an appropriate class AlgorithmLibrary<T> where T: new()
{
Calculator<T> calculator;
public AlgorithmLibrary(Calculator<T> calculator)
{
this.calculator = calculator;
}
public T Sum(List<T> items)
{
T sum = new T();
for (int i = 0; i < items.Count; i++)
{
sum = calculator.Add(sum, items[i]);
}
return sum;
}
}
You would use it like this: AlgorithmLibrary library = new AlgorithmLibrary<int>(new Int32.Calculator());
There are many other creative solutions, but all of them have the drawback that they involve some kind of dynamic method invocation (virtual methods, interfaces or delegates). So while they make it possible to perform calculations with generic type parameters, the performance is unacceptably low for numerical applications. The SolutionThe first person to come up with a solution that does not involve any virtual method invocation was Jeroen Frijters. The solution uses the fact that constraining type parameters using interfaces is not the same as casting to interfaces. Calling a method using an interface has the overhead of dynamic method dispatch, but calling a method on a type parameter that is constrained by an interface has no such overhead. Here is an example. It is a generic method that sorts two numbers. The type public class Sorter
{
private static void Swap<T>(ref T a, ref T b)
{
T t=a;a=b;b=t;
}
public static void Sort<T>(ref T a,ref T b)
where T:IComparable<T>
{
if(a.CompareTo(b)>0)
Swap(ref a,ref b);
}
}
The approach suggested by Jeroen Frijters uses a second type parameter. To avoid unnecessary object creation and virtual method dispatch, it is best to use a value type for the type containing the operations. Here is a small example for this approach: interface ICalculator<T>
{
T Sum(T a,T b);
}
struct IntCalculator : ICalculator<int>
{
public int Add(int a,int b) { return a+b; }
}
// struct FloatAdder ...
// struct DoubleAdder ...
class Lists<T,C>
where T:new()
where C:ICalculator<T>,new();{
//since C is a struct with zero size, we can create an instance
//of C whenever we need one without any overhead.
private static C calculator=new C();
public static T Sum(List<T> list)
{
T sum=new T();
for(int i=0;i<list.Count;i++)
sum=calculator.Add(sum,list[i]);
return sum;
}
}
The use of this class is a bit awkward because of the second type parameter. But you could use an alias. Here is how you would use the using IntLists=Lists<int,IntCalculator>;
//...
List<int> list=new List<int>();
list.Add(1);
list.Add(2);
list.Add(3);
Console.WriteLine("The sum of all elements is {0}",
IntLists.Sum(list));
PerformanceSince we made sure that our code does not contain any virtual method calls or unnecessary object creation, the generic code should be exactly as fast as non-generic code. To test this, I wrote a small benchmark. The result is encouraging. Even the current very limited JIT compiler manages to inline the Please make sure that you run the benchmark in release mode, since in debug mode many very important optimizations such as method inlining are disabled. Using operators with type parametersWe now can do calculations on generic types with acceptable performance. But we still can not use operators with type parameters. In the above example, we had to write This might not be a big deal if you want to perform a simple addition. But if you have large, complex methods that excessively use operators, it would be a tedious and error-prone process to convert them to use method calls instead. Fortunately, it is possible to create a wrapper This is how a wrapper public struct Number<T,C>
where C:ICalculator<T>,new()
{
private T value;
private static C calculator=new C();
private Number(T value) {
this.value=value;
}
public static implicit operator Number<T,C>(T a)
{
return new Number<T,C>(a);
}
public static implicit operator T(Number<T,C> a)
{
return a.value;
}
public static Number<T,C> operator + (Number<T,C> a, Number<T,C> b)
{
return calculator.Add(a.value,b.value);
}
...other operators...
}
Now, instead of using class Lists<T,C>
where T:new()
where C:ICalculator<T>,new()
{
public static T Sum(List<T> list)
{
Number<T,C> sum=new T();
for(int i=0;i<list.Count;i++)
sum+=list[i];
return sum;
}
}
You might expect that this code runs exactly as fast as the non-operator version. But unfortunately, this is not the case. The current .NET JIT compiler has some very serious limitations. Basically, it does not do any inlining if a method has an explicit struct parameter (Yes, I was shocked too). Since the operator methods in the wrapper Here is a suggestion about this topic that you can vote for. This is the only thing that is missing for lightning-fast and easy to use numerics in C#. ConclusionEven though the constraint syntax of .NET generics system is very limited, it is possible to work around those limits by using a second type parameter. This approach is a bit more work for the writer of the generic class library, but it is quite transparent for the user of the class library and it yields performance that is identical to a non-generic version. Just like with C++ templates, the runtime cost of the abstraction is zero. There are some limitations, but these should go away very soon as Microsoft and the competing vendors improve the JIT compiler performance. So, it is possible to write numeric libraries that are as fast or even faster than compile-time template libraries such as the C++ STL. About the codeThe included project consists of interfaces, implementations and wrappers for all primitive types with a few nice additions. The list benchmark calculates the standard deviation of a large list both with generic and with non-generic methods. Another benchmark compares the performance of the non-generic The code is under the BSD license, so feel free to use it for your own projects. References
| ||||||||||||||||||||||