Хобрук: Ваш путь к мастерству в программировании

Уменьшить косвенность в коде OCL

У меня есть следующий фрагмент (псевдо) кода в моем ядре:

kernel void krnl(global X* restrict x){ 
    for(int i = 0; i < 100; i++){
        x[a].y[b].z[i] * x[a].y[i].n;
    }
}

Я использую устройство Xilinx FPGA, поэтому некоторые вещи могут немного отличаться. Код работает на GPU/CPU, но не на FPGA. Просто некоторые подробности, если это кому-то поможет.

x, y, z и n следующие:

typedef struct Y
{
    float n;
    float z[MAXSIZE]
} Y;

typedef struct X
{
    int i;
    Y y[MAXSIZE];
} X;

«a» и «b» просто векторы int.

Мне нужно уменьшить количество косвенных обращений, в идеале только изменив ядра (или добавив встроенные функции - я пробовал это, но похоже, что данные не передаются ядрам).

конкретно, мне нужно, чтобы код был примерно таким:

for(int i = 0; i < 100; i++){
    V.z[i] * K[i].n;
}
22.06.2016

  • Я разместил вопрос, связанный с этим, в другом потоке, потому что я чувствовал, что природа вопроса была совершенно другой, посмотрите, если хотите: stackoverflow.com/questions/37981455/ Я верю в решение к этой проблеме будет передаваться все данные из __global в __local, использовать данные __local в циклах, а затем передавать их обратно в __global. Однако я еще не закончил тестирование этого кода и пока не буду делать поспешных выводов. Если получится, выложу решение здесь. 23.06.2016

Ответы:


1

Что ж, если a и b статичны во время цикла, выполните:

kernel void krnl(global X* restrict x){ 
    const float* fast_l = x[a].y[b].z;
    const Y*     fast_r = x[a].y;
    for(int i = 0; i < 100; i++){
        fast_l[i] * fast_r[i].n;
    }
}

Для компилятора довольно типично не кэшировать глобальные операции чтения и записи, поскольку глобальные данные считаются очень изменчивыми (доступными для всех рабочих элементов). Ручное кэширование обычно помогает решить эти проблемы.

Однако умный компилятор должен догадаться, что вся эта косвенность — просто смещение указателя. Я не думаю, что в этом случае у вас будет какая-то выгода. Пример:

kernel void krnl(global X* restrict x){ 
    const float* off_l = ((float *)x)+sizeof(X)*a+sizeof(Y)*b+sizeof(int);
    const float* off_r = ((float *)x)+sizeof(X)*a;
    for(int i = 0; i < 100; i++){
        *(off_l+i)  *  *(off_r+sizeof(Y)*i);
    }
}
22.06.2016
  • Спасибо за ответ. К сожалению, код дает тот же результат, что и моя предыдущая попытка, и не делает код более эффективным. 23.06.2016
  • Извините, я хочу быть немного более конкретным с моим предыдущим комментарием. Я считаю, что причина, по которой производительность не меняется, заключается в том, что ядро ​​​​по-прежнему выполняет то же количество вызовов даже после изменений. В настоящее время я пытаюсь переместить данные в __local перед вызовом, но у меня возникают некоторые проблемы из-за num_elements в async_work_group_copy. Я обновлю это или ОП, если не смогу заставить его работать, и если оно сработает, я вернусь с решением. Спасибо еще раз. 23.06.2016

  • 2

    Вам нужно меньше косвенности, чтобы иметь меньше операций с памятью?

    Это должно быть лучше для GPU:

    typedef struct X
    {
        Y y[MAXSIZE];
        int i;
    } X;
    

    скорее, чем

    typedef struct X
    {
        int i;
        Y y[MAXSIZE];
    } X;
    

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

    Если это работает, это:

    typedef struct Y
    {
        float z[MAXSIZE];
        float n;
    } Y;
    

    должно быть быстрее, особенно когда MAXSIZE является четным числом.

    Разделение Y от i как двух отдельных массивов вместо массива объектов Y+i будет быстрее для GPU. То же самое для z и n в Y. Чистые массивы собственных элементов быстрее, особенно только тогда, когда одно из полей необходимо, а другое не нужно.

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

    Наилучшая производительность требует параллелизма на уровне потоков и непрерывного чтения из памяти, в то время как объектно-ориентированный подход обеспечивает удобочитаемость, устойчивость и возможность обновления, но не переносимость, поскольку у некоторых аппаратных средств есть проблемы с выравниванием. Зачем загружать целое число с плавающей запятой z[MAXSIZE] из памяти, если вам нужно только z[i]? Потому что он находится в объекте. Если бы это был чистый массив, для получения z[i] потребовалась бы только 1 операция индексации. Загрузка поля объектов требует шага MAXSIZE в памяти, даже если он загружает только одно число с плавающей запятой, но версия чистого массива по вашему выбору заставит его шагать с размером 1 и достичь, возможно, оптимальной скорости.

    Пример массива для z:

    z[0]:  1st thread's z[0]
    z[1]:  2nd thread's z[0]
    z[2]:  3rd thread's z[0]
    ....
    z[n]:  1st thread's z[1]
    z[n+1]:  2nd thread's z[1]
    z[n+3]:  3rd thread's z[1]
    ....
    

    поэтому на каждом этапе

    for(int i = 0; i < 100; i++)
    

    gpu обращается к памяти без отверстий для всех z, что было бы намного быстрее, чем объектно-ориентированная версия imho.

    24.06.2016
  • может потребоваться меньше операций чтения памяти на элемент, потому что первая операция чтения в исходной структуре имеет меньшую эффективность, в то время как последняя структура может выполнять ее с полной эффективностью. Это интересно знать. На самом деле я использую Xilinx FPGA, поэтому не думаю, что некоторые вещи, которые вы сказали, применимы к моей работе. Я не думаю, что было бы уместно указать это, поскольку это не совсем связано с кодом opencl или C++, но я укажу это в следующий раз. Спасибо за ваш ответ. 27.06.2016
  • Затем компилятор организует чтение и запись на аппаратном уровне, поэтому ему не нужна оптимизация программного обеспечения? 27.06.2016
  • В моем случае, если ядро ​​недостаточно оптимизировано, кажется, что оно не будет работать на фабрике FPGA. Я считаю, что (возможно, неэффективное или неправильное) использование объектов в ядре может быть одним из факторов, вызывающих это, отсюда и вопрос. 28.06.2016
  • Новые материалы

    Dall-E 2: недавние исследования показывают недостатки в искусстве, созданном искусственным интеллектом
    DALL-E 2 — это всеобщее внимание в индустрии искусственного интеллекта. Люди в списке ожидания пытаются заполучить продукт. Что это означает для развития креативной индустрии? О применении ИИ в..

    «Очень простой» эволюционный подход к обучению с подкреплением
    В прошлом семестре я посетил лекцию по обучению с подкреплением (RL) в моем университете. Честно говоря, я присоединился к нему официально, но я редко ходил на лекции, потому что в целом я нахожу..

    Освоение информационного поиска: создание интеллектуальных поисковых систем (глава 1)
    Глава 1. Поиск по ключевым словам: основы информационного поиска Справочная глава: «Оценка моделей поиска информации: подробное руководство по показателям производительности » Глава 1: «Поиск..

    Фишинг — Упаковано и зашифровано
    Будучи старшим ИТ-специалистом в небольшой фирме, я могу делать много разных вещей. Одна из этих вещей: специалист по кибербезопасности. Мне нравится это делать, потому что в настоящее время я..

    ВЫ РЕГРЕСС ЭТО?
    Чтобы понять, когда использовать регрессионный анализ, мы должны сначала понять, что именно он делает. Вот простой ответ, который появляется, когда вы используете Google: Регрессионный..

    Не зря же это называют интеллектом
    Стек — C#, Oracle Опыт — 4 года Работа — Разведывательный корпус Мне пора служить Может быть, я немного приукрашиваю себя, но там, где я живу, есть обязательная военная служба на 3..

    LeetCode Проблема 41. Первый пропущенный положительный результат
    LeetCode Проблема 41. Первый пропущенный положительный результат Учитывая несортированный массив целых чисел, найдите наименьшее пропущенное положительное целое число. Пример 1: Input:..