2012-06-24 7 views
4

Я нашел проблему с стандартом float-сервера host-client в OpenCL. Проблема заключалась в том, что плавающие точки, рассчитанные Opencl, не соответствуют тем же плавающим точкам, что и мой компилятор visual studio 2010 при компиляции в x86. Однако при компиляции в x64 они находятся в одном пределе. Я знаю, что это должно быть что-то с, http://www.viva64.com/en/b/0074/OpenCL Точка плавающей запятой

Источником я использовал во время тестирования был: http://www.codeproject.com/Articles/110685/Part-1-OpenCL-Portable-Parallelism Когда я запускал программу в x86 это дало бы мне 202 номера, которые были равны, когда ядро ​​и программа C++ взяли квадрат из 1269760 номеров. Однако в 64-битной сборке 1269760 номеров были правильными, другими словами 100%. Кроме того, я обнаружил, что ошибка между вычисленным результатом opencl и x86 C++ была 5.5385384e-014, что является очень небольшой долей, но не достаточно малой, по сравнению с эпсилон числа, которое было 2.92212543378266922312416e-19.
Это потому, что ошибка должна быть меньше, чем epsilon, так что программа может распознавать два числа как одно одинаковое число. Конечно, обычно никто не сравнивал бы поплавки изначально, но хорошо знать, что ограничения поплавка разные. И да, я попытался установить flt: static, но получил ту же ошибку.

Так что я хочу дать объяснения этому поведению. Спасибо за все ответы.

+0

Возможный дубликат [Скорость OpenCL и точность точки плавания] (http://stackoverflow.com/questions/11170012/opencl-speed-and-float-point-precision) – talonmies

+0

@talonmies, он был разделен на вопрос о том, что вы указали на это, а не на дубликат. –

ответ

8

Поскольку при преобразовании проекта с 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 
+0

Может быть nitpick, но x86-64 требует, чтобы процессор имел SSE2, а не только SSE. –

-1

В моем случае

(float)(f*f) 

не помогло. I использовали

correct = 0; 
    for(unsigned int i = 0; i < count; i++) { 
    volatile float sqr = data[i] * data[i]; 
    if(results[i] == sqr) 
     correct++; 
    } 

вместо этого.

+1

Я не думаю, что это то, о чем просит ОП. –