Div / Mul с плавающей точкой> в 30 раз медленнее, чем Add / Sub? - PullRequest
4 голосов
/ 20 июля 2010

Я недавно прочитал этот пост: С плавающей точкой против целочисленных вычислений на современном аппаратном обеспечении , и мне было любопытно, что касается производительности моего собственного процессора в этом квази-бенчмарке, поэтому я собрал две версии кода,один в C # и один в C ++ (Visual Studio 2010 Express) и скомпилировал их оба с оптимизацией, чтобы посмотреть, что получится.Вывод из моей версии C # довольно разумный:

int add/sub: 350ms
int div/mul: 3469ms
float add/sub: 1007ms
float div/mul: 67493ms
double add/sub: 1914ms
double div/mul: 2766ms

Когда я скомпилировал и запустил версию C ++, что-то совершенно другое вытряхнуло:

int add/sub: 210.653ms
int div/mul: 2946.58ms
float add/sub: 3022.58ms
float div/mul: 172931ms
double add/sub: 1007.63ms
double div/mul: 74171.9ms

Я ожидал некоторых отличий в производительности, но неэто большое!Я не понимаю, почему деление / умножение в C ++ намного медленнее, чем сложение / вычитание, где управляемая версия C # более оправдывает мои ожидания.Код для версии функции на C ++ выглядит следующим образом:

template< typename T> void GenericTest(const char *typestring)
{
    T v = 0;
    T v0 = (T)((rand() % 256) / 16) + 1;
    T v1 = (T)((rand() % 256) / 16) + 1;
    T v2 = (T)((rand() % 256) / 16) + 1;
    T v3 = (T)((rand() % 256) / 16) + 1;
    T v4 = (T)((rand() % 256) / 16) + 1;
    T v5 = (T)((rand() % 256) / 16) + 1;
    T v6 = (T)((rand() % 256) / 16) + 1;
    T v7 = (T)((rand() % 256) / 16) + 1;
    T v8 = (T)((rand() % 256) / 16) + 1;
    T v9 = (T)((rand() % 256) / 16) + 1;

    HTimer tmr = HTimer();
    tmr.Start();
    for (int i = 0 ; i < 100000000 ; ++i)
    {
        v += v0;
        v -= v1;
        v += v2;
        v -= v3;
        v += v4;
        v -= v5;
        v += v6;
        v -= v7;
        v += v8;
        v -= v9;
    }
    tmr.Stop();

      // I removed the bracketed values from the table above, they just make the compiler
      // assume I am using the value for something do it doesn't optimize it out.
    cout << typestring << " add/sub: " << tmr.Elapsed() * 1000 << "ms [" << (int)v << "]" << endl;

    tmr.Start();
    for (int i = 0 ; i < 100000000 ; ++i)
    {
        v /= v0;
        v *= v1;
        v /= v2;
        v *= v3;
        v /= v4;
        v *= v5;
        v /= v6;
        v *= v7;
        v /= v8;
        v *= v9;
    }
    tmr.Stop();

    cout << typestring << " div/mul: " << tmr.Elapsed() * 1000 << "ms [" << (int)v << "]" << endl;
}

Код для тестов C # не является общим и реализован таким образом:

static double DoubleTest()
{
    Random rnd = new Random();
    Stopwatch sw = new Stopwatch();

    double v = 0;
    double v0 = (double)rnd.Next(1, int.MaxValue);
    double v1 = (double)rnd.Next(1, int.MaxValue);
    double v2 = (double)rnd.Next(1, int.MaxValue);
    double v3 = (double)rnd.Next(1, int.MaxValue);
    double v4 = (double)rnd.Next(1, int.MaxValue);
    double v5 = (double)rnd.Next(1, int.MaxValue);
    double v6 = (double)rnd.Next(1, int.MaxValue);
    double v7 = (double)rnd.Next(1, int.MaxValue);
    double v8 = (double)rnd.Next(1, int.MaxValue);
    double v9 = (double)rnd.Next(1, int.MaxValue);

    sw.Start();
    for (int i = 0; i < 100000000; i++)
    {
        v += v0;
        v -= v1;
        v += v2;
        v -= v3;
        v += v4;
        v -= v5;
        v += v6;
        v -= v7;
        v += v8;
        v -= v9;
    }
    sw.Stop();

    Console.WriteLine("double add/sub: {0}", sw.ElapsedMilliseconds);
    sw.Reset();

    sw.Start();
    for (int i = 0; i < 100000000; i++)
    {
        v /= v0;
        v *= v1;
        v /= v2;
        v *= v3;
        v /= v4;
        v *= v5;
        v /= v6;
        v *= v7;
        v /= v8;
        v *= v9;
    }
    sw.Stop();

    Console.WriteLine("double div/mul: {0}", sw.ElapsedMilliseconds);
    sw.Reset();

    return v;
}

Есть идеи здесь?

Ответы [ 5 ]

4 голосов
/ 21 июля 2010

Для тестов float div / mul вы, вероятно, получаете денормализованные значения, которые намного медленнее обрабатывают обычные значения с плавающей точкой.Это не проблема для тестов int и может возникнуть гораздо позже для двойных тестов.

Вы должны иметь возможность добавить это в начало C ++, чтобы сбрасывать денормалы в ноль:

_controlfp(_DN_FLUSH, _MCW_DN);

Я не уверен, как это сделать в C # (или даже если это возможно).

Дополнительная информация здесь: Время выполнения с плавающей запятой

3 голосов
/ 20 июля 2010

Возможно, что C # оптимизировал деление на vx для умножения на 1 / vx, поскольку он знает, что эти значения не были изменены во время цикла, и он может вычислить обратные значения только один раз.

Выможете выполнить эту оптимизацию самостоятельно и рассчитать время в C ++.

2 голосов
/ 20 июля 2010

Если вас интересует скорость с плавающей точкой и возможные оптимизации, прочитайте эту книгу: http://www.agner.org/optimize/optimizing_cpp.pdf

, также вы можете проверить это: http://msdn.microsoft.com/en-us/library/aa289157%28VS.71%29.aspx

Ваши результаты могут зависеть от вещейтакие как JIT, флаги компиляции (отладка / выпуск, какие оптимизации FP выполнять или разрешенный набор инструкций).

Попробуйте установить эти флаги на максимальное количество оптимизаций и измените вашу программу, чтобы она точно не производилапереполнения или NAN, потому что они влияют на скорость вычислений.(даже что-то вроде "v + = v1; v + = v2; v - = v1; v - = v2;" в порядке, поскольку оно не будет уменьшено в "строгом" или "точном" режиме с плавающей запятой).Также старайтесь не использовать больше переменных, чем у вас есть регистры FP.

1 голос
/ 20 июля 2010

Умножение не плохо.Я думаю, что это на несколько циклов медленнее, чем сложение, но да, деление на очень медленное по сравнению с другими.Это занимает значительно больше времени и, в отличие от трех других операций, не передается по конвейеру.

0 голосов
/ 21 июля 2010

Я также решил, что ваш C ++ был невероятно медленным. Так что я запустил это сам. Оказывается, на самом деле, вы совершенно не правы. ошибка http://img59.imageshack.us/img59/3597/loltimer.jpg

Я заменил ваш таймер (я понятия не имею, какой таймер вы использовали, но у меня его нет под рукой) на высокопроизводительный таймер Windows. Эта вещь может сделать наносекунды или лучше. Угадай, что? Visual Studio говорит нет. Я даже не настраивал его для максимальной производительности. VS может видеть сквозь все это дерьмо и ликвидировать все циклы. Вот почему вы никогда не должны использовать этот тип «профилирования». Получить профессиональный профилировщик и вернуться. Если 2010 Экспресс не отличается от 2010 Professional, в чем я сомневаюсь. В основном они отличаются функциями IDE, а не производительностью и оптимизацией исходного кода.

Я даже не собираюсь беспокоиться о том, как запустить ваш C #.

Редактировать: это DEBUG x64 (предыдущий экран x86, но я думал, что буду делать x64, так как я на x64), и я также исправил небольшую ошибку, из-за которой время было скорее отрицательным, чем положительным. Так что, если вы не хотите сказать мне, что ваш релиз FP на 32-битной версии в сто раз медленнее, я думаю, что вы облажались. альтернативный текст http://img693.imageshack.us/img693/1866/loltimerdebug.jpg

Одна вещь, которая мне показалась любопытной, заключается в том, что программа отладки x86 никогда не завершалась во втором тесте с плавающей запятой, то есть, если вы сначала выполнили float, то удвоили, это был двойной div / mul, который не удался. Если вы выполнили удвоение, то float, float div / mul потерпел неудачу. Должен быть глюк компилятора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...