C# 编译器或 Jitter 会优化此类算术运算吗?

问题描述 投票:0回答:4

假设我有这样的东西:

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# 编译器或抖动是否会执行类似的操作?如果没有,为什么?我认为要么不值得,要么他们还没有添加此优化。

c# .net optimization jit
4个回答
8
投票

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

抖动可以合法的把

d / 1000.0
变成
d * 0.001
吗?

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

抖动可以合法的把

d / 2.0
变成
d * 0.5
吗?

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

抖动还可以将整数除法和乘法(如

x / 2
x * 2
)更改为
x >> 1
x << 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编译器没有做这个优化。
有关系吗?不经常。您可以通过编写

i * (1 / 1000.0)
或类似的内容轻松“修复”它(在这种情况下,必须强制折叠 - 不要删除括号)。

JIT 编译器对整数进行这种优化。


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

如您所见,它不会将乘法更改为除法或将除法更改为乘法。 不过,我不清楚一个操作如何相对于另一个操作进行优化。

编辑:忘记了抖动。 嗯,这取决于平台。 所以我想,除非你是埃里克·利珀特,否则根本不可能回答。


0
投票

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

for(double i = .001; i < 1.001; i+=.001){
    //TODO: Implement
}
© www.soinside.com 2019 - 2024. All rights reserved.