Будет ли C # -компилятор или Jitter оптимизировать такие арифметические операции? - PullRequest
2 голосов
/ 28 декабря 2011

Предположим, у меня есть что-то вроде этого:

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 ]

8 голосов
/ 28 декабря 2011

Давайте разберем его на несколько вопросов:

Может ли джиттер юридически изменить d / 1000.0 на d * 0.001?

Нет, потому что эти два вычисления дают разные результаты. Помните, что числа с плавающей запятой - это двоичные, а не десятичные дроби; что 0,001 как двойное число точно не равно 1/1000 больше, чем 0,333333333 как двойное, точно равно 1/3. 0,001 - это ближайшая дробная часть к 1/1000, которая может быть выражена в 52 двоичных битах. И поэтому существуют такие значения, что x / 1000.0 не равен x * 0,001.

Может ли джиттер юридически изменить d / 2.0 на d * 0.5?

Да. В этом случае значения могут быть представлены точно в двоичном виде, потому что 1/2 имеет небольшую степень два снизу.

Джиттер также может изменять целочисленные деления и умножения, такие как x / 2 или x * 2 на x >> 1 или x << 1.

Действительно ли джиттер делает это, когда это законно?

Я не знаю. Попробуйте!

Вам нужно скомпилировать программу "retail", а затем запустить ее , а не в отладчике , и запускать ее до тех пор, пока вы не узнаете, что рассматриваемый код был присоединен. Затем присоедините отладчик и изучите приведенный код. Джиттер будет генерировать худший код, если он знает, что отладчик подключен, потому что он пытается сгенерировать код, который легче отлаживать.

Полагаю, либо оно того не стоит, либо они еще не добавили эту оптимизацию.

В случае деления на умножение вы предполагаете, что умножение происходит быстрее, чем деление. Современные чипсы чертовски хороши в обоих; хотя для деления обычно требуется больше битовых операций, возможно, разница незначительна.

5 голосов
/ 28 декабря 2011

Вы могли бы просто попробовать это, но я чувствую себя щедрым сегодня, поэтому я сделал это для вас.

Тест 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 голосов
/ 28 декабря 2011

Я начал с этих двух методов:

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 голосов
/ 28 декабря 2011

Это то, что вы спрашивали? Я был немного неуверен ..

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