inlining compareTo(int a,intb) { return a-b; }

Hello,

I am writing a class that implements the IDictionary<TKey,TValue> interface and it extensivly needs to compare two "TKey" types. (using Microsoft Visual C# 2008 Beta 2)

The problem is that the resulting assembly code in the common case, in which TKey is an integer is far from being optimized, and it is definitly not inlined (checked the release code after disabling the Suspend IT and Just My Code). I tried defining TKey as implements Comparable, and tried accepting an IComparator and tried with extension methods and with overriding an abstract compare method, and tried overriding a virtual function. The compare method is never inlined, there is always the method call instead of subtructing b from a. The best implementation was with abstract/virtual in which there was no extra dereferencing of a pointer and no stack push, just the method call overhead.

Is this becuase inlining has some problems with the unknown TKey type ?

Can't C# accept a "default" implementation for compare<TKey> and an "optimized-to-be-inlined-at-runtime" version for compare<int> ?

something like:

bool compare<int>(int a, int b) {return a-b;}

bool compare<TKey>(TKey a,TKey b) { return a.CompareTo(b);}

bool compare<TKey>(TKey a,TKey b,TComparator<TKey> c) { return c.Compare(a,b);}

or instead:

class MyDictionary<TKey,TValue>

{

bool Contains(Tkey k) { ... compare(k,node_k)... }

bool compare(TKey a ,TKey b) { return a.compareTo(b):}

}

class MyDictionaryInt<TValue> : MyDictionary<int,TValue>

{

override bool compare(int a ,int b) {return a-b; } //inlined not virtual

}

Is it necessary to "copy paste" the "Contains" implementation into MyDictionaryInt and then replace manually TKey with int - in order to get the comparison inlined ?

Regards,

Itai Frenkel

[2063 byte] By [ItaiFrenkel] at [2008-1-10]
# 1
How are you verifying that the method is not inlined?
PeterRitchie at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 2

Hello Peter,

I'm looking at the resulting assembly code, in release mode after disabling "Suspend JIT" and disabling "Just My Code".

- using the beta2 version of Visual Studio Team 2008.

Regards,

Itai

ItaiFrenkel at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 3

Do you mean "Supress JIT optimizations"? If so, inlining is a JIT optimization. If you're suppressing JIT optimization you won't see inlining...

Also, viewing disassembly of code running in a managed debugger is JITted differently than when run outside of a managed debugger. You'll have to run the code outside of a managed debugger (with JIT optimizations on) and attach to the process to see inlined code. Or, use WinDbg.

PeterRitchie at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 4


Hello Peter,

Below is a sample code that demonstrates the performance decrease.

The program asks the user for two numbers, compares them and displays whether a==b , a>b or a<b.

It calls two methods to comapre a and b: MyDictionaryInt.Test1() and MyDictionaryInt.Test2().

Test1() is the method I am using in my code today.

Test2() is the method that I would need to use if I want better performance (It's actually a copy of Test1(), but instead of calling the virtual method Compare(a,b) it calls Compare2(a,b) which is not virtual and is inlined ( replace the call to Compare2(a,b) with (a-b) to validate this claim).

Below is the output. The result varies, but Test1 is always 3 to 4 times slower than Test2.

So the question is - how to compile Test1() so it will match the performance of Test2().

a=2
b=1
a>b
Test1 Ticks=375
Test2 Ticks=94
Press any key to continue . . .

namespace TestIntCompare

{

public class MyDictionary<TKey,TValue>

where TKey : System.IComparable

{

virtual protected int Compare(TKey x, TKey y)

{

return x.CompareTo(y);

}

public int Test1(TKey a,TKey b)

{

int c = 0;

const int count = 10000;

for (int i = 0; i < count; i++)

{

for (int j = 0; j < count; j++)

{

c += this.Compare(a, b);

}

}

return c;

}

}//MyDictionary

sealed public class MyDictionaryInt<TValue> : MyDictionary<int,TValue>

{

public int Test2(int a,int b)

{

int c = 0;

const int count = 10000;

for (int i = 0; i < count; i++)

{

for (int j = 0; j < count; j++)

{

c += Compare2(a,b);

//c += a - b;

}

}

return c;

}

private int Compare2(int x, int y)

{

return x - y;

}

override protected int Compare(int x, int y)

{

return x - y;

}

}

class Program

{

static void Main(string[] args)

{

MyDictionaryInt<string> aDictionary = new MyDictionaryInt<string>();

System.Console.Write("a=");

int a = System.Convert.ToInt32(System.Console.ReadLine());

System.Console.Write("b=");

int b = System.Convert.ToInt32(System.Console.ReadLine());

int c;

int tick1 = System.Environment.TickCount;

c = aDictionary.Test1(a,b);

int tick2 = System.Environment.TickCount;

c = aDictionary.Test2(a,b);

int tick3 = System.Environment.TickCount;

if (c == 0)

{

System.Console.WriteLine("a==b");

}

else if (c < 0)

{

System.Console.WriteLine("a<b");

}

else

{

System.Console.WriteLine("a>b");

}

System.Console.WriteLine("Test1 Ticks="+ (tick2 - tick1));

System.Console.WriteLine("Test2 Ticks="+ (tick3 - tick2));

}

}

}

ItaiFrenkel at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 5

Your Test1 isn't fair when compared with Test2 as the class is not sealed. The JIT compiler will never inline an unsealed virtual method on an unsealed class because it doesn't know that it won't be inherited from (even if it's an internal class, because it doesn't perform semantic analysis of the application to detect whether it is inherited from).

I can't guarantee it the virtual method will be inlined if the class is sealed, but I can guarantee it won't be if it isn't.

GregBeech at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 6

Hello Greg,

It is my understanding that a generic class is not JITed unless TKey and TValue have concrete values or objects.

I've made a semantic change in the code, and replaced the virtual keyword with abstract.

When the JIT "compiles" MyDictionary<TKey,TValue>.Test1(TKey,TKey) into MyDictionaryInt<string>.Test1(int,int)

it implements the method within a sealed class. I am not looking for runtime polymorphism/overloading, but rather just-in-time compiling polymorphism/specialization. Theoretically speaking, it seams reasonable to inline the call to Compare(a,b) in the generic Test1 method, as it is being specialized in the context of IntMyDictionary.

Regards,

Itai

namespace TestIntCompare

{

public abstract class MyDictionary<TKey,TValue>

where TKey : System.IComparable

{

abstract protected int Compare(TKey x, TKey y);

public int Test1(TKey a,TKey b)

{

int c = 0;

const int count = 10000;

for (int i = 0; i < count; i++)

{

for (int j = 0; j < count; j++)

{

c += this.Compare(a, b);

}

}

return c;

}

}//MyDictionary

sealed public class MyDictionaryInt<TValue> : MyDictionary<int,TValue>

{

public int Test2(int a,int b)

{

int c = 0;

const int count = 10000;

for (int i = 0; i < count; i++)

{

for (int j = 0; j < count; j++)

{

c += Compare2(a,b);

//c += a - b;

}

}

return c;

}

private int Compare2(int x, int y)

{

return x - y;

}

override protected int Compare(int x, int y)

{

return x - y;

}

}

class Program

{

static void Main(string[] args)

{

MyDictionaryInt<string> aDictionary = new MyDictionaryInt<string>();

System.Console.Write("a=");

int a = System.Convert.ToInt32(System.Console.ReadLine());

System.Console.Write("b=");

int b = System.Convert.ToInt32(System.Console.ReadLine());

int c;

int tick1 = System.Environment.TickCount;

c = aDictionary.Test1(a,b);

int tick2 = System.Environment.TickCount;

c = aDictionary.Test2(a,b);

int tick3 = System.Environment.TickCount;

if (c == 0)

{

System.Console.WriteLine("a==b");

}

else if (c < 0)

{

System.Console.WriteLine("a<b");

}

else

{

System.Console.WriteLine("a>b");

}

System.Console.WriteLine("Test1 Ticks="+ (tick2 - tick1));

System.Console.WriteLine("Test2 Ticks="+ (tick3 - tick2));

}

}

}

ItaiFrenkel at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 7

An abstract method is still virtual. The runtime doesn't in-line a call to a virtual/abstract method because it can never be sure that method pointer hasn't been replaced with a new reference to a different object that overrides that method.

PeterRitchie at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 8

What Peter said.

Ultimately, what it sounds like you're looking for is an __inline macro for .NET, but it doesn't exist. The opposite does exist with [MethodImpl(MethodImplOptions.NoInlining)] because inlining could arguably affect the function of your application (one could look at GC.KeepAlive as an example of a method that needs this attribute, as otherwise it would be optimised away) whereas lack of inlining can't.

I'm not making any case as to whether the ability to indicate you'd like a function to be inlined or not is a good or bad thing, I'm just saying the way it is.

You could try the x64 JIT for interests sake; it has more optimisations than the x86 one.

GregBeech at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...
# 9

Even in C++ with __inline, there's no guarentee of inlining; __inline is merely a suggestion. It would depend on the compiler; but I would imagine it would be rare that the compiler would actually listen to __inline.

PeterRitchie at 2007-10-3 > top of Msdn Tech,Visual C#,Visual C# Language...