Улучшение расположения памяти для параллельных вычислений - PullRequest
2 голосов
/ 13 марта 2012

Я пытаюсь оптимизировать алгоритм (Lattice Boltzmann) для параллельных вычислений с использованием C ++ AMP. И, посмотрев некоторые предложения по оптимизации размещения памяти, только что обнаружил, что удаление одного параметра из структуры в другой вектор (заблокированный вектор) дало и увеличивало примерно на 10%.

Кто-нибудь получил какие-нибудь советы, которые могут улучшить это, или что-то, что я должен принять во внимание? Ниже приведена наиболее трудоемкая функция, которая выполняется для каждого временного шага, и структура, используемая для макета.

struct grid_cell {
//  int blocked;    // Define if blocked
    float n;        // North
    float ne;       // North-East
    float e;        // East
    float se;       // South-East
    float s;
    float sw;
    float w;
    float nw;
    float c;        // Center
};

int collision(const struct st_parameters param, vector<struct grid_cell> &node, vector<struct grid_cell> &tmp_node, vector<int> &obstacle) {
    int x,y;
    int i = 0;
    float c_sq = 1.0f/3.0f;     // Square of speed of sound
    float w0 = 4.0f/9.0f;       // Weighting factors
    float w1 = 1.0f/9.0f;
    float w2 = 1.0f/36.0f;

    int chunk = param.ny/20;
    float total_density = 0;

    float u_x,u_y;              // Avrage velocities in x and y direction
    float u[9];                 // Directional velocities
    float d_equ[9];             // Equalibrium densities
    float u_sq;                 // Squared velocity
    float local_density;        // Sum of densities in a particular node

    for(y=0;y<param.ny;y++) {
        for(x=0;x<param.nx;x++) {
            i = y*param.nx + x; // Node index
            // Dont consider blocked cells
            if (obstacle[i] == 0) {
                // Calculate local density
                local_density = 0.0;
                local_density += tmp_node[i].n;
                local_density += tmp_node[i].e;
                local_density += tmp_node[i].s;
                local_density += tmp_node[i].w;
                local_density += tmp_node[i].ne;
                local_density += tmp_node[i].se;
                local_density += tmp_node[i].sw;
                local_density += tmp_node[i].nw;
                local_density += tmp_node[i].c;

                // Calculate x velocity component
                u_x = (tmp_node[i].e + tmp_node[i].ne + tmp_node[i].se - 
                      (tmp_node[i].w + tmp_node[i].nw + tmp_node[i].sw)) 
                      / local_density;
                // Calculate y velocity component
                u_y = (tmp_node[i].n + tmp_node[i].ne + tmp_node[i].nw - 
                      (tmp_node[i].s + tmp_node[i].sw + tmp_node[i].se)) 
                      / local_density;
                // Velocity squared
                u_sq = u_x*u_x +u_y*u_y;

                // Directional velocity components;
                u[1] =  u_x;        // East
                u[2] =        u_y;  // North
                u[3] = -u_x;        // West
                u[4] =      - u_y;  // South
                u[5] =  u_x + u_y;  // North-East
                u[6] = -u_x + u_y;  // North-West
                u[7] = -u_x - u_y;  // South-West
                u[8] =  u_x - u_y;  // South-East

                // Equalibrium densities
                // Zero velocity density: weight w0
                d_equ[0] = w0 * local_density * (1.0f - u_sq / (2.0f * c_sq));
                // Axis speeds: weight w1
                d_equ[1] = w1 * local_density * (1.0f + u[1] / c_sq
                                 + (u[1] * u[1]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                d_equ[2] = w1 * local_density * (1.0f + u[2] / c_sq
                                 + (u[2] * u[2]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                d_equ[3] = w1 * local_density * (1.0f + u[3] / c_sq
                                 + (u[3] * u[3]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                d_equ[4] = w1 * local_density * (1.0f + u[4] / c_sq
                                 + (u[4] * u[4]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                // Diagonal speeds: weight w2
                d_equ[5] = w2 * local_density * (1.0f + u[5] / c_sq
                                 + (u[5] * u[5]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                d_equ[6] = w2 * local_density * (1.0f + u[6] / c_sq
                                 + (u[6] * u[6]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                d_equ[7] = w2 * local_density * (1.0f + u[7] / c_sq
                                 + (u[7] * u[7]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));
                d_equ[8] = w2 * local_density * (1.0f + u[8] / c_sq
                                 + (u[8] * u[8]) / (2.0f * c_sq * c_sq)
                                 - u_sq / (2.0f * c_sq));

                // Relaxation step
                node[i].c = (tmp_node[i].c + param.omega * (d_equ[0] - tmp_node[i].c));
                node[i].e = (tmp_node[i].e + param.omega * (d_equ[1] - tmp_node[i].e));
                node[i].n = (tmp_node[i].n + param.omega * (d_equ[2] - tmp_node[i].n));
                node[i].w = (tmp_node[i].w + param.omega * (d_equ[3] - tmp_node[i].w));
                node[i].s = (tmp_node[i].s + param.omega * (d_equ[4] - tmp_node[i].s));
                node[i].ne = (tmp_node[i].ne + param.omega * (d_equ[5] - tmp_node[i].ne));
                node[i].nw = (tmp_node[i].nw + param.omega * (d_equ[6] - tmp_node[i].nw));
                node[i].sw = (tmp_node[i].sw + param.omega * (d_equ[7] - tmp_node[i].sw));
                node[i].se = (tmp_node[i].se + param.omega * (d_equ[8] - tmp_node[i].se));
            }
        }
    }
    return 1;
}

Ответы [ 3 ]

7 голосов
/ 13 марта 2012

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

6 голосов
/ 16 марта 2012

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

  1. Доступ с единичного шага очень важен, поэтому графические процессоры предпочитают «структуры массивов» «массивам структур». Поскольку вы делали перемещение поля «заблокировано» в вектор «препятствие», было бы выгодно преобразовать все поля «grid_cell» в отдельные векторы. Это должно показать преимущества для ЦП и для циклов, которые не имеют доступа ко всем полям.

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

  3. Это более умозрительно, но теперь, когда вы записываете весь выходной вектор, возможно, подсистема памяти избегает чтения значений в «узле», которые просто будут перезаписаны

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

  5. Некоторые системы также «векторизуют» нагрузки и сохраняют, поэтому повторное удаление «заблокированных» из структуры может привести к большей векторизации. Переход к структуре массивов смягчает это беспокойство.

Спасибо за ваш интерес к C ++ AMP.

Дэвид Каллахан

http://blogs.msdn.com/b/nativeconcurrency/ C ++ AMP Team Blog

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

Некоторые небольшие общие вершины:

  • Любая структура данных, которая совместно используется несколькими процессорами, должна быть доступна только для чтения.

  • Любая структура данных, котораяТребуется модификация, уникальная для процессора и не разделяющая местность памяти с данными, которые требуются другому процессору.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...