Click here to Skip to main content
Click here to Skip to main content

Using generics for calculations

By , 11 Oct 2004
 
Prize winner in Competition "C# Sep 2004"

Introduction

The 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 problem

An example for doing calculations on generic types would be a generic method to calculate the sum of all elements in a List<T>. Of course, summing up all elements of a list only makes sense if T is a type such as int, double, decimal that defines an addition operation.

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 System.Object, which does not define a + operation.

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 int, float, double, decimal etc. implement an interface for arithmetic operations. Then this interface could be used to constrain the type parameters. This would work similar to the IComparable<T> interface that all basic data types implement.

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 own

Many 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 Calculator<T> that has to be specialized for each primitive type. The generic type would then use an instance of the appropriate calculator to do the calculations.

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 Calculator<T> to do the calculations. Here is an example that calculates the sum of all elements in a List<T>.

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 Solution

The 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 T has to implement IComparable<T> so that we can be sure that it has the CompareTo method. But nevertheless, calling the CompareTo method does not have the overhead normally associated with interfaces.

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 Lists<T,C> type:

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));

Performance

Since 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 Sum method, so the generic version is indeed exactly as fast as the non-generic version on my machine.

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 parameters

We 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 sum=calculator.Add(sum,list[i]) instead of simply writing sum+=list[i].

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 struct that defines operators. By using implicit conversions to and from the wrapped type, we can use this wrapper struct almost transparently.

This is how a wrapper struct for the above ICalculator interface might look like:

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 T in your generic class, you would just use the wrapper struct instead. Here is how the Sum example would look like using the wrapper struct:

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 struct have struct parameters, they will not be inlined, so the performance will suffer. But I am sure that Microsoft will fix this important performance issue soon.

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#.

Conclusion

Even 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 code

The 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 System.Drawing.Point and System.Drawing.PointF structs with a generic Point<T> version.

The code is under the BSD license, so feel free to use it for your own projects.

References

  • Anders Hejlsberg proposed using an abstract base class.
  • A solution using dynamic method generation.
  • Jeroen Frijters proposed using value types for the calculator.
  • This was my first posting about the topic. As you might notice, I was quite angry...
  • Here is where I will publish newer versions of the code.
  • Two articles by me on osnews about the topic.
  • Please vote for this suggestion. It is more important than the presidential elections :-)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Rüdiger Klaehn
Web Developer
Germany Germany
Member
Rüdiger Klaehn works as freelance developer in the space industry. He is very interested in functional programming languages and hopes that .NET will lead to a more widespread adoption in the industry.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMin and MaxmemberSimmoTech23 Oct '05 - 5:17 
Are the implementations for Min and Max the wrong way around?
e.g.
public T Min(T a, T b) { return Math.Max(a, b); }
public T Max(T a, T b) { return Math.Min(a, b); }
 

 
Cheers
Simon
GeneralI would solve the problem like this:memberDaniel Steiner12 Aug '05 - 9:53 
Very interesting Article. I would solve the problem like this:
 
C#:

public static T Sum<T>(List<T> list)
{
  double sum = 0;
  foreach (object item in list)
  {
      sum += (double)Convert.ChangeType(item, typeof(double));
  }
  return (T)Convert.ChangeType(sum, typeof(T));
}

 
-Dani
GeneralRe: I would solve the problem like this:memberRüdiger Klaehn14 Aug '05 - 2:03 
That works for int, but the performance will be incredibly low. And also it won't work at all for types (vector, complex etc.) that have a + operation but can not be converted to a double.
GeneralRe: I would solve the problem like this:memberKeith Farmer23 Aug '05 - 9:31 
Here's how I solved it. I don't know how the speed compares, but I prefer the syntax:
 
http://www.codeproject.com/csharp/GenericOperators.asp[^]
GeneralRe: I would solve the problem like this:memberRüdiger Klaehn22 Sep '05 - 23:11 
Interesting.
 
But it still involves a delegate invocation. Delegate invocation is much faster in .NET 2.0 than in 1.1, but it still has an overhead comparable to a virtual method invocation. As you found out in your benchmark, this carries a large performance penalty.
 
The nice thing about my approach is that by using structs you avoid any virtual method invocation, so (when the notorious struct inlining problems of the JIT are solved), this method will be exactly as fast as working with primitive types directly.
 
Besides, I think the syntax with the two different type parameters is not that bad. It allows you to specify the storage type and the calculator type separately. This can be very useful. For example you can define a double calculator which defines the = operator differently so that two numbers that differ by less than 1e-30 are considered equal.
 
regards,
 
Rüdiger
GeneralRe: I would solve the problem like this:memberKeith Farmer19 Oct '05 - 13:00 
You could define = to involved some standard epsilon just fine with LCG -- that's the point of LCG.
 
And while the performance penalty is finite, it is not what I would term large.
 
I think the solution to calculations using generics is not to be found in structs and crufty workarounds (mine or yours), but in MS correcting the lack of operator overload support.

Questionwhy??memberfedo50011 May '05 - 21:09 
why no one replay in my problem ??
 
anyway
thanks to all ...
Generalerrormemberfedo5002 May '05 - 17:48 
when i try to open these files(Arithmetic.exe,Arithmetic.vshost.exe)
i got this error:
"The application failed to initialize properly(0xc0000135). Click on OK to terminate the application."
 
and i using windows xp-sp2
 
what can i do ??Confused | :confused: Confused | :confused:
 
thanks
GeneralRe: errorstaffNishant Sivakumar11 May '05 - 21:32 
Do you have .NET 2.0 installed?
GeneralGood but a bit offtopicsussMax Shaposhnikov11 Mar '05 - 23:02 
Very, very interesting OO methods, they are very useful but not in the context of article. .NET isn't for computational purposes.
 
Using c++ classes is much more convenient for such tasks than to play with objects.
GeneralRe: Good but a bit offtopicmemberRüdiger Klaehn12 Mar '05 - 1:56 
Max Shaposhnikov wrote:
Very, very interesting OO methods, they are very useful but not in the context of article. .NET isn't for computational purposes.
 
Using c++ classes is much more convenient for such tasks than to play with objects.

 
The people I currently work for seem to disagree.
 
Of course at this time C++ gives you better performance than C#. But this will improve once the struct inlining issue is taken care of.
 
There is no theroetical reason why MSIL should be slower than natively compiled binaries. In fact runtime profiling and using processor-specific features will make MSIL faster than native C++ eventually. A native C++ program will usually be compiled for the lowest common denominator (e.g. x86-32), while a good and mature MSIL JIT will use all the goodies of your processor such as 64bit registers, SSE2 etc.
 
Of course this is a few years in the future. But the performance of .NET languages is OK for quite sophisticated numerical problems even now. The biggest issue is the struct inlining issue. And this will hopefully be taken care of soon, either by Microsoft or by one of the open source implementations.
GeneralRe: Good but a bit offtopicmembergaby2r5 Apr '05 - 4:29 
the Rose | [Rose] is for the efort. however you pointed out the problem :
There is no theroetical reason why MSIL should be slower than natively compiled binaries.
you should start looking at the problem from bottom to top and you will see the difference
OMG | :OMG:
GeneralRe: Good but a bit offtopicmemberKeith Farmer8 Jun '05 - 9:17 
I learned C#, working with computationally-heavy data involving the human genome. Python, and the Numeric Python library, is very popular in the physical sciences.
 
You should re-think in light of your C++ bias. In science, it's much more important that the calculation be correct than swift, and .NET's more than fast enough.
GeneralRe: Good but a bit offtopicmemberpascal ganaye30 Oct '05 - 10:48 
I think you're right.
The difference between C++ and Dotnet is within a range of 10.
At the moment it is more in favour of C++ but as said earlier this could well change.
 
In one of my article I surprised myself to reduce an algorithm taking initially several hours into 1 seconds.
http://www.codeproject.com/dotnet/macmahon.asp[^]
 
The point is that often when you have heavy calculation the solution is not in the processor but the algorithm.

General.Net 2.0memberBishoyAtef12 Jan '05 - 16:50 
the .Net 2.0 will enable you to make types in your list as was in C++
 
Smile | :) don`t ask me how , as i don`t know c++ nor what is this , but i have seen it in last microsoft summit Smile | :)
 
Bishoy atef
QuestionWant to try this with matrices?sussSerdar Astarlioglu1 Dec '04 - 17:00 
Any ideas appreciated, I tried the following approaches:
 
1.   Put the methods, properties in the generic class, and derive concrete classes with operators from it:
 
   public class GenericMatrix<T>
      {
            private int rows;
            private int cols;
            private T[][] matrix;
            public int Rows {/*...*/}
            public int Cols {/*...*/}
            public T this[int row, int col] {/*...*/}
            public GenericMatrix(int rows, int cols) {/*...*/}
            private void InitializeMatrix() {/*...*/}
      }
 
And the concrete class for double matrices:
 
      public class DoubleMatrix : GenericMatrix<double>
      {
            public DoubleMatrix(int rows, int cols):base(rows,cols) {}
 
            public static DoubleMatrix operator *(DoubleMatrix m, double d) {/*...*/}
 
            public static DoubleMatrix operator *(DoubleMatrix m1, DoubleMatrix m2) {/*...*/}
      }
 
My idea was to avoid retyping the indexer, initialization over and over again for each concrete class and take care of it in the generic base class.   After all you can have a matrix of almost any type (matrix of textboxes, etc.), that doesn't mean you want to implement and add operator to that type of matrix
 
However, the performance of DoubleMatrix class derived in this fashion (although there are not virtual methods) was very poor compared to the case where DoubleMatrix was directly derived from object and implemented its own indexer.
 
2. Implement the operators in the generic class for each possible type.   Below is the * operator:
 
            public static GenericMatrix<T> operator *(GenericMatrix<T> m, double d)
            {
                  if (m != null && d != 0)
                  {
                        if (m is GenericMatrix<double>)
                        {
                              GenericMatrix<double> mp = m as GenericMatrix<double>;
                              GenericMatrix<double> m1 = new GenericMatrix<double>(mp.rows, mp.cols);
                              for (int row = 0; row < mp.rows; row++)
                              {
                                    for (int col = 0; col < mp.cols; col++)
                                    {
                                          m1[row, col] = d * mp[row, col];
                                    }
                              }
                              return m1 as GenericMatrix<T>;
                        }
                     else
                        {
                              throw new NotImplementedException();
                        }
                  }
                  else
                  {
                        return null;
                  }
            }
 
I know, this looks sick.   But in spite of all the type checks, this thing runs much faster than (1) and almost as fast as a non-generic double matrix class I am using at this time.   I would certainly want to use the first approach, but I can't figure out what I am doing wrong.Sleepy | :zzz:
AnswerRe: Want to try this with matrices?memberKeith Farmer20 Jun '05 - 13:21 
What happens with this?
 
            public static GenericMatrix<T> operator *(GenericMatrix<T> m, T d)
            {
                  if (m != null && d != 0)
                  {
                        GenericMatrix<T> m1 = new GenericMatrix<T>(m.rows, m.cols);
                        for (int row = 0; row < mp.rows; row++)
                        {
                              for (int col = 0; col < m.cols; col++)
                              {
                                    m1[row, col] = d * m[row, col];
                              }
                        }
                        return m1;
                  }
                  else
                  {
                        return null;
                  }
            }
 

 

GeneralGreat!!memberLuis Alonso Ramos21 Nov '04 - 16:59 
Great article, you've definitely got my 6 5. Smile | :)
 
-- LuisR
 


Luis Alonso Ramos
Intelectix - Chihuahua, Mexico
 
Not much here: My CP Blog!

GeneralExcellentstaffPaul Watson21 Nov '04 - 10:00 
Normally articles like this leave me wondering if I can even call myself a programmer but this was excellent, I understood it.
 
regards,
Paul Watson
South Africa
 
Michael Dunn wrote:
"except the sod who voted this a 1, NO SOUP FOR YOU"
 
Crikey! ain't life grand?

GeneralRe: Excellentmembercomputerguru9238225 Feb '06 - 16:20 

Very nicely done article, enjoyed it and bookmarked it Smile | :)
 
Paul
Generalbenchmark with floatsmemberMichael Nischt11 Nov '04 - 3:45 
Did you try your benchmark with floats instead of using doubles?
 
My results are:
doubles: performace is equal
floats: much slower, even slower than using doubles..
 

So it seems your work around doesn't work with floats ?!
 
[Edit]
On the other hand, using the benchmark with PointF und PointFG, in your Arithmetic Library, it works fine.
[/Edit]
 

 
Greetings
-Michael Nischt
GeneralRe: benchmark with floatsmemberPierre Arnaud30 Nov '04 - 19:54 
Maybe this has nothing to do with the work around, but simply that operations with floats really take more time than with doubles. You'd have to check this. I vaguely remember that some compiler/CPUs first convert the floats to doubles, do the calculation and then convert back to floats. This could be the case with .NET/IL.
GeneralThank you !memberSébastien Lorion20 Oct '04 - 20:56 
Articles like yours are great time savers. It's easy to grasp the fundamentals of generics, but to go a step further and find a way to use them to their fullest needs dedication and attention to detail. I'm glad someone took upon himself to do so and more importantly, wrote a well written article about the process.
 
Got my 5 Smile | :)
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralRe: Thank you !memberRüdiger Klaehn21 Oct '04 - 1:27 
Glad you liked it.
 
I just looked at your website and noticed that you are working on a collection library.
 
The approach described in this article can be used to speed up common collection operations such as sorting by eliminating virtual method calls.
 
Take for example the method List<T>.Sort(IComparer<T> comparer). It takes an interface as parameter, so each call to the comparer will have the overhead of an interface invocation. For simple types such as int, double... this overhead will be quite large.
 
A faster way to do it would be this:
class FastList<T>
{
    ...
    public static void Sort<C>() 
        where C:IComparer<T>,new()
    {
        C comparer=new C();
        ...
        // if C is a valuetype, calls to comparer
        // will not be virtual and can thus be 
        // inlined
    }
}

GeneralRe: Thank you !memberSébastien Lorion21 Oct '04 - 8:05 
Yes indeed, but the NCollection project is aimed at version 1.1, so no generics. In fact, the project is more or less in limbo because we got other projects to work on (a data framework in my case) and because generics simplify so much stuff that it removes the drive to overcome 1.1 limitations.
 
I read that for the IEnumerable, the C# compiler directly call the public method GetEnumerator() instead of the private interface implementation. Does it do that kind of trick in some other cases too ?
 
Sébastien
 


Intelligence shared is intelligence squared.
 
Homepage : http://sebastienlorion.com
GeneralTremendously enjoyed your articlestaffNishant S13 Oct '04 - 17:25 
Thank you. Got my 5 too Smile | :)
 

My blog on C++/CLI, MFC/Win32, .NET - void Nish(char* szBlog);
My MVP tips, tricks and essays web site - www.voidnish.com

GeneralExcellent article. :)memberJon Rista12 Oct '04 - 9:32 
This was a great article, not so much for the practical use of generics, but for the way they were implemented, and for the benchmark. I'm glad to know that proper use of generics produces no impact to performance, and that the C# compiler properly optimizes generics code (when compiled as a release, of course).
 
Thanks for the work and verification. Quite an informative article. Smile | :)
GeneralRe: Excellent article. :)memberRüdiger Klaehn12 Oct '04 - 22:29 
Thanks.
 
After playing with generics for a few months, I can say that they are really well-designed. For example a List<int> will have performance close to or identical to an int[] since it uses an int[] internally and the indexer is usually inlined.
 
Compare that to the java version, which uses an object[] internally and incurs a huge boxing/unboxing overhead each time you access the List<Integer>.
 
Here are two simple benchmarks for the java and the .NET version of generics:
C#
java
 
The .NET version blows the java version out of the water. On my machine, it is 20 times faster and consumes 5 times less memory. The reason is that in java 1000001 objects have to be managed on the heap: 1000000 objects for the boxed ints, and 1 object for the array of references. In C# only 1 large object is allocated on the heap, the int[].
Generalreflection invokememberRoger J12 Oct '04 - 3:14 
wouldnt this be possible by calling the + operator via reflection?
 
this wouldnt require any dynamic code generation nor extra helperclasses for each type.
 
//Roger
GeneralRe: reflection invokememberRüdiger Klaehn12 Oct '04 - 3:26 
You could write a type that calls the + operator using Reflection.Emit without any overhead. But since the type would be generated at runtime, the only way to refer to it is through an interface or a base class. So you have virtual methods or interface calls. See here for an example of this approach
 
Calling the op_Additon method directly through reflection would be orders of magnitude slower than even virtual dispatch. Just try it.
 
If you can work around this somehow, by all means please post the code. But I am not holding my breath.
GeneralRe: reflection invokememberRoger J12 Oct '04 - 4:10 
oke i see the problem , calling op_Addition was about 500 times slower than res=o1+o2; Dead | X|
 
//Roger
GeneralRe: reflection invokememberKeith Farmer12 Oct '04 - 13:27 
How about a method that uses lightweight code generation/DynamicMethods to locate and return the appropriate method? That would incur a penalty on first use, but would it incur any further penalties?
 
Such things are being looked at wrt IronPython and other dynamic language implementations.
GeneralRe: reflection invokememberRüdiger Klaehn12 Oct '04 - 22:19 
Yes, it is possible to find out the appropriate method at template initialization time. For example you could do so in the static constructor of the generic type. .NET creates a new type for each specialization, so this should work just fine.
 
But how do you store the method without using an extra field? And how do you call the method without using a delegate or an interface?
 
For scripting languages like IronPython a simple virtual method invocation is no big deal. But for numerical applications a single virtual method call means that your performance is ruined.
 
If you find a way to do it, you will have my eternal gratitude Smile | :)
 
Write a Point<T> that
a) can be used with T=int,float,double,decimal,...
b) is not larger than 2*sizeof(T)
c) is as fast as System.Drawing.Point for ints and as fast as System.Drawing.PointF for floats.
GeneralRe: reflection invokememberRüdiger Klaehn13 Oct '04 - 2:48 
There is one way to do it.
 
You could have dummy methods and insert the appropriate IL for the type shortly before the method gets JITed by using the ICorProfiler COM API.
 
Or you could use System.Reflection.Emit.MethodRental to replace the method IL with the IL for the type.
 
But that would be quite complex and hackish, and I am not sure wether you still get proper inlining when using MethodRental.
 
But it would be a nice hack Smile | :) Maybe I should mess with MethodRental a bit...
GeneralRe: reflection invokememberKeith Farmer21 Jun '05 - 22:35 
[BIG EDIT]
 
Someone check me on this...
 
With caching optimized for looping over constant types, and using LCG, I'm consistently running about *30-50 percent FASTER* (!?!) than non-generic class when running over 1 million ints (it reversed to twice as *SLOW* when using 10 million ints, and is about 3.5x slower if these are structs).   This was just about 2 hours of fiddling with it, and never having used LCG before.
 
Notice that I'm *not* using dynamic invocation.   Instead, I'm creating the delegate using LCG, then storing the delegate, strongly typed, in a static cache.   When needed, I check to see if it's already pulled from the cache into an execution delegate member, then I simply invoke the member using a regular method call.   The test omits what should be this one-time hit, and only times the loop.
 
Also, since I'm emitting IL directly, it's possible I'm bypassing some checks?
 
// Class1.cs
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;
using System.Reflection;
 
namespace GenericOperators
{
      public delegate T BinaryOperator<T, U>(T t, U u);
 
      public class FooInt
      {
            public int Value;
            public FooInt(int newValue)
            {
                  this.Value = newValue;
            }
 
            public static FooInt operator +(FooInt p1, FooInt p2)
            {
                  return new FooInt(p1.Value + p2.Value);
            }
      }
 
      public class Foo<T>
      {
            public T Value;
            public Foo(T newValue)
            {
                  this.Value = newValue;
            }
 
            private static Dictionary<Type, Delegate> addOpCache = new Dictionary<Type, Delegate>();
            private static Type LastType;
            private static BinaryOperator<T, T> LastDelegate;
 
            public static Foo<T> operator+(Foo<T> p1, Foo<T> p2)
            {
                  if (typeof(T) != LastType)
                  {
                        if (!addOpCache.ContainsKey(typeof(BinaryOperator<T, T>)))
                        {
                              LastDelegate = (BinaryOperator<T, T>)CreateAddOperator<T>();
                        }
                        else
                        {
                              LastDelegate = (BinaryOperator<T, T>)addOpCache[typeof(BinaryOperator<T, T>)];
                        }
 
                        LastType = typeof(T);
                  }
                 
                  return new Foo<T>(LastDelegate(p1.Value, p2.Value));
            }
 
            private static Delegate CreateAddOperator<U>()
            {
                  DynamicMethod method = new DynamicMethod(
                        "op_Addition_" + typeof(T).ToString() + "_" + typeof(U).ToString(),
                        typeof(T),
                        new Type[] { typeof(T), typeof(U) },
                        typeof(Foo<T>)
                  );
 
                  ILGenerator generator = method.GetILGenerator();
                 
                  generator.Emit(OpCodes.Ldarg_0);
                  generator.Emit(OpCodes.Ldarg_1);
 
                  if (typeof(T).IsPrimitive)
                  {
                        generator.Emit(OpCodes.Add);
                  }
                  else
                  {
                        MethodInfo info = typeof(T).GetMethod(
                              "op_Addition",
                              new Type[] { typeof(T), typeof(U) },
                              null
                        );
 
                        generator.EmitCall(OpCodes.Call, info, null);
                  }
 
                  generator.Emit(OpCodes.Ret);
 
                  Delegate result = method.CreateDelegate(typeof(BinaryOperator<T, U>));
 
                  addOpCache.Add(typeof(BinaryOperator<T, U>), result);
 
                  return result;
            }
      }
}
 
// Test.cs
 
using System;
using System.Collections.Generic;
using System.Text;
 
using GenericOperators;
 
namespace GenericOperators.Test
{
      class Test
      {
            static void Main(string[] args)
            {
                  List<FooInt> list1 = new List<FooInt>();
                  List<Foo<int>> list2 = new List<Foo<int>>();
 
                  for (int x = 0; x < 1000000; x++)
                  {
                        list1.Add(new FooInt(x));
                  }
 
                  for (int x = 0; x < 1000000; x++)
                  {
                        list2.Add(new Foo<int>(x));
                  }
 
                  FooInt test1 = new FooInt(0);
                  Foo<int> test2 = new Foo<int>(0);
 
                  long start1;
                  long end1;
                  long start2;
                  long end2;
 
                  test1 = test1 + test1;
 
                  start1 = DateTime.Now.Ticks;
 
                  foreach (FooInt x in list1)
                  {
                        test1 += x;
                  }
 
                  end1 = DateTime.Now.Ticks;
 
                  test2 = test2 + test2;
 
                  start2 = DateTime.Now.Ticks;
 
                  foreach (Foo<int> x in list2)
                  {
                        test2 += x;
                  }
 
                  end2 = DateTime.Now.Ticks;
 
                  Console.WriteLine("FooInt = " + test1.Value + "; delta: " + (end1 - start1));
                  Console.WriteLine("Foo<int> = " + test2.Value + "; delta: " + (end2 - start2));
 
                  Console.WriteLine("ratio = " + (double) (end2 - start2) / (double) (end1 - start1));
 
                  Console.ReadKey(true);
            }
      }
}
 


GeneralRe: reflection invokememberKeith Farmer21 Jun '05 - 22:47 
That said, I'd be interested hearing what the performance is for the direct ILasm version.[^]
 
If that were fast enough, you could create a code generator that would take a spec of a generic type, create IL for it, and have done with it (until C#3).

GeneralWow!memberNemanja Trifunovic12 Oct '04 - 2:22 
Great article. Thanks for writing.
 
This is very interesting. Constraints as implemented with .NET have some obvious benefits (easier debugging) but there are problems as well and your article demonstrates overcoming some of them. Maybe it would have been better if constraints were optional Unsure | :~
 


My programming blahblahblah blog. If you ever find anything useful here, please let me know to remove it.
GeneralRe: Wow!memberRüdiger Klaehn12 Oct '04 - 23:07 
I used to think that the .NET generic constraints are too limited. But since you can work around the limitations by using a second type parameter, it is not that bad.
 
I would rather have a too limited constraint syntax than the mess that results if you have implicit constraints like in C++. Every time I get intellisense on type paramters I know that constraints were a good idea.
 
C# should be simple enough for the novice programmer, yet expressive enough for the master hacker. Overall it does a very good job. Java is not nearly expressive enough for even moderately advanced programmers, while C++ has way too many opportunities for the novice programmer to shoot himself in the foot.
 
By the way: if you are getting bored with C#'s features, you can always extend them with Reflection.Emit.
 
p.s. I really liked your "Forofobia" blog entry. I almost ruined my keyboard by spitting coffee over it.
GeneralRe: Wow!memberNemanja Trifunovic13 Oct '04 - 2:27 
Hehehe, I don't really want a templates vs generics discussion here. Some MS people argue that such comparison does not make sense, because they are two different mechanisms.[^] and it is best to have both. Frankly, I think this theory is not 100% convincing: both templates and generics were invented to serve the same basic task: type-safe collections. The fact that templates can be used for other programming tasks came out almost accidentally.
 

Rüdiger Klaehn wrote:
I used to think that the .NET generic constraints are too limited.
When I first saw constraints, my first thought was: "Why generics at all then? You can have the same thing with interfaces.". I even asked this question at CP Lounge, and was lucky that Bill Kempf answered that for me. The funny thing is, there are still many people who don't get it even if they write books on programming[^] Wink | ;)
 
Anyway, constraints in general are a good thing (with C++ templates, there is Boost Check Concept Library[^] for that purpose), only I am not quite sure it is a good thing to have mandatory constraints. I'd like to decide for myself when to use constraints and when not Smile | :)
 
Rüdiger Klaehn wrote:
By the way: if you are getting bored with C#'s features, you can always extend them with Reflection.Emit.
 
Yep, I know that. In fact, I have been trying to find a good way to introduce a good deterministic cleanup mechanism in C# (something like in C++/CLI), but so far without any luck Cry | :((
 

Rüdiger Klaehn wrote:
p.s. I really liked your "Forofobia" blog entry.
 
Thanks. I love programming rants (when appropriate, of course).
 


My programming blahblahblah blog. If you ever find anything useful here, please let me know to remove it.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130516.1 | Last Updated 11 Oct 2004
Article Copyright 2004 by Rüdiger Klaehn
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid