473,405 Members | 2,445 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,405 software developers and data experts.

Interesting performance odities

Hi,

I was initially benchmarking inlinine capabilities of C# in VS 2005 while I
stumble on the following surprising results.

Please take a look at the following complete sample (you can just copy+paste
and compile it yourself)
>>>>>


using System;

public interface I { void Do () ; }

public class A : I { public void Do () {} }

public struct B<T> : I { public void Do () {} }

public class C : I { public virtual void Do () {} }

public class Test
{
const int c = int.MaxValue / 10 ;

static public void Try( I f )
{
int s = Environment.TickCount ;
for( int i = 0 ; i < c ; ++ i )
f.Do();
int e = Environment.TickCount ;
Console.WriteLine(e-s);
}

static public void GTry<F>( F f ) where F : I
{
int s = Environment.TickCount ;
for( int i = 0 ; i < c ; ++ i )
f.Do();
int e = Environment.TickCount ;
Console.WriteLine(e-s);
}
}

class Program
{

static void Main(string[] args)
{
Test.Try ( new A() );
Test.GTry( new A() );
Test.Try ( new B<int>() );
Test.GTry( new B<int>() );
Test.Try ( new C() );
}

}
<<<<<<<<<<<

In that program there are 3 types:

A a non-generic interface
A non-generic class implementing the interface.
And a generic class also implementing the interface.

All the interface declares is a trival no-op method.

There are two _exactly equivalent_ methods which differ ONLY in that one
takes the interface non-generically, and the other takes any type
implementing the interface generically.
The methods just call the trivial function on the parameter a number of
times and outputs the ellapsed time taken.

Do you expect any difference in the times taken to call Do() in each case?

I certianly wouldn't (Do is not a virtual method and it's trivial)

Now compile in release mode (making sure the code is optimized) and run it
_outside the IDE_.

I get the following surprising results:

1515
1516
2531
140
2109

Why is this surprising?

A few things:

(1) The method from the non-generic class A is not inlined at all in the
calls.

(2) The method in the generic-class B is as slow as if it were virtual
(compare it to class C) when called from the non-generic interface
(something which doesn't happen with A)

(3) The method is ONLY fully inlined when called on the generic-class B (not
on the non-generic class A) AND from the a generic parameter (thus fully
retaining its dynamic type) rather than an interface.

Can anyone help me make sense out of it? [or spot a mistake in the
benchmarks?]

TIA
--
Fernando Cacciola
SciSoft
http://fcacciola.50webs.com/



Feb 3 '06 #1
10 1094
Fernando,

See inline:
(1) The method from the non-generic class A is not inlined at all in the
calls.
Why should it be? You are passing an interface implementation. The
call on the interface is never going to be inlined by the JIT.
(2) The method in the generic-class B is as slow as if it were virtual
(compare it to class C) when called from the non-generic interface
(something which doesn't happen with A)
No, it is slow because you are passing a structure to B and it is
getting boxed in the non-generic version of the method. In the generic
version, the parameter is not boxed.
(3) The method is ONLY fully inlined when called on the generic-class B
(not on the non-generic class A) AND from the a generic parameter (thus
fully retaining its dynamic type) rather than an interface.
This has nothing to do with it being generic. Rather, it is a matter of
in B, the public method is exposed as the implementation, and it is not
virtual, so therefore, that method can be inlined (that's not to say that it
^is^ inlined, but it is possible that it can be since it is not virtual).

Hope this helps.

--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard.caspershouse.com
"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:Oq****************@TK2MSFTNGP15.phx.gbl... Hi,

I was initially benchmarking inlinine capabilities of C# in VS 2005 while
I stumble on the following surprising results.

Please take a look at the following complete sample (you can just
copy+paste and compile it yourself)
>>>>>>


using System;

public interface I { void Do () ; }

public class A : I { public void Do () {} }

public struct B<T> : I { public void Do () {} }

public class C : I { public virtual void Do () {} }

public class Test
{
const int c = int.MaxValue / 10 ;

static public void Try( I f )
{
int s = Environment.TickCount ;
for( int i = 0 ; i < c ; ++ i )
f.Do();
int e = Environment.TickCount ;
Console.WriteLine(e-s);
}

static public void GTry<F>( F f ) where F : I
{
int s = Environment.TickCount ;
for( int i = 0 ; i < c ; ++ i )
f.Do();
int e = Environment.TickCount ;
Console.WriteLine(e-s);
}
}

class Program
{

static void Main(string[] args)
{
Test.Try ( new A() );
Test.GTry( new A() );
Test.Try ( new B<int>() );
Test.GTry( new B<int>() );
Test.Try ( new C() );
}

}
<<<<<<<<<<<

In that program there are 3 types:

A a non-generic interface
A non-generic class implementing the interface.
And a generic class also implementing the interface.

All the interface declares is a trival no-op method.

There are two _exactly equivalent_ methods which differ ONLY in that one
takes the interface non-generically, and the other takes any type
implementing the interface generically.
The methods just call the trivial function on the parameter a number of
times and outputs the ellapsed time taken.

Do you expect any difference in the times taken to call Do() in each case?

I certianly wouldn't (Do is not a virtual method and it's trivial)

Now compile in release mode (making sure the code is optimized) and run it
_outside the IDE_.

I get the following surprising results:

1515
1516
2531
140
2109

Why is this surprising?

A few things:
Can anyone help me make sense out of it? [or spot a mistake in the
benchmarks?]

TIA
--
Fernando Cacciola
SciSoft
http://fcacciola.50webs.com/



Feb 3 '06 #2
Nicholas Paldino [.NET/C# MVP] wrote:
Fernando,

See inline:
(1) The method from the non-generic class A is not inlined at all in
the calls.
Why should it be? You are passing an interface implementation. The
call on the interface is never going to be inlined by the JIT.

I surely expected methods called from interfaces to be inlined, but, well,
now I regret to know they won't.
(2) The method in the generic-class B is as slow as if it were
virtual (compare it to class C) when called from the non-generic
interface (something which doesn't happen with A)


No, it is slow because you are passing a structure to B and it is
getting boxed in the non-generic version of the method.


Are you saying that when you pass a value-type to a method accepting an
interface, the value is boxed?
I surely never expected that.
Also, even if you were right, the difference is 1 second and there is at
most just 1 single boxing when the function object is passed to the Try
method. So no, your explanation doesn't account for the difference.
See below if you disagree.
In the generic version, the parameter is not boxed.
(3) The method is ONLY fully inlined when called on the
generic-class B (not on the non-generic class A) AND from the a
generic parameter (thus fully retaining its dynamic type) rather
than an interface.


This has nothing to do with it being generic. Rather, it is a
matter of in B, the public method is exposed as the implementation,
and it is not virtual, so therefore, that method can be inlined
(that's not to say that it ^is^ inlined, but it is possible that it
can be since it is not virtual).

To me it has to do with it being generic (the compiler sees the
implementation _precisely_ because its not dealing with an interface but a
concrete type) (that's why I mentioned that its dynamic type was fully
retained)
Maybe I should have said that this very particular result was the only one I
expected.

I changed my code so that B is not a struct anymore but a class (that was a
mechanical mistake from years of C++ sample snippets)

These are the new results:

1484
1484
2078
3235
2062

Notice that

Test.Try ( new B<int>() );

Is still as slow as the virtual call.

But... What happened to the greately inlined call!!??
Why is
Test.GTry ( new B<int>() );

now taking _ven more_than the virtual call? I just changed B from a struct
to a class.

Puzzled
--
Fernando Cacciola
SciSoft
http://fcacciola.50webs.com/


Feb 3 '06 #3
Fernando,
I surely expected methods called from interfaces to be inlined, but, well,
now I regret to know they won't.
Nope, the JIT will not inline them.
Are you saying that when you pass a value-type to a method accepting an
interface, the value is boxed?
Absolutely.
Also, even if you were right, the difference is 1 second and there is at
most just 1 single boxing when the function object is passed to the Try
method. So no, your explanation doesn't account for the difference.
See below if you disagree.
There is only one boxing, but you have to remember, everything counts in
large amounts. Your math is a little off.

There is a difference of 1016 ticks between the two. What you are not
factoring in is that there are 214,748,364 calls occuring in the method!!!!
If you divide the 1016 ticks by that, you come out with 4.73e-6 which is
4.73e-9 extra seconds per call! That's a little less than one billionth of
a second.

Yes, you are right, it is not because of boxing, but you can't just
ignore the number of times you iterate and not factor it back into a
per-call number.

Also, you should use the new Stopwatch class in System.Diagnostics in
order to time these things. The counter is based on the performance
counter, which is more accurate.
To me it has to do with it being generic (the compiler sees the
implementation _precisely_ because its not dealing with an interface but a
concrete type) (that's why I mentioned that its dynamic type was fully
retained)
Maybe I should have said that this very particular result was the only one
I expected.
Yes, but it still has nothing to do with being generic. If the method
is not virtual, or an interface implementation, then there is no chance that
it will be inlined.
--
- Nicholas Paldino [.NET/C# MVP]
- mv*@spam.guard.caspershouse.com

"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:eZ****************@tk2msftngp13.phx.gbl... Nicholas Paldino [.NET/C# MVP] wrote:
Fernando,

See inline:
(1) The method from the non-generic class A is not inlined at all in
the calls.


Why should it be? You are passing an interface implementation. The
call on the interface is never going to be inlined by the JIT.

(2) The method in the generic-class B is as slow as if it were
virtual (compare it to class C) when called from the non-generic
interface (something which doesn't happen with A)


No, it is slow because you are passing a structure to B and it is
getting boxed in the non-generic version of the method.


I surely never expected that.
In the generic version, the parameter is not boxed.
(3) The method is ONLY fully inlined when called on the
generic-class B (not on the non-generic class A) AND from the a
generic parameter (thus fully retaining its dynamic type) rather
than an interface.


This has nothing to do with it being generic. Rather, it is a
matter of in B, the public method is exposed as the implementation,
and it is not virtual, so therefore, that method can be inlined
(that's not to say that it ^is^ inlined, but it is possible that it
can be since it is not virtual).

To me it has to do with it being generic (the compiler sees the
implementation _precisely_ because its not dealing with an interface but a
concrete type) (that's why I mentioned that its dynamic type was fully
retained)
Maybe I should have said that this very particular result was the only one
I expected.

I changed my code so that B is not a struct anymore but a class (that was
a mechanical mistake from years of C++ sample snippets)

These are the new results:

1484
1484
2078
3235
2062

Notice that

Test.Try ( new B<int>() );

Is still as slow as the virtual call.

But... What happened to the greately inlined call!!??
Why is
Test.GTry ( new B<int>() );

now taking _ven more_than the virtual call? I just changed B from a struct
to a class.

Puzzled
--
Fernando Cacciola
SciSoft
http://fcacciola.50webs.com/


Feb 3 '06 #4
"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:eZ**************@tk2msftngp13.phx.gbl...
I surely expected methods called from interfaces to be inlined, but, well,
now I regret to know they won't.
As the compiler would not know what type of object that method would be
called with, how could it *possibly* inline the call?
Are you saying that when you pass a value-type to a method accepting an
interface, the value is boxed?


Again, the compiler knows *nothing* about the object being passed, other
than it implements that interface. It has to assume that sometimes it will
be passed a reference-type (which, in your very example, sometime it is).
Hence, everything passed to it must be (or be made into) a reference-type.
Remember, there is one one copy of Test.Try(), and it must handle both As
and B<T>s.

--
Truth,
James Curran
[erstwhile VC++ MVP]

Home: www.noveltheory.com Work: www.njtheater.com
Blog: www.honestillusion.com Day Job: www.partsearch.com
Feb 3 '06 #5
James Curran wrote:
"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:eZ**************@tk2msftngp13.phx.gbl...
I surely expected methods called from interfaces to be inlined, but,
well, now I regret to know they won't.


Again, the compiler knows *nothing* about the object being passed,
other than it implements that interface. It has to assume that
sometimes it will be passed a reference-type (which, in your very
example, sometime it is). Hence, everything passed to it must be (or
be made into) a reference-type. Remember, there is one one copy of
Test.Try(), and it must handle both As and B<T>s.

Doesn't the JIT knows the exact target address of the called function
through the interface?
If not, what's the difference with a virtual function?
Or is it that interfaces are just abstract base classes in disguise?

TIA
--
Fernando Cacciola
SciSoft
http://fcacciola.50webs.com/
Feb 3 '06 #6
On Fri, 3 Feb 2006 15:53:40 -0500, "Nicholas Paldino [.NET/C# MVP]"
<mv*@spam.guard.caspershouse.com> wrote:
Yes, but it still has nothing to do with being generic. If the method
is not virtual, or an interface implementation, then there is no chance that
it will be inlined.


I think you meant to say here: "If the method _is_ virtual..."
--
http://www.kynosarges.de
Feb 4 '06 #7
"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:OG**************@tk2msftngp13.phx.gbl...
Doesn't the JIT knows the exact target address of the called function
through the interface?
If not, what's the difference with a virtual function?
Or is it that interfaces are just abstract base classes in disguise?

All that's irrelevant, because (as I repeat): There is one copy of
Test.Try(), and it must handle both As and B<T>s.

Sure, the JIT knows that exact target address, but it both cases, it's
the same address.

--
Truth,
James Curran
[erstwhile VC++ MVP]

Home: www.noveltheory.com Work: www.njtheater.com
Blog: www.honestillusion.com Day Job: www.partsearch.com
Feb 6 '06 #8

"James Curran" <ja*********@mvps.org> wrote in message
news:uk**************@tk2msftngp13.phx.gbl...
"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:OG**************@tk2msftngp13.phx.gbl...
Doesn't the JIT knows the exact target address of the called function
through the interface?
If not, what's the difference with a virtual function?
Or is it that interfaces are just abstract base classes in disguise?

All that's irrelevant, because (as I repeat): There is one copy of
Test.Try(), and it must handle both As and B<T>s.

Sure, the JIT knows that exact target address, but it both cases, it's
the same address.


Thus pointing out the difference between generics (in this case boxing and
calling the same function) and templates (creating a specialized function
for each) -- this may be the OP's confusion.


--
Truth,
James Curran
[erstwhile VC++ MVP]

Home: www.noveltheory.com Work: www.njtheater.com
Blog: www.honestillusion.com Day Job: www.partsearch.com

Feb 7 '06 #9
Mike <vi********@yahoo.com> wrote:
Sure, the JIT knows that exact target address, but it both cases, it's
the same address.


Thus pointing out the difference between generics (in this case boxing and
calling the same function) and templates (creating a specialized function
for each) -- this may be the OP's confusion.


No, the generic version doesn't have the boxing. It's the non-generic
version which has the boxing.

In generics, each reference type gets the same implementation (which
then can't do inlining of interface methods) but each value type gets a
different implementation (which is able to inline).

--
Jon Skeet - <sk***@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
Feb 7 '06 #10
James Curran wrote:
"Fernando Cacciola" <fe***************@hotmail.com> wrote in message
news:OG**************@tk2msftngp13.phx.gbl...
Doesn't the JIT knows the exact target address of the called function
through the interface?
If not, what's the difference with a virtual function?
Or is it that interfaces are just abstract base classes in disguise?

All that's irrelevant,


I'ts irrelevant as far as the discussion about inlining goes. But that was
just really a comment of mine (I tried to explain why the results were odd
to me).
The important thing of this whole thread which is going unnoticed is that
the generic's type method performs as if it where a virtual function _even_
if called in a generic method (that is, even if seen in its actual type).

So, forget about inlining.

Can you explain why all these 3 methods take the same average time?
Or at least, don't you find it surprising and odd?

Test.Try ( new B<int>() );
Test.GTry( new B<int>() );

Test.Try ( new C() );

TIA
--
Fernando Cacciola
SciSoft
http://fcacciola.50webs.com/


Feb 7 '06 #11

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

15
by: Nick Coghlan | last post by:
Thought some folks here might find this one interesting. No great revelations, just a fairly sensible piece on writing readable code :) The whole article:...
23
by: Bruno R. Dias | last post by:
Perhaps it would be interesting to program a virtual machine simulating an ancient computer (such as the pdp-7). Then, it would be rather interesting to code for it (porting gcc to it maybe?). I...
7
by: David Sworder | last post by:
Hi, I'm developing an application that will support several thousand simultaneous connections on the server-side. I'm trying to maximize throughput. The client (WinForms) and server communicate...
12
by: Daniel Earwicker | last post by:
I wrote two trivial test programs that do a billion iterations of a virtual method call, first in C# (Visual Studio 2005): Thing t = new DerivedThing(); for (System.Int64 n = 0; n < 10000000000;...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: Sonnysonu | last post by:
This is the data of csv file 1 2 3 1 2 3 1 2 3 1 2 3 2 3 2 3 3 the lengths should be different i have to store the data by column-wise with in the specific length. suppose the i have to...
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...
0
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.