【问题标题】:C# compiler optimizations for benchmarking purposes用于基准测试的 C# 编译器优化
【发布时间】:2011-09-25 07:54:27
【问题描述】:

我需要检查一些 .NET API 的性能,我想出了这段代码。

Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++) {
    int res = (int) SOME .NET FUNCTION TO RUN;
}
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;

再想一想,我担心编译器会优化所有操作,因为res 不会在任何地方使用。我修改了代码如下。

Stopwatch sw = Stopwatch.StartNew();
long sumIt = 0;
for (int i = 0; i < 1000; i++) {
    int res = (int) SOME .NET FUNCTION TO RUN;
    sumIt += res;
}
sw.Stop();
double time = sw.Elapsed.TotalMilliseconds;
value = (int) sumIt / 1000;

有趣的是,编译器似乎没有在我的第一个示例中优化操作。我使用 csc (Visual Studio) 和 mono 进行了测试。

我的问题来了。

  • 作为我的第一个示例进行基准测试是否安全?
  • 难道 C# 编译器还不够聪明,无法理解某些代码可以优化出来吗?或者,是否有任何编译器参数?

添加

SOME FUNCTION TO RUN实际上是SOME .NET FUNCTION TO RUN,我修改了OP。 根据答案,C# 编译器似乎无法(或没有)优化操作,因为 .NET FUNCTION TO RUN 可能有副作用。

【问题讨论】:

  • 如果 SOMEFUNCION 有副作用怎么办?您不使用它的返回值这一事实并不意味着它可以被丢弃。从理论上讲,编译器可以分析函数(以及该函数中的任何函数,...),但我认为这不切实际,我不认为它这样做。
  • 我在过去编写一些基准测试时也想知道这一点。我还没有遇到一个结论性的声明,但似乎编译器不会优化对驻留在另一个程序集中的方法(也包括属性获取器,顺便说一句)的任何调用。

标签: c# .net optimization profiling benchmarking


【解决方案1】:

如果编译器 (JIT) 发现它没有副作用,它可能会优化整个函数调用。它可能需要能够内联函数才能检测到这一点。

我尝试了只对输入参数起作用的小函数,并通过检查生成的程序集来查看它是否已被优化(确保在未选中“抑制模块加载时的优化”的情况下尝试发布构建)。

 ...
 for (int i = 0; i < 1000; i++) 
 {    
    int res = (int) Func(i);
 }
 ...

 static int Func(int arg1)
 {
    return arg1 * arg1;
 }

反汇编:

      for (int i = 0; i < 1000; i++) 
    00000016  xor         eax,eax 
    00000018  inc         eax 
    00000019  cmp         eax,3E8h 
    0000001e  jl          00000018 
        }

【讨论】:

  • 听起来好像循环内的函数有任何不能内联的函数。 C# 编译器无法优化代码。我说的对吗?
  • 我不知道。理论上,JIT(不是 C# 编译器)也可以用常规函数做到这一点——只有 JIT 团队的人才能给你明确的答案。
  • @prossek 我不是 C# 编译器方面的专家,但对于 Hotspot,答案是肯定的。绝大多数优化不能跨方法边界工作(积极内联对 JIT 如此重要的一个原因)——或者更确切地说,他们可以实现这一点,但不值得额外的复杂性和成本。基本上,JIT 必须确保在所有可能的代码路径中,即使所有代码都可以内联,该函数也不会产生可观察到的副作用。
猜你喜欢
  • 1970-01-01
  • 2017-02-28
  • 1970-01-01
  • 2011-09-02
  • 2011-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-03
相关资源
最近更新 更多