g++, двойники, оптимизация и большой WTF

ошибка в моем gcc? ошибка в моем коде? оба?

http://files.minthos.com/code/speedtest_doubles_wtf.cpp

Каким-то образом ему удается «оптимизировать» функцию, которая приводит к обнулению массива двойных значений, что занимает 2,6 секунды на моем q6600 вместо 33 мс, которые требуется более сложной функции, чтобы заполнить массив чем-то значимым.

Мне было бы интересно узнать, получают ли другие аналогичные результаты, и если да, то может ли кто-нибудь объяснить, что происходит. А также выяснить, что вызывает огромную разницу между производительностью целых чисел и операций с плавающей запятой (особенно при компиляции без оптимизации).


person Minthos    schedule 03.09.2010    source источник


Ответы (3)


Строка 99:

memcpy(floats, ints, sizeof(floats));

частично эффективно инициализирует floats[] мусором с плавающей запятой. Остальные остаются равными нулю. Это связано с заменой чисел с плавающей запятой целочисленными растровыми изображениями и их последующей интерпретацией как двойников. Возможно, переполнения и потери значимости влияют на производительность? Для проверки я изменил начальное значение случайного числа на постоянное 1000 для воспроизводимости и получил следующие результаты:

[wally@zenetfedora Downloads]$ ./speedtest_doubles_wtf.cpp
no optimization
begin: 0.017000
floats: 27757.816000
ints: 28117.604000
floats: 40346.196000
ints: 41094.988000
sum: 7999999.998712
sum2: 67031739228347449344.000000
mild optimization
begin: 0.014000
floats: 68.574000
ints: 68.609000
floats: 147.105000
ints: 820.609000
sum: 8000000.000001
sum2: 67031739228347441152.000000
heavier optimization
begin: 0.014000
floats: 73.588000
ints: 73.623000
floats: 144.105000
ints: 1809.980000
sum: 8000000.000001
sum2: 67031739228347441152.000000
again, now using ffun2()
no optimization
begin: 0.017000
floats: 22720.648000
ints: 23076.134000
floats: 35480.824000
ints: 36229.484000
floats: 46324.080000
sum: 0.000000
sum2: 67031739228347449344.000000
mild optimization
begin: 0.013000
floats: 69.937000
ints: 69.967000
floats: 138.010000
ints: 965.654000
floats: 19096.902000
sum: 0.000000
sum2: 67031739228347441152.000000
heavier optimization
begin: 0.015000
floats: 95.851000
ints: 95.896000
floats: 206.594000
ints: 1699.698000
floats: 29382.348000
sum: 0.000000
sum2: 67031739228347441152.000000

Повторение после замены memcpy на правильное назначение, чтобы могло произойти преобразование типа, должно предотвратить граничные условия с плавающей запятой:

for(int i = 0; i < 16; i++)
{
    ints[i] = rand();
    floats[i]= ints[i];
}

Модифицированная программа, по-прежнему с константой 1000 в качестве случайного начального числа, дает следующие результаты:

[wally@zenetfedora Downloads]$ ./speedtest_doubles_wtf.cpp
no optimization
begin: 0.013000
floats: 35814.832000
ints: 36172.180000
floats: 85950.352000
ints: 86691.680000
sum: inf
sum2: 67031739228347449344.000000
mild optimization
begin: 0.013000
floats: 33136.644000
ints: 33136.678000
floats: 51600.436000
ints: 52494.104000
sum: inf
sum2: 67031739228347441152.000000
heavier optimization
begin: 0.013000
floats: 31914.496000
ints: 31914.540000
floats: 48611.204000
ints: 49971.460000
sum: inf
sum2: 67031739228347441152.000000
again, now using ffun2()
no optimization
begin: 0.014000
floats: 40202.956000
ints: 40545.120000
floats: 104679.168000
ints: 106142.824000
floats: 144527.936000
sum: inf
sum2: 67031739228347449344.000000
mild optimization
begin: 0.014000
floats: 33365.716000
ints: 33365.752000
floats: 49180.112000
ints: 50145.824000
floats: 80342.648000
sum: inf
sum2: 67031739228347441152.000000
heavier optimization
begin: 0.014000
floats: 31515.560000
ints: 31515.604000
floats: 47947.088000
ints: 49016.240000
floats: 78929.784000
sum: inf
sum2: 67031739228347441152.000000

Это старый компьютер примерно 2004 года выпуска, в остальном слабо загруженный.

Похоже, это замедлило ход событий. Возможно, меньше нулей для арифметики? Так выглядят многие случайные битовые комбинации. Или такие значения, как 0,00000000000000000000000000382652. Как только это добавляется, скажем, к 0,1, младшие биты, как правило, удаляются.

person wallyk    schedule 03.09.2010
comment
Ничего себе, я понятия не имел, что случайные биты могут так повлиять на производительность! Я озадачен и поражен :) - person Minthos; 03.09.2010
comment
Поскольку целые числа являются 64-битными (но содержат 32-битные значения), memcpy фактически инициализирует все двойные значения денормализованными значениями. - person Mike Seymour; 03.09.2010
comment
Известно, что денормализованная математика работает медленнее, по крайней мере, в некоторых реализациях. - person MSalters; 03.09.2010

Вы не сбрасываете begin между тестами, поэтому ваши временные показатели трудно интерпретировать. Может быть, это и есть источник вашего заблуждения?

person dhaffey    schedule 03.09.2010
comment
Да, хорошее предложение, я не думаю, что это проблема, но, по крайней мере, это облегчает интерпретацию вывода. Код обновлен, чтобы отразить это. - person Minthos; 03.09.2010

Все случайные 64-битные целые числа имеют нули в старших 32 битах, поскольку rand() возвращает 32-битные значения (по крайней мере, для gcc на 32-битной платформе). Таким образом, все удвоения будут денормализованы, поскольку переинтерпретированные битовые комбинации целых чисел будут иметь нуль для поля экспоненты. Добавление 0,1 к денормализованному значению дает нормализованное значение (очень близкое к 0,1).

Таким образом, каждая строка ffun2 представляет собой умножение на денормализованное значение; каждая строка ffun3 представляет собой умножение на нормализованное значение. Глядя на сгенерированную сборку, я вижу, что множители вычисляются перед циклом; в каждом случае цикл состоит только из умножений. Наиболее вероятным объяснением разницы во времени выполнения является то, что умножение занимает гораздо больше времени, если множитель денормализован.

Что касается последнего вопроса: арифметика с плавающей запятой (особенно с двойной точностью) намного сложнее, чем арифметика с целыми числами, поэтому на достаточно современном конвейерном процессоре каждая инструкция будет выполняться дольше.

person Mike Seymour    schedule 03.09.2010