14

In Javascript for example, one is strongly encouraged to place function calls outside of loops for better performance:

var id = someIdType.ToString();
someList.Where(a => a.id == id) ...

How about C#? Same case or does the compiler/runtime employ internal optimization/caching?

someList.Where(a => a.id == someIdType.ToString()) ...

Probably a noob question and has been asked before, but can't find a reference.

3
  • What have you tried? Do you have a sample application that you could do some measurements on? Commented Nov 6, 2014 at 7:10
  • 3
    A very simple test using the sample you provided (looking up a string) within a list of 1000000 elements, the latter one is 4 times slower, ~118ms against ~32ms. Atleast on my workstation ;) Commented Nov 6, 2014 at 7:29
  • 2
    Before you do any manual optimization, it's important to know whether it's worth the effort - is this piece of code actually the bottleneck in performance for your application? If not, just pick the form that you think reads best and leave it alone. If it is the bottleneck, then try out different variants and pick the one that performs best. Don't try to learn thousands of "performance" rules and then rigidly code according to them. It's better to write clear code that you'll be able to go back and quickly understand in 6 months time than to try to write all code as if every ms counts. Commented Nov 6, 2014 at 9:04

2 Answers 2

13

C# code:

List<string> list = new List<string>();
list.Where(a => a == typeof(String).ToString());

Lambda expression in MSIL, Debug configuration:

.method private hidebysig static bool  '<Main>b__0'(string a) cil managed
{
  .custom instance void     [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       26 (0x1a)
  .maxstack  2
  .locals init ([0] bool CS$1$0000)
  IL_0000:  ldarg.0
  IL_0001:  ldtoken    [mscorlib]System.String
  IL_0006:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_000b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0010:  call       bool [mscorlib]System.String::op_Equality(string,
                                                             string)
  IL_0015:  stloc.0
  IL_0016:  br.s       IL_0018
  IL_0018:  ldloc.0
  IL_0019:  ret
} // end of method Program::'<Main>b__0'

Lambda expression in MSIL, Release configuration:

.method private hidebysig static bool  '<Main>b__0'(string a) cil managed
{
  .custom instance void     [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldtoken    [mscorlib]System.String
  IL_0006:  call       class [mscorlib]System.Type     [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
  IL_000b:  callvirt   instance string [mscorlib]System.Object::ToString()
  IL_0010:  call       bool [mscorlib]System.String::op_Equality(string,
                                                             string)
  IL_0015:  ret
} // end of method Program::'<Main>b__0'

Both versions call typeof(String).ToString()), this lambda is called on every iteration. No optimization on IL level, JIT compilation will not add anything here. The reason is: function may have side effects.

Sign up to request clarification or add additional context in comments.

5 Comments

I don't think this is necessarily/particularly due to the fact that function may have side effects... For example, the member being accessed inside the lambda may be a field, and not a function... Even though field access does not have side effect, it will not be cached.. Hence to put is as "reason is: function may have side effects" is not exactly accurate.. So I'd simply say.. Compiler / Runtime doesn't cache it.. Why? Who knows? May be they never considered it.. May be they had 10 reasons, including side effecting functions.. may be they found the feature in general to be low ROI.
@VikasGupta - probably it is better to formulate this as "one of the reasons". I tested this for public field, it is not optimized as well.
Another good reason: function call (and even field access) may give different results from time to time. For example, Random.Next() mentioned by @Mad Sorcerer. Or result of multithreading, when object is changed in another thread.
@AlexFarber Random.Next() is not a good example of this other reason, since this is a function that has side effects - And those side effects is the reason that the output changes.
Looking at MSIL doesn't help, code hoisting is an optimization performed by the jitter on the generated machine code.
5

The lambda will be executed for each element of the list.. Hence the code someIdType.ToString() will execute for every element. I don't think compiler or runtime will cache it for you. (AFAIK someIdType will be captured in a closure, but not .ToString())

EDIT: The original question was only about "Does?", but not about "Why?", but still there are several comments and other answers which attempt to answer / demonstrate "Why?".

Given so much interest around "Why?" I am editing my answer to state my version of "Why?". i.e. if you look at C# specification, for any of the relevant scenarios, the specification talks about capturing the variable.. not capturing the expression. That is the reason why compiler behaves the way it does.. Because its not in the specification. Why is it not in the specification, is something C# Design team can answer.. Rest is speculation, parts or all of which may or may not have merit, should the feature of capturing expressions be considered.

5 Comments

Compiler cannot and will not cache it, because in C# functions are not pure functions, i.e. they can have side-effects or return different values on same arguments (look at Random.Next())
It is reasonable. Another question. What if called method has Pure attribute? Does it make sense?
@serdar AFAIK no, it does not enable such optimizations.
@MadSorcerer In fact if it enabled that optimization that would cause very big problems in the case we incorrectly put a pure attribute to a method which has side effects.
Even if it were possible to reliably determine which functions are "pure", you'd still have another problem: a pure function may still throw an exception, and if that pure function is called in a loop which should be executed not at all (let's say you're using foreach on an empty list), you should get no exception. If the call were simply moved out of the loop, you would get an exception.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.