Ложный обмен и выравнивание кэша
У меня есть следующий код в C++ (объяснено позже):
#include <stdio.h>
#include <string>
#include <vector>
using namespace std;
struct th_private{
double mean_tau;
th_private()
{
mean_tau = 0;
}
};
class resistor
{
public:
string name;
/*****************************************************************************
Approach 0: Within each resistor strcuture, declare arrays of 'thread private'
variables. Thread 0 will use mean_tau[0], offset[0].., Thread 1 will use
mean_tau[1], offset[1]... and so on. As I understand, this is not a good
approach, would lead to a lot of false sharing.
/*****************************************************************************/
vector<double> mean_tau;
/*****************************************************************************
Approach 1: 1D array of struct th_private in each instance of the resistor,
where state[0] is used ONLY by thread[0], state[0] is used ONLY by thread[1]
and so on. Could potentially elimiate false sharing, but how to ensure
it will align in the cache?
/*****************************************************************************/
vector<th_private> state;
resistor( )
{
name = "";
}
void prepare_for_threads( int num_threads )
{
/* If Approach 0 */
mean_tau.resize(num_threads);
/* Else If Approach 1 */
state.resize(num_threads);
}
~resistor(){}
};
class mesh
{
public:
vector<resistor*> R;
mesh( int num_resistors, int num_threads )
{
for( int i = 0; i < num_resistors; i++ )
{
resistor *r = new resistor();
r->prepare_for_threads( num_threads );
R.push_back(r);
}
}
~mesh(){}
};
/*****************************************************************************
Approach 2: Declare a global 2D matrix, where each row belongs to a
thread and each column belongs to a resistor. Seems to be the best approach.
R[0] R[1] R[2] R[3] R[4] R[9]
thread0: [0][0] [0][1] [0][2] [0][3] [0][4] .. [0][9]
...
thread3: [3][0] [3][1] [3][2] [3][3] [3][4] .. [3][9]
/*****************************************************************************/
th_private __attribute__((aligned(0x1000))) global_state[4][10];
int main( int argc, char** argv )
{
// Assume that there are 4 threads declared.
mesh grid(10, 4);
printf("sizeof(th_private): %d\n", sizeof(th_private));
printf("Approach 1: %p %p %p %p\n", &grid.R[0]->state[0], &grid.R[0]->state[1], &grid.R[0]->state[2], &grid.R[0]->state[3]);
printf("Approach 2: %p %p %p %p\n", &global_state[0][0], &global_state[0][1], &global_state[0][2], &global_state[0][3]);
}
И вывод на 64-битной машине Linux:
sizeof(th_private): 8
Approach 1: 0x658080 0x658088 0x658090 0x658098
Approach 2: 0x608000 0x608008 0x608010 0x608018
Каждый резистор имеет набор атрибутов, которые должны быть изменены (чтение и запись) потоками. В идеале они могут рассматриваться как частные переменные потока. Но из-за некоторых ограничений, налагаемых старой базой кода, я могу использовать только один из трех подходов:
- Подход 0: Внутри каждой структуры резисторов объявите массивы переменных "private private". Поток 0 будет использовать mean_tau[0], offset[0].., Поток 1 будет использовать mean_tau[1], offset[1]... и так далее. Как я понимаю, это не очень хороший подход, приведет к большому количеству ложного обмена.
- Подход 1: 1D массив struct th_private в каждом экземпляре резистора, где состояние [0] используется ТОЛЬКО в потоке [0], состояние [0] используется ТОЛЬКО в потоке [1] и так далее. Может ли потенциально устранить ложное совместное использование, но как обеспечить его выравнивание в кеше?
- Подход 2: Объявите глобальную 2D матрицу, где каждая строка принадлежит потоку, а каждый столбец принадлежит резистору (подробнее в коде).
Теперь, i) чтобы избежать ложного совместного использования и ii) для выравнивания кэша, какой подход является лучшим?