CUDA nvcc неправильно компилирует тернарные операторы/условные сокращения?

РЕДАКТИРОВАНИЕ
Вот небольшая программа, которую вы скомпилируете, чтобы увидеть такие ошибки самостоятельно...

//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 (с тернарным ограничением), он segfaults, по существу застревая в бесконечном цикле. Обратите внимание, это на стороне хоста!!

При запуске 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.

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

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

Исходный вопрос

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

e.g.

int myVar;
myVar=(testFlg==true)?-1:1;

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

Я обнаружил это, применив 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?

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

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

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

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

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

Я планирую последовать совету комментатора ниже и отправить отчет об ошибке в 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
   ...
}

person Jason R. Mick    schedule 15.03.2012    source источник
comment
...?...:... — это GNU C, а не C++, поэтому я не понимаю, какое отношение интерфейс nvvc к C++ имеет к его поддержке или отсутствию функций C. Однако основа C-компилятора будет... gnu.org/software/gnu-c-manual/   -  person Jason R. Mick    schedule 15.03.2012
comment
Я не уверен, что вы говорите о том, что это расширение GNU - a ? b : c является тернарным оператором C (en.wikipedia.org/wiki/Ternary_operation) и полностью, полностью, универсально стандартна для C и языков, производных от C. GNU имеет забавное расширение этого, где вы можете опустить b, и он вернет выражение a вместо него (так, например, вы возвращаете a, если a не равно нулю, и в этом случае вы возвращаете c), но насколько я может видеть, что вы не используете это.   -  person Jonathan Dursi    schedule 15.03.2012
comment
@ Джонатан ... О, я подумал, основываясь на своем чтении, что это нестандартно, должно быть, неправильно прочитал. Но да, это указывает на то, что это, вероятно, ошибка компилятора (я бы предположил)?   -  person Jason R. Mick    schedule 15.03.2012
comment
@ Джонатан, спасибо, я исправил описание этой проблемы.   -  person Jason R. Mick    schedule 15.03.2012
comment
Почему бы не опубликовать более полный код? По моему опыту тернарный оператор отлично работает в CUDA C/C++. Компилятор выполняет устранение мертвого кода. Так, например, если он определяет, что krnl является константой, код будет упрощен, особенно если krnl присваивается ноль. Какой у вас код для инициализации krnl?   -  person harrism    schedule 16.03.2012
comment
Я не могу опубликовать полный код, так как в настоящее время он закрыт, но я попытаюсь его очистить и опубликовать как можно большую выдержку позже.   -  person Jason R. Mick    schedule 16.03.2012
comment
Тернарный оператор, который вы показали, является стандартным C и C++. OTOH тернарный оператор с пропущенным операндом является расширением GCC. Пример: х = а? : б; Это может не поддерживаться CUDA.   -  person Ashwin Nanjappa    schedule 16.03.2012
comment
@Ashwin Да, я исправил это в отредактированной версии, см. выше ... Я тоже был удивлен, я на 90 процентов уверен, что это какая-то ошибка оптимизации, потому что, когда я поставил точно такое же условие ниже, оно исправило это .. .   -  person Jason R. Mick    schedule 16.03.2012
comment
@harrism Я поместил код выше, переименовав переменные и обрезав остальную часть ядра. Опять же, я мог сказать, что происходит что-то странное, потому что tidx почти никогда не было равно нулю (это только ноль для первого потока ядра, но сумма его добавления к результату тернарного оператора была ВСЕГДА 0, что подтверждается как cuPrintf вызовами, так и cudaMemcpy выводом результата с устройства... Как только я заменил тернарный оператор (путем разделения на добавление первых двух терминов, чем условное добавление для замены тернарного ) эта странность исчезла, к счастью.   -  person Jason R. Mick    schedule 16.03.2012
comment
Не помогает, так как вы не указали значения Box или offset...   -  person harrism    schedule 16.03.2012
comment
@harrism ... Box - это переданный параметр функции ядра, либо 1, либо 0, я добавлю это в список параметров, забыл это в вырезанном описании. offset — это параметр, передаваемый ядру, он меняется во время выполнения — может быть от 128 до 150 000 в зависимости от того, как я настроил симуляцию и как долго она выполняется. Он постоянен только внутри этого ядра. Надеюсь, это поможет.   -  person Jason R. Mick    schedule 16.03.2012
comment
С точки зрения логики, в основном длина первого набора индексов (троичный блок, основанный на offset) применяется только в том случае, если вы находитесь во втором box, поскольку первый набор использует первый участок доступных индексов.   -  person Jason R. Mick    schedule 16.03.2012
comment
(Я должен сказать, что второе обозначено значением box 1... более подробную информацию см. в пересмотренном комментарии.)   -  person Jason R. Mick    schedule 16.03.2012
comment
Разве вы не можете написать простое автономное ядро, которое воспроизводит проблему? Очень сложно диагностировать потенциальную проблему компилятора без компиляции реального кода. Кроме того, вы пробовали смотреть на выдаваемый компилятором PTX и дизассемблированный вывод ассемблера, чтобы увидеть, что происходит на устройстве. Пока что этот вопрос кажется не чем иным, как плохо описанным предположением, множеством правок и маханием руками.....   -  person talonmies    schedule 16.03.2012
comment
Спасибо за отзыв talonmies. Позже постараюсь опубликовать полный код.   -  person Jason R. Mick    schedule 16.03.2012
comment
@talonmies Опубликовал пример кода, который не работает ... проверьте его !! Спасибо.   -  person Jason R. Mick    schedule 19.03.2012
comment
@harrism, посмотрите недавно опубликованный пример кода, это может быть полезно ...   -  person Jason R. Mick    schedule 19.03.2012
comment
Джейсон, реляционные операторы C/C++ имеют более высокий приоритет, чем побитовые операторы. Поэтому ваше выражение offset&0x01==0x0 всегда равно нулю. Я подозреваю, что это не то, что вы имеете в виду.   -  person harrism    schedule 19.03.2012
comment
@harrism Хороший улов! Аааа, я этого не понимал... но это все еще не объясняет, почему полный оператор всегда равен 0... если этот оператор всегда оценивается как ноль, он все равно должен добавлять смещение, насколько я могу видеть (хотя и неправильный)... например (offset==0)?0:... должен правильно вводить предложение ..., верно? Я до сих пор не понимаю, как это утверждение последовательно оценивается как 0, даже если оно неверно с точки зрения порядка операций во втором предложении.   -  person Jason R. Mick    schedule 19.03.2012
comment
Похоже, что он должен последовательно оценивать myElement+offset+(offset)*(offset>>1) (неверно, но не ноль), если offset!=0, или еще myElement+offset, если offset==0 (правильно и не ноль... то, как появляется последовательное 0, сбивает с толку, исходя из моих текущих знаний.   -  person Jason R. Mick    schedule 19.03.2012
comment
Я согласен. Я все еще думаю, что можно было бы сделать более простую реплику ...   -  person harrism    schedule 19.03.2012
comment
@harrism Извините, это было долго, я вас слышу. По сути, я просто хотел показать, что он терпит неудачу на стороне host (в цикле for) и на стороне device. Мне интересно, является ли эта ошибка эндемичной для всех gnu ... проверит программу, отличную от CUDA.   -  person Jason R. Mick    schedule 19.03.2012
comment
@harrism и др. Ааа, ответ здесь... stackoverflow.com/questions/7499400/ Первый, кто опубликует сообщение, получит халяву. :)   -  person Jason R. Mick    schedule 19.03.2012
comment
Подождите... на самом деле это касается троек на LHS... немного отличается.   -  person Jason R. Mick    schedule 19.03.2012
comment
Я знал, что все зависело от приоритета. Поэтому я и намекнул вам на очевидную ошибку приоритета в вашем коде (у меня просто не было времени найти их все). Вот почему код, который вы пишете, так опасен: тернарные операторы (и особенно вложенные) имеют тенденцию запутывать смысл кода. Напишите этот код в несколько строк и используйте вместо этого if/else, и он будет намного чище, менее ломким и более удобным в сопровождении.   -  person harrism    schedule 19.03.2012


Ответы (1)


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

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

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

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

person Jason R. Mick    schedule 19.03.2012