Поскольку при преобразовании проекта с x86 на x64 ничего не меняется в коде GPU, все это должно делать то, как умножение выполняется на CPU. Существуют некоторые тонкие различия между обработкой чисел с плавающей запятой в режимах x86 и x64, и самым большим является то, что, поскольку любой процессор x64 также поддерживает SSE и SSE2, он используется по умолчанию для математических операций в 64-битном режиме в Windows.
Графический процессор HD4770 выполняет все вычисления с использованием единиц с плавающей точкой с одной точностью. Современные x64 процессоры с другой стороны, есть два вида функциональных блоков, которые обрабатывают числа с плавающей точкой:
- x87 FPU, который работает с гораздо более высокой расширенной точностью 80 битых
- SSE FPU, который работает с 32-битным и 64-битная точность и очень совместима с тем, как другие ЦП обрабатывают числа с плавающей запятой
В 32-битном режиме компилятор не предполагает, что SSE доступен и генерирует обычный код FPU x87 для выполнения математики. В этом случае операции, подобные data[i] * data[i]
, выполняются внутренне с использованием гораздо более высокой 80-битной точности. Сравнение любезного if (results[i] == data[i] * data[i])
выполняется следующим образом:
data[i]
помещается в стек x87 FPU с использованием FLD DWORD PTR data[i]
data[i] * data[i]
вычисляются с помощью FMUL DWORD PTR data[i]
result[i]
помещается в стек x87 FPU с использованием FLD DWORD PTR result[i]
- оба значения сравниваются с использованием
FUCOMPP
Вот и проблема. data[i] * data[i]
находится в элементе стека x87 FPU в 80-битной точности. result[i]
поставляется с графическим процессором с 32-битной точностью. Оба номера, скорее всего, будут отличаться, так как data[i] * data[i]
имеет гораздо более значимые цифры, тогда как result[i]
имеет множество нулей (в 80-битной точности)!
В 64-битном режиме все происходит по-другому. Компилятор знает, что ваш процессор SSE способен, и он использует инструкции SSE для выполнения математики. То же самое утверждение сравнение выполняется следующим образом на x64:
data[i]
загружается в SSE регистр, используя MOVSS XMM0, DWORD PTR data[i]
data[i] * data[i]
вычисляется с использованием MULSS XMM0, DWORD PTR data[i]
result[i]
загружается в другой SSE регистр с помощью MOVSS XMM1, DWORD PTR result[i]
- оба значения сравниваются с использованием
UCOMISS XMM1, XMM0
В этом случае квадратная операция выполняется с той же 32-битной точностью, что и на графическом процессоре. Не создаются промежуточные результаты с 80-битной точностью. Вот почему результаты одинаковы.
Это очень просто проверить, даже без использования графического процессора. Просто запустите следующую простую программу:
#include <stdlib.h>
#include <stdio.h>
float mysqr(float f)
{
f *= f;
return f;
}
int main (void)
{
int i, n;
float f, f2;
srand(1);
for (i = n = 0; n < 1000000; n++)
{
f = rand()/(float)RAND_MAX;
if (mysqr(f) != f*f) i++;
}
printf("%d of %d squares differ\n", i);
return 0;
}
mysqr
специально написан так, что промежуточный 80-битный результат будет получить преобразован в 32-битной точности float
. Если скомпилировать и запустить в 64-битном режиме, выход:
0 of 1000000 squares differ
Если скомпилировать и запустить в 32-битном режиме, выход:
999845 of 1000000 squares differ
В принципе, вы должны быть в состоянии изменить модель с плавающей точкой в 32-битном режиме (Свойства проекта -> Свойства конфигурации -> C/C++ -> Генерация кода -> Модель с плавающей запятой), но это ничего не меняет, поскольку по крайней мере на VS2010 промежуточные результаты все еще сохраняются в FPU. Что вы можете сделать, так это обеспечить сохранение и перезагрузку вычисленного квадрата так, чтобы он был округлен до 32-битной точности до, что сравнивается с результатом GPU. В простом примере выше это достигается за счет изменения:
if (mysqr(f) != f*f) i++;
к
if (mysqr(f) != (float)(f*f)) i++;
После того, как изменение 32-битный выходной код становится:
0 of 1000000 squares differ
Возможный дубликат [Скорость OpenCL и точность точки плавания] (http://stackoverflow.com/questions/11170012/opencl-speed-and-float-point-precision) – talonmies
@talonmies, он был разделен на вопрос о том, что вы указали на это, а не на дубликат. –