2013-08-14 3 views
10

Я написал программу следующей короткой C++, чтобы воспроизвести ложный эффект обмена, как описано Herb Sutter:строки кэша, ложное разделение и выравнивание

Скажем, мы хотим, чтобы выполнить общее количество целочисленных операций WORKLOAD, и мы хотим, чтобы они быть равномерно распределенным по числу (PARALLEL) потоков. Для целей этого теста каждый поток будет увеличивать свою собственную выделенную переменную из массива целых чисел, поэтому процесс может быть идеально параллелизуемым.

void thread_func(int* ptr) 
{ 
    for (unsigned i = 0; i < WORKLOAD/PARALLEL; ++i) 
    { 
     (*ptr)++; 
    } 
} 

int main() 
{ 
    int arr[PARALLEL * PADDING]; 
    thread threads[PARALLEL]; 

    for (unsigned i = 0; i < PARALLEL; ++i) 
    { 
     threads[i] = thread(thread_func, &(arr[i * PADDING])); 
    } 
    for (auto& th : threads) 
    { 
     th.join(); 
    } 
    return 0; 
} 

Я думаю, что эту идею легко понять. Если вы установите

#define PADDING 16 

каждый поток будет работать на отдельной строке кэша (предполагается, что длина строки кэша, чтобы быть 64 байта). Таким образом, результатом будет линейное увеличение ускорения до PARALLEL> # core. Если, с другой стороны, PADDING устанавливается на любое значение ниже 16, следует столкнуться с серьезным соперничеством, поскольку, по крайней мере, два потока теперь могут работать в одной и той же строке кэша, которая, однако, защищена встроенным аппаратным мьютексом. Мы ожидаем, что наше ускорение не только будет сублинейным в этом случае, но даже всегда будет < 1 из-за невидимого конвоя.

Теперь мои первые попытки почти оправдали эти ожидания, но минимальная ценность PADDING, необходимая для избежания ложного обмена, составляла около 8, а не 16. Я был довольно озадачен примерно полчаса, пока не пришел к очевидному выводу, что нет никакой гарантии, что мой массив будет выровнен точно до начала строки кэша внутри основной памяти. Фактическое выравнивание может варьироваться в зависимости от многих условий, включая размер массива.

В этом примере, конечно, нет необходимости в том, чтобы массив был выровнен по-особому, потому что мы можем просто оставить PADDING на 16, и все будет хорошо. Но можно представить случаи, когда это действительно имеет значение, независимо от того, выстроена ли определенная структура в строке кэша или нет. Следовательно, я добавил несколько строк кода, чтобы получить некоторую информацию о фактическом выравнивании моего массива.

int main() 
{ 
    int arr[PARALLEL * 16]; 
    thread threads[PARALLEL]; 
    int offset = 0; 

    while (reinterpret_cast<int>(&arr[offset]) % 64) ++offset; 
    for (unsigned i = 0; i < PARALLEL; ++i) 
    { 
     threads[i] = thread(thread_func, &(arr[i * 16 + offset])); 
    } 
    for (auto& th : threads) 
    { 
     th.join(); 
    } 
    return 0; 
} 

Несмотря на это решение работало хорошо для меня в данном случае, я не уверен, будет ли это хороший подход в целом. Итак, вот мой вопрос:

Есть ли какой-либо общий способ иметь объекты в памяти, выровненные по линиям кэша, отличные от того, что я сделал в приведенном выше примере?

(с помощью г ++ MinGW Win32 x86 v.4.8.1 карлик rev3 POSIX)

+0

VirtualAlloc? Он кашляет вверх страницы, поэтому должен быть выровнен. –

+0

Я очень удивлен, что вы видите какую-либо разницу.Компилятор должен хранить '* ptr' в регистре =, тем самым скрывая штраф за ложное разделение. – Mysticial

+0

Для обучения я превратил оптимизацию компилятора, поэтому 'ptr' должен быть разыменован каждый раз. –

ответ

10

Вы должны иметь возможность запрашивать необходимое согласование с компилятора:

alignas(64) int arr[PARALELL * PADDING]; // align the array to a 64 byte line 
+1

Я раньше этого не пробовал, потому что думал, что поведение компилятора в спецификаторе alignas может зависеть от ABI. Ну, оказалось, что он работает на каждой машине, которую я тестировал до сих пор. Спасибо. –

4

GCC поддерживает выровненное ключевое слово: http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html

Вы, вероятно, хотите что-то вроде этого:

int arr[PARALLEL * 16] __attribute__ ((aligned (8)));

Это выравнивает arr на восьмибайтную границу.

Visual Studio имеет аналогичную функцию, тоже: http://msdn.microsoft.com/en-us/library/83ythb65.aspx

Смежные вопросы