Gui*_*e07 16 c# optimization jit
using System;
namespace ConsoleApplication1
{
class TestMath
{
static void Main()
{
double res = 0.0;
for(int i =0;i<1000000;++i)
res += System.Math.Sqrt(2.0);
Console.WriteLine(res);
Console.ReadKey();
}
}
}
Run Code Online (Sandbox Code Playgroud)
通过将此代码与c ++版本进行基准测试,我发现性能比c ++版本慢10倍.我对此没有任何问题,但这引出了以下问题:
似乎(在几次搜索之后)JIT编译器无法像c ++编译器那样优化此代码,即只调用sqrt一次并在其上应用*1000000.
有没有办法迫使JIT这样做?
Han*_*ant 10
我重申,我将C++版本的时钟设置为1.2毫秒,C#版本为12.2毫秒.如果你看一下C++代码生成器和优化器发出的机器代码,原因很容易看出来.它重写这样的循环(使用C#等价物):
double temp = Math.Sqrt(2.0);
for (int i = 0; i < 1000000; ++i) {
res += temp;
}
Run Code Online (Sandbox Code Playgroud)
这是两种优化的组合,称为"不变代码运动"和"循环提升".换句话说,C++编译器对sqrt()函数有足够的了解,知道它的返回值不受周围代码的影响,因此可以随意移动.然后,将该代码移出循环并创建一个额外的局部变量来存储结果是值得的.并且计算sqrt()比添加慢.听起来很明显,但这是一个必须内置到优化器中的规则,必须考虑许多规则之一.
是的,抖动优化器错过了那个.它无法与C++优化器花费相同的时间,它在严重的时间限制下运行.因为如果它需要太长时间,那么程序开始时需要花费太多时间.
舌头:C#程序员需要比代码生成器更聪明,并且自己认识到这些优化机会.这是一个相当明显的问题.那么,既然你现在知道了:)
要进行所需的优化,编译器必须确保函数Sqrt()始终为特定输入返回相同的值.
编译器可以执行各种检查,函数不使用任何其他"外部"变量来查看它是否是无状态的.但这并不总是意味着它不会受到副作用的影响.
当在循环中调用函数时,应该在每次迭代中调用它(想想多线程环境,看看为什么这很重要).因此,如果他想要进行那种优化,通常由用户自行调整循环.
回到C++编译器 - 编译器可能对其库函数进行了某些优化.许多编译器尝试优化像数学库这样的重要库,因此可能是特定于编译器的.
另一个很大的区别是在C++中你通常会从头文件中包含那些东西.这意味着编译器可能具有决定函数调用在调用之间是否不发生变化所需的所有信息.
.Net编译器(在编译时 - Visual Studio)并不总是具有要解析的所有代码.大多数库函数已经编译(进入IL - 第一阶段).考虑到第三方dll,可能无法进行深度优化.在JIT(运行时)编译中,跨程序集进行这些优化可能成本太高.
如果Math.Sqrt注释为,则可能对JIT(甚至C#编译器)有所帮助[Pure]。然后,假设函数的参数像您在示例中一样是恒定的,则可以将值的计算放到循环外。
而且,这样的循环可以合理地转换为代码:
double res = 1000000 * Math.Sqrt(2.0);
Run Code Online (Sandbox Code Playgroud)
理论上,编译器或JIT可以自动执行此操作。但是,我怀疑它将针对实际代码中很少发生的模式进行优化。
我为ReSharper打开了一个功能请求,建议设计时工具建议进行这种重构。
| 归档时间: |
|
| 查看次数: |
1125 次 |
| 最近记录: |