Nvcc в CUDA неправильно компилирует троичные операторы / условные сокращения? - PullRequest
0 голосов
/ 15 марта 2012

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

//for printf
#include <stdio.h>

#include <cuda.h>

__inline __host__ void gpuAssert(cudaError_t code, char *file, int line, 
                 bool abort=true)
{
   if (code != cudaSuccess) 
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code),
          file, line);
      //if (abort) exit(code);
   }
}

#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }

__global__ void myKernel1(int *dev_idx, int *dev_tID, const int offset)
{
   int myElement = threadIdx.x + blockDim.x * blockIdx.x;
   //
   int temp;
   temp = myElement+
      offset +
      (offset==0)?0:(offset&0x01==0x0)?(offset-1)*(offset>>1):
      (offset)*(offset>>1);
   dev_idx[myElement+offset] = temp;
   dev_tID[myElement+offset] = myElement;

}

__global__ void myKernel2(int *dev_idx, int *dev_tID, const int offset)
{
   int myElement = threadIdx.x + blockDim.x * blockIdx.x;
   //
   int temp;
   temp = myElement+offset;
   if (offset != 0 && offset&0x01==0x0) temp+= (offset-1)*(offset>>1);
   if (offset != 0 && offset&0x01!=0x0) temp+= offset*( offset>>1);
   dev_idx[myElement+offset] = temp;
   dev_tID[myElement+offset] = myElement;

}

__host__ void PrintMethod1(int *h_idx, int * h_tID, const int offset, 
               const int total, const int h_set)
{
   for (int c=(h_set==0)?0:offset;
    c < (h_set==0)?offset:total;
    c++)
      printf("Element #%d --> idx: %d   tID: %d\n",
         c,h_idx[c],h_tID[c]);
}

__host__ void PrintMethod2(int *h_idx, int * h_tID, const int offset, 
               const int total, const int h_set)
{
   int loopStart = (h_set==0)?0:offset;
   int loopEnd = (h_set==0)?offset:total;
   printf("Loop Start: %d, Loop End: %d\n",
      loopStart, loopEnd);
   for (int c=loopStart; c < loopEnd; c++)
      printf("Element #%d --> idx: %d   tID: %d\n",
         c,h_idx[c],h_tID[c]);
}

//Checks if there is a compatible device
bool IsCompatibleDeviceRunning()
{
   int *dummy;
   return cudaGetDeviceCount(dummy) != cudaSuccess;
}

int main()
{
   //Check for compatible device
   if (!IsCompatibleDeviceRunning())
   {
      printf("ERROR: No compatible CUDA devices found!\n");
      exit(1);
   }
   const int total = 30;
   const int offset = total/2;

   int * h_tID, * dev_tID, * h_idx, * dev_idx, h_set;
   h_tID = (int *) malloc(total*sizeof(int));
   h_idx = (int *) malloc(total*sizeof(int));
   gpuErrchk(cudaMalloc((void **) &dev_tID,total*sizeof(int)));
   gpuErrchk(cudaMalloc((void **) &dev_idx,total*sizeof(int)));
   myKernel1<<<1,offset>>>(dev_idx, dev_tID, 0);
   //myKernel2<<<1,offset>>>(dev_idx, dev_tID, 0);
   gpuErrchk(cudaPeekAtLastError());
   gpuErrchk(cudaDeviceSynchronize());
   myKernel1<<<1,offset>>>(dev_idx, dev_tID, offset);
   //myKernel2<<<1,offset>>>(dev_idx, dev_tID, offset);
   gpuErrchk(cudaPeekAtLastError());
   gpuErrchk(cudaDeviceSynchronize());
   gpuErrchk(cudaMemcpy(h_tID, dev_tID, total*sizeof(int),
            cudaMemcpyDeviceToHost));
   gpuErrchk(cudaMemcpy(h_idx, dev_idx, total*sizeof(int),
            cudaMemcpyDeviceToHost));
   h_set = 0;
   //PrintMethod1(h_idx, h_tID, offset, total, h_set);
   PrintMethod2(h_idx, h_tID, offset, total, h_set);
   h_set = 1;
   //PrintMethod1(h_idx, h_tID, offset, total, h_set);
   PrintMethod2(h_idx, h_tID, offset, total, h_set);
   return 0;
}

При запуске MyKernel2 правильный массив записывается в массив:

Loop Start: 0, Loop End: 15
Element #0 --> idx: 0   tID: 0
Element #1 --> idx: 1   tID: 1
Element #2 --> idx: 2   tID: 2
Element #3 --> idx: 3   tID: 3
Element #4 --> idx: 4   tID: 4
Element #5 --> idx: 5   tID: 5
Element #6 --> idx: 6   tID: 6
Element #7 --> idx: 7   tID: 7
Element #8 --> idx: 8   tID: 8
Element #9 --> idx: 9   tID: 9
Element #10 --> idx: 10   tID: 10
Element #11 --> idx: 11   tID: 11
Element #12 --> idx: 12   tID: 12
Element #13 --> idx: 13   tID: 13
Element #14 --> idx: 14   tID: 14
Loop Start: 15, Loop End: 30
Element #15 --> idx: 120   tID: 0
Element #16 --> idx: 121   tID: 1
Element #17 --> idx: 122   tID: 2
Element #18 --> idx: 123   tID: 3
Element #19 --> idx: 124   tID: 4
Element #20 --> idx: 125   tID: 5
Element #21 --> idx: 126   tID: 6
Element #22 --> idx: 127   tID: 7
Element #23 --> idx: 128   tID: 8
Element #24 --> idx: 129   tID: 9
Element #25 --> idx: 130   tID: 10
Element #26 --> idx: 131   tID: 11
Element #27 --> idx: 132   tID: 12
Element #28 --> idx: 133   tID: 13
Element #29 --> idx: 134   tID: 14

Когда запускается MyKernel1, с идентичным троичным назначением idx, он получает ноль для всех результатов:

Loop Start: 0, Loop End: 15
Element #0 --> idx: 0   tID: 0
Element #1 --> idx: 0   tID: 1
Element #2 --> idx: 0   tID: 2
Element #3 --> idx: 0   tID: 3
Element #4 --> idx: 0   tID: 4
Element #5 --> idx: 0   tID: 5
Element #6 --> idx: 0   tID: 6
Element #7 --> idx: 0   tID: 7
Element #8 --> idx: 0   tID: 8
Element #9 --> idx: 0   tID: 9
Element #10 --> idx: 0   tID: 10
Element #11 --> idx: 0   tID: 11
Element #12 --> idx: 0   tID: 12
Element #13 --> idx: 0   tID: 13
Element #14 --> idx: 0   tID: 14
Loop Start: 15, Loop End: 30
Element #15 --> idx: 0   tID: 0
Element #16 --> idx: 0   tID: 1
Element #17 --> idx: 0   tID: 2
Element #18 --> idx: 0   tID: 3
Element #19 --> idx: 0   tID: 4
Element #20 --> idx: 0   tID: 5
Element #21 --> idx: 0   tID: 6
Element #22 --> idx: 0   tID: 7
Element #23 --> idx: 0   tID: 8
Element #24 --> idx: 0   tID: 9
Element #25 --> idx: 0   tID: 10
Element #26 --> idx: 0   tID: 11
Element #27 --> idx: 0   tID: 12
Element #28 --> idx: 0   tID: 13
Element #29 --> idx: 0   tID: 14

Когда запускается PrintMethod1 (с троичной границей), он выходит из строя, по сути, застревая в бесконечном цикле. Обратите внимание, это на стороне хоста !!

Когда запускается PrintMethod2, выходные данные печатаются обычно так, как ожидалось выше.

Вот моя команда компиляции:

nvcc --compiler-options -fno-strict-aliasing -DUNIX -m64 -O2 \
--compiler-bindir /usr/bin/g++ \
-gencode=arch=compute_20,code=\"sm_21,compute_20\" \
-I/usr/local/CUDA_SDK/C/common/inc -I/usr/local/CUDA_SDK/shared/inc \
-o TEST Test.cu

О единственной подсказке, которую я имею, это то, что она жалуется, что оба ядра имеют неправильный параметр, хотя выглядит правильно и дает правильные результаты для MyKernel2.

Я думаю, что приведенный выше пример - это почти то, что комментаторы могли бы попробовать самостоятельно, основываясь на приведенном ниже описании, но это экономит ваше время и усилия при написании кода!

Дайте мне знать, если я смогу опубликовать что-нибудь еще, чтобы помочь выяснить это.

Оригинальный вопрос

Большинство компиляторов Си, как определено lang. стандартная поддержка троичных операторов.

* * Например, тысяча тридцать один
int myVar;
myVar=(testFlg==true)?-1:1;

Однако, как ни странно, nvcc CUDA, кажется, удаляет некоторые троичные операторы и заменяет их нулями, когда они используются в ядре ...

Я обнаружил это, применив cuPrintf для проверки проблемного блока кода. Например, допустим, у меня есть два ядра, которые совместно используют глобальный массив для своего вывода. Первое ядро ​​имеет дело с первым блоком элементов. Второе ядро ​​получает смещение, указывающее, как далеко нужно перейти в массиве, чтобы не перезаписывать элементы первого ядра. Смещение отличается для четного и нечетного.

Чтобы я мог написать:

if (krnl!=0 && offset&0x01==0x0)
   idx+=(offset-1)*(offset>>1);
if (krnl!=0 && offset&0x01!=0x0)
   idx+=offset*(offset>>1);

Но было бы более компактным и удобным для чтения (на мой взгляд) написать почти эквивалентный сокращенный синтаксис.

idx += (krnl==0)?0:(offset&0x01==0)?
   (offset-1)*(offset>>1):
   offset*(offset>>1);

Последний код, тем не менее, всегда будет давать ноль, так как компилятор CUDA исключает условные обозначения.

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

Это ошибка в компиляторе или она намеренно не поддерживается?

Кто-нибудь знает, появится ли эта функция в CUDA?

Я был очень удивлен, обнаружив, что это было источником моих ошибок адресации и segfaults ...

EDIT
Это стандартная функция C, я неправильно ее прочитал и сказал, что она нестандартная.

РЕДАКТИРОВАТЬ 2
Я сказал "дроссели и умирает" для компилятора. «Умирает» было определенно неуместной терминологии для использования. Скорее, nvcc завершает компиляцию, но, по-видимому, удалил троичное операторное присваивание и заменил его нулем. Позже это вернулось бы и укусило меня, поскольку материал не записывался в правильные места, и эти места в свою очередь использовались как индексы в схеме с двойным индексированием. Индексы использовались во время обработки на стороне процессора, следовательно, ошибка произошла на стороне процессора, но была вызвана перехватом компилятора.

Я использую компилятор v4.1 и -O2 включен. Похоже, что оптимизатор может оптимизировать переменную, которая используется внутри троичной операции, которая может быть источником этой ошибки.

Тернарная операция, подверженная ошибкам, практически идентична приведенному выше примеру, но участвует в большой операции сложения.

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

Редактировать 3

Здесь слегка очищенное полное утверждение, которое всегда дает ноль:

__global__ void MyFunc
( const int offset
  const CustomType * dev_P,
  ...
  const int box)
{
   int tidx = blockIdx.x * blockDim.x + threadIdx.x;
   int idx=0;
   ...
   idx = tidx +
      dev_P->B +
      (box == 0)?0:(offset&0x01!=0x0):
      (offset-1)*(offset>>1):offset*(offset>>1);
   //NOTES:
   //I put the cuPrintf here.... from it I could see that tidx was diff. ints (as you 
   //would expect), but that when added together the sum was always "magically"
   //becoming zero.  The culprit was the nested ternary operator.
   //Once I replaced it with the equivalent conditional, the assignment worked as
   //expected.
   //"offset" is constant on the level of this kernel, but it is not always 0.
   //Outside the kernel "offset" varies greatly over the course of the simulation,
   //meaning that each time the kernel is called, it likely has a different value.
   //"tidx" obviously varies.
   //but somehow the above sum gave 0, likely due to an unreported compiler bug.
   //box is either 0 or 1.  For a certain type of op in my simulation I call this 
   //kernel twice, once for box value 0 and a second time for box value 1
   ...
}

1 Ответ

1 голос
/ 19 марта 2012

Я нашел ответ ... это общая проблема С, а не специфичная для CUDA.

Тернарный оператор имеет очень низкий приоритет, как на LHS, так и на RHS (странно разные приоритеты для каждого, хотя).

Однако приоритет можно переопределить, заключив в круглые скобки весь троичный, например, ((...)?...:...).

Я задал общий вопрос об общемсмысл принятия этого подхода для языкового стандарта здесь: Неожиданный результат, троичный оператор в Gnu C

...