c ++ openmp false-share на примере выровненного массива - PullRequest
0 голосов
/ 13 ноября 2018

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

У меня есть массив, содержащий 100 м целых чисел.Рассмотрим это как матрицу mxn.Один поток изменяет нечетные индексированные строки, а другой поток изменяет четные индексированные строки.

Эксперимент A: Количество столбцов равно 16. Таким образом, каждая строка составляет 64 байта, это точно мой размер строки кэша.Поскольку каждый поток обрабатывает ровно 1 кэш-строку за раз, не должно быть ложного разделения.Поэтому я ожидаю ускорения примерно на 100%.

Эксперимент B: Количество столбцов равно 8. Каждый поток изменяет 32 байта за раз, что составляет половину кэш-строки.Например, если поток 1 обрабатывает строку 33, данные должны быть перенесены из потока 0, поскольку поток 1 уже обработал строку 32, которая находится в той же строке кэша.(или наоборот, порядок не имеет значения).Из-за этого сообщения скорость должна быть низкой.

#include <iostream>
#include <omp.h>

using namespace std;

int main(int argc, char** argv) {

    if(argc != 3) {
        cout << "Usage: " << argv[0] << " <iteration> <col_count>" << endl;
        return 1;
    }

    int thread_count = omp_get_max_threads();
    int iteration = atoi(argv[1]);
    int col_count = atoi(argv[2]);
    int arr_size = 100000000;

    int* A = (int*) aligned_alloc(16 * sizeof(int), arr_size * sizeof(int));

    int row_count = arr_size / col_count; 
    int row_count_per_thread = row_count / thread_count;

    #pragma omp parallel
    {
        int thread_id = omp_get_thread_num();

        long long total = 1ll * iteration * row_count_per_thread * col_count;
        printf("%lld\n", total);

        for(int t = 0; t < iteration; t++) {

            for(int i = 0; i < row_count_per_thread; i++) {

                int start = (i * thread_count + thread_id) * col_count;
                for(int j = start; j < start + col_count; j++) {


                    if(A[j] % 2 == 0)
                        A[j] += 3;
                    else
                        A[j] += 1;
                }
            }
        }
    }

    return 0;
}

Я запускаю этот код с различными конфигурациями следующим образом:

time taskset -c 0-1 ./run 100 16

Вот результатыдля 100 итераций:

Thread      Column      Optimization        Time (secs)
_______________________________________________________
1           16          O3                  7.6
1           8           O3                  7.7
2           16          O3                  7.7
2           8           O3                  7.7

1           16          O0                  35.9
1           8           O0                  34.3
2           16          O0                  19.3
2           8           O0                  18.2

Как вы можете видеть, хотя оптимизация O3 дает наилучшие результаты, они очень странные, поскольку увеличение количества потоков не приводит к ускорению.Для меня результаты оптимизации O0 более интерпретируемы.

Реальный вопрос: посмотрите на последние 2 строки.В обоих случаях я получил ускорение почти на 100%, однако я ожидаю, что время выполнения эксперимента B должно быть намного выше, поскольку у него есть проблема ложного обмена.Что не так с моим экспериментом или моим пониманием?

Я скомпилировал его с g++ -std=c++11 -Wall -fopenmp -O0 -o run -Iinc $(SOURCE) и g++ -std=c++11 -Wall -fopenmp -O3 -o run -Iinc $(SOURCE)

Дайте мне знать, если моя проблема не ясна или вам нужно больше подробностей.


Обновление: Характеристики:

MemTotal:        8080796 kB
Architecture:        x86_64
CPU op-mode(s):      32-bit, 64-bit
Byte Order:          Little Endian
CPU(s):              8
On-line CPU(s) list: 0-7
Thread(s) per core:  2
Core(s) per socket:  4
Socket(s):           1
NUMA node(s):        1
Vendor ID:           GenuineIntel
CPU family:          6
Model:               71
Model name:          Intel(R) Core(TM) i7-5700HQ CPU @ 2.70GHz
Stepping:            1
CPU MHz:             2622.241
CPU max MHz:         3500,0000
CPU min MHz:         800,0000
BogoMIPS:            5387.47
Virtualization:      VT-x
L1d cache:           32K
L1i cache:           32K
L2 cache:            256K
L3 cache:            6144K
NUMA node0 CPU(s):   0-7

Обновление 2: Я пробовал разные параметры iteration_count и arr_size, чтобы массивумещается в кэшах L2, L1, при этом общее число изменений элемента остается постоянным.Но результаты все те же.

Спасибо.

1 Ответ

0 голосов
/ 23 ноября 2018

Ваша синхронизация -O3, по-видимому, соответствует скорости доступа к 1-канальной памяти вашего процессора . Возможно, вы сможете получить в 2 раза лучшую скорость, используя 2-канальную конфигурацию памяти, но вряд ли это приведет к каким-либо другим различиям в ваших результатах. Имейте в виду, что на вашем процессоре имеется один кэш L3, совместно используемый ядрами, поэтому любое ложное совместное использование с большой вероятностью будет разрешено на уровне кэша L3 и не приведет к дополнительной нагрузке на шину внешней памяти.

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

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

Во-вторых, даже если у них действительно есть конфликт, он будет временным, потому что любой фактор, приводящий к асимметричному замедлению, заставит «более медленный» поток отложить сканирование, пока он не выйдет за пределы конфликта.

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

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

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