Can I compare IL code to determine which technique is faster or better?
c#, linqpad, performance
Solution
- For this specific example, are my assumptions correct?
- In general, how should I go about comparing two solutions via IL code?
- In general, does a solution with fewer IL LOC mean that it will be faster or use less memory?
- As the title says, Can I compare IL code to determine which technique is faster or better?
1) Your assumptions are correct about what's happening.
2) You need to understand what the IL code is doing in order to determine which is "better"
3) No. It means it takes fewer instructions to run. However, these individual instructions might use more memory or less. For example, the instruction you were referencing, in one case is creating a Func delegate, and in the other is creating a Converter object. Without more information, it's difficult to tell which of those two things is more expensive.
4) Yes and no....
The problem is that the IL code will tell you what's happening, but it's really the nested calls in IL that are going to be the large performance driver. If the IL code is doing simple operations everywhere, in general the shorter the better (although individual IL operations can vary in speed, themselves). When the code calls into methods or constructors on other types, such as yours, this becomes impossible to tell from this alone. One line of IL can take longer in one case (if it's calling an expensive method, for example) than 50 in another case (where they're doing simple operations).
In your case above, for example, the first 20 operations are very, very fast, wheras the last few take nearly all of your executable time.
Problem
Background This question got me thinking about something. Lately, since I've been looking at linq pad's IL functionality, I've been comparing the IL code of two approaches to the same problem to "determine" which is best. Using the question linked to above, about converting an array, I generated the IL code for the two answers: ``` var arr = new string[] { "1", "2", "3", "4" }; var result = Array.ConvertAll(arr, s => Int32.Parse(s)); ``` produced: ``` IL_0001: ldc.i4.4 IL_0002: newarr System.String IL_0007: stloc.2 IL_0008: ldloc.2 IL_0009: ldc.i4.0 IL_000A: ldstr "1" IL_000F: stelem.ref IL_0010: ldloc.2 IL_0011: ldc.i4.1 IL_0012: ldstr "2" IL_0017: stelem.ref IL_0018: ldloc.2 IL_0019: ldc.i4.2 IL_001A: ldstr "3" IL_001F: stelem.ref IL_0020: ldloc.2 IL_0021: ldc.i4.3 IL_0022: ldstr "4" IL_0027: stelem.ref IL_0028: ldloc.2 IL_0029: stloc.0 IL_002A: ldloc.0 IL_002B: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_0030: brtrue.s IL_0045 IL_0032: ldnull IL_0033: ldftn b__0 IL_0039: newobj System.Converter<System.String,System.Int32>..ctor IL_003E: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_0043: br.s IL_0045 IL_0045: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_004A: call System.Array.ConvertAll IL_004F: stloc.1 b__0: IL_0000: ldarg.0 IL_0001: call System.Int32.Parse IL_0006: stloc.0 IL_0007: br.s IL_0009 IL_0009: ldloc.0 IL_000A: ret ``` and the other answer: ``` var arr = new string[] { "1", "2", "3", "4" }; var result = arr.Select(s => int.Parse(s)).ToArray(); ``` produced: ``` IL_0001: ldc.i4.4 IL_0002: newarr System.String IL_0007: stloc.2 IL_0008: ldloc.2 IL_0009: ldc.i4.0 IL_000A: ldstr "1" IL_000F: stelem.ref IL_0010: ldloc.2 IL_0011: ldc.i4.1 IL_0012: ldstr "2" IL_0017: stelem.ref IL_0018: ldloc.2 IL_0019: ldc.i4.2 IL_001A: ldstr "3" IL_001F: stelem.ref IL_0020: ldloc.2 IL_0021: ldc.i4.3 IL_0022: ldstr "4" IL_0027: stelem.ref IL_0028: ldloc.2 IL_0029: stloc.0 IL_002A: ldloc.0 IL_002B: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_0030: brtrue.s IL_0045 IL_0032: ldnull IL_0033: ldftn b__0 IL_0039: newobj System.Func<System.String,System.Int32>..ctor IL_003E: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_0043: br.s IL_0045 IL_0045: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 IL_004A: call System.Linq.Enumerable.Select IL_004F: call System.Linq.Enumerable.ToArray IL_0054: stloc.1 b__0: IL_0000: ldarg.0 IL_0001: call System.Int32.Parse IL_0006: stloc.0 IL_0007: br.s IL_0009 IL_0009: ldloc.0 IL_000A: ret ``` Looking at this, all I can tell is that the latter option - takes 1 extra line - uses linq when the 1st answer doesn't - creates the Int's differently via IL_0039. Questions - For this specific example, are my assumptions correct? - In general, how should I go about comparing two solutions via IL code? - In general, does a solution with fewer IL LOC mean that it will be faster or use less memory? - As the title says, Can I compare IL code to determine which technique is faster or better? FWIW, I don't do this often, just every once in a rare while when some discussion comes up amongst developers at work. Someone will say "oh this is more efficient" and we'll throw it into linqpad to check out the IL code. Also FWIW, I almost always abide by the getting it working before getting it efficient/fast approach. Just so people don't think I'm constantly comparing IL code of what I'm developing :)