C#编译器或Jitter会优化这些算术运算吗?

时间:2011-12-28 05:24:01

标签: c# .net optimization compiler-construction jit

假设我有这样的事情:

for (int i = 0; i < 1001; i++)
{
    double step = i / 1000.0;

    // do some math here
}

基本上转向:

double step = i / 1000.0;

进入这个:

double step = i * 0.001;

我不确定是否可以在不改变程序结果的情况下进行这种更改,但是想知道C#编译器或抖动是否做了类似的事情?如果没有,为什么?我认为它不值得,或者它们还没有添加这个优化。

4 个答案:

答案 0 :(得分:8)

让我们把它分解成几个问题:

  

抖动能否合法地将d / 1000.0更改为d * 0.001

不,因为这两个计算给出了不同的结果。请记住,浮点数是二进制分数,而不是小数分数;当一个双精度等于1/3时,0.001作为双精度并不完全等于1/1000大于0.333333333.0.001是最接近1/1000的分数,可用52个二进制位表示。因此,存在x / 1000.0不等于x * 0.001的值。

  

抖动能否合法地将d / 2.0更改为d * 0.5

是。在这种情况下,值可以精确地以二进制表示,因为1/2在底部具有2的小功率。

抖动还可以将整数除法和乘法(如x / 2x * 2)更改为x >> 1x << 1

  

合法时抖动是否真的这样做了?

我不知道。试试吧!

你要做的是编译程序“零售”,然后启动它而不是在调试器中并运行它,直到你知道有问题的代码被jitted。然后附加调试器并检查jitted代码。如果抖动知道附加了调试器,那么抖动将产生更糟糕的代码,因为它试图生成更容易调试的代码。

  

我认为它不值得,或者它们还没有添加此优化。

对于除乘乘法,你假设乘法比除法快。现代芯片在这两方面都相当不错;虽然除法通常需要更多位操作,但差异可能是可以忽略不计的。

答案 1 :(得分:5)

你可以尝试一下,但我今天感觉很慷慨,所以我为你做了。

测试1:

    static void Test1(int i)
    {
        double x = i / 1000.0;
        if (x == 0)
            throw new Exception();
    }

(抛出是为了便于在恰当的时刻安装调试器)

反汇编(64位):

cvtsi2sd    xmm0,dword ptr [rsp+60h] 
divsd       xmm0,mmword ptr [000000C8h] 

反汇编(32位):

fild        dword ptr [ebp-4] 
fdiv        dword ptr ds:[0460012Ch] 

好的,测试代码2:i / 2.0
反汇编(64位):

cvtsi2sd    xmm0,dword ptr [rsp+60h] 
divsd       xmm0,mmword ptr [000000C8h] 

反汇编(32位):

fild        dword ptr [ebp-4] 
fdiv        dword ptr ds:[0460012Ch] 

结论:不,JIT编译器不进行此优化 有关系吗?不经常。你可以轻松地修复&#34;通过编写i * (1 / 1000.0)或其他一些内容(在这种情况下必须进行常量折叠 - 不要删除括号)。

JIT编译器 对整数执行此优化。

答案 2 :(得分:4)

我从这两种方法开始:

public static double Division(double i)
{
    return i / 1000.0;
}

public static double Multiplication(double i)
{
    return i * 0.001;
}

编译,然后在ILSpy中打开程序集。这是由此产生的IL:

.method public hidebysig static 
    float64 Division (
        float64 i
    ) cil managed 
{
    // Method begins at RVA 0x2052
    // Code size 12 (0xc)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldc.r8 1000
    IL_000a: div
    IL_000b: ret
} // end of method Program::Division

.method public hidebysig static 
    float64 Multiplication (
        float64 i
    ) cil managed 
{
    // Method begins at RVA 0x205f
    // Code size 12 (0xc)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: ldc.r8 0.001
    IL_000a: mul
    IL_000b: ret
} // end of method Program::Multiplication

正如您所看到的,它不会将乘法转换为除法或将除法转换为乘法。我不清楚一个操作如何优化另一个操作。

编辑:忘记抖动。嗯,那是平台依赖的。我认为,除非你是Eric Lippert,否则它甚至不可能回答。

答案 3 :(得分:0)

这就是你问的问题吗?我有点不确定..

for(double i = .001; i < 1.001; i+=.001){
    //TODO: Implement
}