我可以比较 IL 代码以确定哪种技术更快或更好吗?
背景
这个问题让我思考一些事情。 最近,由于我一直在研究 linq pad 的 IL 功能,我一直在比较解决同一问题的两种方法的 IL 代码,以“确定”哪种方法最好。
使用上面链接的关于转换数组的问题,我生成了两个答案的 IL 代码:
var arr = new string[] { "1", "2", "3", "4" };
var result = Array.ConvertAll(arr, s => Int32.Parse(s));
Produce:
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
和另一个答案:
var arr = new string[] { "1", "2", "3", "4" };
var result = arr.Select(s => int.Parse(s)).ToArray();
Produce:
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
看着这个,我只能说后一个选项
- 需要 1 个额外的行
- 使用 linq当第一个答案没有
- 通过 IL_0039 创建不同的 Int 时。
问题
- 对于这个具体示例,我的假设正确吗?
- 一般来说,我应该如何通过 IL 代码比较两个解决方案?
- 一般来说,IL LOC 较少的解决方案是否意味着它会更快或使用更少的内存?
- 正如标题所说,我可以比较 IL 代码来确定哪种技术更快或更好吗?
FWIW,我不经常这样做,只是偶尔在工作中的开发人员之间进行一些讨论时这样做。 有人会说“哦,这更有效率”,我们将把它扔到 linqpad 中检查 IL 代码。 另外,FWIW,我几乎总是遵守先使其工作,然后再获得高效/快速的方法。 这样人们就不会认为我一直在比较我正在开发的 IL 代码:)
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.CSlt;>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.CSlt;>9__CachedAnonymousMethodDelegate1
IL_0043: br.s IL_0045
IL_0045: ldsfld UserQuery.CSlt;>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.CSlt;>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.CSlt;>9__CachedAnonymousMethodDelegate1
IL_0043: br.s IL_0045
IL_0045: ldsfld UserQuery.CSlt;>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 :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
1)你对正在发生的事情的假设是正确的。
2) 您需要了解 IL 代码正在做什么,才能确定哪个“更好”。
3) 不。这意味着运行所需的指令更少。 但是,这些单独的指令可能会使用更多或更少的内存。 例如,您引用的指令在一种情况下是创建 Func 委托,在另一种情况下是创建 Converter 对象。 如果没有更多信息,很难判断这两种东西哪一个更贵。
4) 是和否...
问题是 IL 代码会告诉您发生了什么,但实际上 IL 中的嵌套调用将成为主要的性能驱动因素。 如果 IL 代码在各处执行简单操作,通常越短越好(尽管各个 IL 操作本身的速度可能有所不同)。 当代码调用其他类型(例如您的类型)的方法或构造函数时,仅凭这一点就无法区分。 在一种情况下(例如,如果它调用昂贵的方法),一行 IL 可能比在另一种情况下(例如,它们执行简单操作)花费 50 行更长的时间。
例如,在上面的例子中,前 20 个操作非常非常快,而最后几个操作几乎占用了所有的可执行时间。
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.
这两个答案的大部分工作都是在 IL 004A 中完成的(第二个答案的工作是在 IL 004F 中)。 除非您知道这些外部调用的成本,否则没有实际基础可以比较两个答案的性能。
The chunk of the work for both answers is done at IL 004A (and IL 004F for the second one). Unless you know the cost of these external calls, there's no practical basis on which you could compare the performance of the two answers.