С++ AMP: array_view и индекс ведут себя странно (сохраняются непредвиденные значения)

Я написал небольшую тестовую функцию, которая ведет себя не так, как я хочу.

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

Выполняя отладку кода графического процессора, я увидел, что первые несколько итераций (каким-то образом выполняющиеся параллельно... что, вероятно, имеет смысл для графического процессора, но меня удивляет, когда я отлаживаю) работают нормально... но затем, после 1-2 команд Debug-Continues (F5), некоторые ранее правильно установленные значения заменяются нулями. Я действительно не понимаю ... к тому времени, когда я снова нахожусь на ЦП, многие значения равны 0, хотя они не должны быть 0 (в основном, они должны иметь исходные данные, которые представляют собой простую тестовую последовательность).

#include "stdafx.h"
#include <amp.h>

typedef unsigned char byte;

using namespace concurrency;

void AMPChangeBrightnessContrastWrapper2(byte* a, int len, float brightness, float contrast)
    {
        array_view<unsigned int> dst(len/4, (unsigned int*)a);
        //dst.discard_data(); 
        parallel_for_each(dst.extent, [=](index<1> idx) restrict(amp) 
        {
            // split into bytes (in floats)
            float temp1 = (dst[idx])  - (dst[idx] >> 8) * 256;
            // this completely fails! float temp1 = dst[idx] & 0xFF;
            float temp2 = (dst[idx] >> 8)  - (dst[idx] >> 16) * 256;
            float temp3 = (dst[idx] >> 16) - (dst[idx] >> 24) * 256;
            float temp4 = (dst[idx] >> 24);
            // convert back to int-array
            dst[idx] = (int)(temp1 + temp2 * 256 + temp3 * 65536 + temp4 * 16777216);

        });
        //dst.synchronize();
    }

int _tmain(int argc, _TCHAR* argv[])
{
    const int size = 30000;
    byte* a = new byte[size];

      // generate some unique test sequence.. first 99 numbers are just 0..98
    for (int i = 0; i < size; ++i)
        a[i] = (byte)((i + i / 99) % 256);

    AMPChangeBrightnessContrastWrapper2(a, size, -10.0f, 1.1f);

    for (int i = 0; i < 50; ++i)
        printf("%i, ", a[i]);
    char out[20];
    scanf_s("%s", out);
    return 0;
}

Итак, простые (плановые) шаги:

  • инициализировать массив
  • передать массив в GPU (как массив целых чисел без знака)
  • разделить каждое целое число без знака на 4 байта и сохранить их в числах с плавающей запятой
  • (проведите некоторые расчеты, здесь опущенные для простоты)
  • снова объединить байты, хранящиеся в числах с плавающей запятой, в исходное положение
  • (повторить)

Если вам интересно... это должны быть значения цвета...

Результат:

  • некоторые значения соответствуют ожидаемым, но большинство из них имеют разные значения
  • кажется, что особенно байт 0 (каждого целого числа без знака) будет иметь плохое значение
  • Сначала я попытался преобразовать unsigned int->byte->float с помощью & 0xFF, но, похоже, это полностью не удалось.

Выход (но должен быть просто увеличивающимся числом, начиная с 0):

0, 1, 2, 3, 0, 5, 6, 7, 0, 9, 10, 11, 16, 13, 14, 15, 0, 17, 18, 19, 32, 21, 22, 23, 32, 25, 26, 27, 32, 29, 30, 31, 0, 33, 34, 35, 64, 37, 38, 39, 64, 41, 42, 43, 64, 45, 46, 47, 64, 49,

Вопросы:

  • почему проблема с & 0xFF?
  • почему байту 0 каждого беззнакового целого числа присваивается странное значение?
  • Я полагаю, я не могу создать массив байтов, мне нужно использовать целые числа или числа с плавающей запятой?
  • комментирование .synchronize в конце концов ничего не изменило - как так?

person Andreas Reiff    schedule 04.07.2013    source источник


Ответы (2)


• Я полагаю, я не могу создать массив байтов, я должен использовать целые числа или числа с плавающей запятой?

Вы не можете создать массив или массив байтов. C++ AMP поддерживает только ограниченное подмножество типов C++. Вы можете использовать текстуру, а не массив. Для обработки изображений это имеет несколько преимуществ, не в последнюю очередь из-за того, что упаковка и распаковка выполняются намного быстрее, поскольку они реализуются аппаратным обеспечением графического процессора. См. полный пример ниже.

• закомментирование .synchronize в итоге ничего не изменило - как так получилось?

Вам не нужен dst.synchronize(), потому что dst array_view выходит за рамки, что вызывает неявную синхронизацию данных обратно в память ЦП. Кстати, вы не должны вызывать dst.discard_data() в начале функции, потому что если вы это сделаете, данные из a не будут скопированы в GPU.

Вот реализация с использованием текстуры‹>. Обратите внимание:

  • Использование текстуры unit_4 позволяет вам получить упаковку и распаковку ваших данных «бесплатно».
  • Использование clip() лучше, чем предложения if, во-первых, оно использует встроенную функцию, для которой оптимизировано аппаратное обеспечение. В общем, ветвление внутри ядра плохо, потому что оно останавливает все потоки в варпе, даже если они оценили условие как ложное.
  • Вам нужны две текстуры, потому что, в отличие от массивов, они не поддерживают сглаживание.
  • Я удалил некоторые временные переменные. Переменные используют регистровое пространство, которое очень ограничено на GPU. Вы должны свести к минимуму его использование, чтобы убедиться, что все ваши потоки могут выполняться, не ожидая, пока освободится место в регистре.
  • Явное приведение с помощью static_cast‹> означает меньше предупреждений компилятора и в целом считается хорошим (современным) стилем C++.

И код...

void AMPChangeBrightnessContrastWrapper3(const byte* a, const int len, 
    const float brightness, const float contrast)
{
    const int pixel_len = len / 4;
    graphics::texture<graphics::uint_4, 1> inputTx(pixel_len, a, len, 8u);
    graphics::texture<graphics::uint_4, 1> outputTx(pixel_len, 8u);
    graphics::writeonly_texture_view<graphics::uint_4, 1> outputTxVw(outputTx);

    parallel_for_each( outputTxVw.extent, [=, &inputTx, &outputTx](index<1> idx) 
        restrict(amp) 
    { 
        const graphics::uint_4 v = inputTx[idx];

        float tmp = static_cast<float>(v.r);
        tmp = (tmp - 128) * contrast + brightness + 128;
        tmp = direct3d::clamp(tmp, 0.0f, 255.0f);
        const unsigned int temp1_ = static_cast<unsigned int>(tmp);

        tmp = static_cast<float>(v.g);
        tmp = (tmp - 128) * contrast + brightness + 128;
        tmp = direct3d::clamp(tmp, 0.0f, 255.0f);
        const unsigned int temp2_ = static_cast<unsigned int>(tmp);

        tmp = static_cast<float>(v.b);
        tmp = (tmp - 128) * contrast + brightness + 128;
        tmp = direct3d::clamp(tmp, 0.0f, 255.0f);
        const unsigned int temp3_ = static_cast<unsigned int>(tmp);

        tmp = static_cast<float>(v.a);
        tmp = (tmp - 128) * contrast + brightness + 128;
        tmp = direct3d::clamp(tmp, 0.0f, 255.0f);
        const unsigned int temp4_ = static_cast<unsigned int>(tmp);        

        outputTxVw.set(idx, graphics::uint_4(temp1_, temp2_, temp3_, temp4_));
    });
    copy(outputTx, (void*)a, len);
}

Вы можете найти гораздо больше примеров C++ AMP в Книге AMP.

person Ade Miller    schedule 04.09.2013
comment
Большое спасибо за этот подробный ответ! У меня есть 24-битные значения RGB (уничтожающие 32-битное выравнивание). В этом конкретном примере (масштабирование) это вообще не имеет значения (байты просто эквивалентны). Знаете ли вы, могу ли я с помощью uint_3 получить сопоставление значений, выровненных по 3 байтам, с .r .g .b в более сложном случае, когда мне действительно нужно различать цвета? - person Andreas Reiff; 06.09.2013
comment
Я думаю, что использование int_3 приведет к невыровненному доступу к памяти, поскольку данные хранятся в текстуре в исходном формате и распаковываются в int при доступе к GPU. Я дважды проверю это с командой продукта. - person Ade Miller; 10.09.2013

Хм... Итак, чтобы ответить на мои собственные вопросы после еще нескольких проб, ошибок и ошибок:

  • & 0xFF работает нормально, как и >> и ‹‹
  • это была моя проблема с негенерированием правильных значений, особенно я не учел (очень ограниченную) точность float, которая значительно ниже точности int (ну, мантисса)
  • до сих пор не знаю
  • еще не знаю, но работает одинаково (результат/скорость) с .synchronize и без него

Если вы наткнулись на что-то подобное или вам это нужно, вот решение (как и предполагалось изначально), изменение яркости и контрастности в массиве значений яркости:

void AMPChangeBrightnessContrastWrapper
    (byte* a, int len, float brightness, float contrast)
{
    array_view<unsigned int> dst(len/4, (unsigned int*)a);
    parallel_for_each(dst.extent, [=](index<1> idx) restrict(amp) 
    {
        float temp1 = dst[idx] & 0xFF;
        temp1 = (temp1 - 128) * contrast + brightness + 128;
        if (temp1 < 0)
            temp1 = 0;
        if (temp1 > 255)
            temp1 = 255;

        float temp2 = (dst[idx] >> 8) & 0xFF;
        temp2 = (temp2 - 128) * contrast + brightness + 128;
        if (temp2 < 0)
            temp2 = 0;
        if (temp2 > 255)
            temp2 = 255;

        float temp3 = (dst[idx] >> 16) & 0xFF;
        temp3 = (temp3 - 128) * contrast + brightness + 128;
        if (temp3 < 0)
            temp3 = 0;
        if (temp3 > 255)
            temp3 = 255;

        float temp4 = (dst[idx] >> 24);
        temp4 = (temp4 - 128) * contrast + brightness + 128;
        if (temp4 < 0)
            temp4 = 0;
        if (temp4 > 255)
            temp4 = 255;

        unsigned int temp1_ = (unsigned int)temp1;
        unsigned int temp2_ = (unsigned int)temp2;
        unsigned int temp3_ = (unsigned int)temp3;
        unsigned int temp4_ = (unsigned int)temp4;
        unsigned int res = temp1_ + (temp2_ << 8) + (temp3_ << 16) + (temp4_ << 24);
        dst[idx] = res;
    });
    dst.synchronize();
}

Кроме того, хотя я (думаю, что я) делаю мало вычислений, это работало в 2-4 раза быстрее (выпуск/отладка), чем на процессоре с Intel HD 4000.

person Andreas Reiff    schedule 05.07.2013
comment
Вы можете заменить тесты для минимальных/максимальных значений встроенными зажимами. Это не только сделает ваш код чище, но и, вероятно, улучшит производительность, поскольку используется встроенная функция, для которой оптимизировано оборудование. msdn.microsoft.com/library/windows/desktop / - person Ade Miller; 04.09.2013