Оптимальный способ передачи нескольких переменных между двумя потоками, закрепляющими разные процессоры - PullRequest
0 голосов
/ 23 декабря 2018

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

writer_thread.h

struct a_few_vars {
    uint32_t x1;
    uint32_t x2;

    uint64_t x3;
    uint64_t x4;
} __attribute__((aligned(64)));

volatile uint32_t head;
struct a_few_vars xxx[UINT16_MAX] __attribute__((aligned(64)));

reader_thread.h

uint32_t tail;
struct a_few_vars *p_xxx;

Поток записи увеличивает переменную head, а поток чтения проверяет, является ли переменная head и tailравны.Если они не равны, то новые данные считываются следующим образом:

while (true) {
    if (tail != head) {
        .. process xxx[head] ..
        .. update tail ..
    }
}

Производительность, безусловно, самая важная проблема.Я использую процессоры Intel Xeon, и поток чтения каждый раз выбирает значение head и данные xxx [head] из памяти.Я использовал выровненный массив, чтобы он был свободен от блокировки

В моем случае, есть ли способ как можно быстрее сбросить переменные в кэш процессора считывателя.Могу ли я запустить предварительную выборку для процессора считывателя из процессора записывающего устройства.Я могу использовать специальные инструкции Intel, используя __asm__, если они существуют.В заключение, какой самый быстрый способ передать переменные в структуре между потоками, закрепляющимися на разных процессорах?

Заранее спасибо

1 Ответ

0 голосов
/ 23 декабря 2018

Это неопределенное поведение для одного потока, чтобы записать переменную volatile, в то время как другой поток читает ее, в соответствии с C11.volatile доступы также не упорядочены по отношению к другим доступам.Вы хотите, чтобы atomic_store_explicit(&head, new_value, memory_order_release) в записывающем устройстве и atomic_load_explicit(&head, memory_order_acquire) в считывающем устройстве создавали синхронизацию acq / rel и заставляли компилятор сделать хранилища в вашей структуре видимыми до сохранения до head, что указывает читателю на наличие новых данных..

(tail является приватным для потока читателя, поэтому у писателя нет механизма, чтобы ждать, пока читатель увидит новые данные, прежде чем записывать больше. Так что технически возможна гонка за содержимым структуры, еслипоток записи пишет снова, в то время как читатель все еще читает. Таким образом, структура также должна быть _Atomic).


Возможно, вы захотите установить seq-lock, когда средство записи обновляет порядковый номер ичитатель проверяет его до и после копирования переменных. https://en.wikipedia.org/wiki/Seqlock Это позволяет обнаруживать и повторять попытки в тех редких случаях, когда писатель находился в середине обновления, когда читательскопировал данные.

Это очень хорошо для ситуаций только для записи / только для чтения, особенно если вам не нужно беспокоиться очитатель пропустил обновление.

Посмотрите мою попытку SeqLock в C ++ 11: Реализация 64-битного атомного счетчика с 32-битной атомикой , а также , как реализовать блокировку секвлокас использованием атомарной библиотеки c ++ 11

и переупорядочение GCC при нагрузке с помощью `memory_order_seq_cst`.Разрешено ли это? показывает другой пример (этот вызывает ошибку gcc).

Портирование их из C ++ 11 std :: atomic в C11 stdatomic должно быть простым.Убедитесь, что вы используете atomic_store_explicit, потому что порядок памяти по умолчанию для обычного atomic_store равен memory_order_seq_cst, что медленнее.


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

Об Xeon см. Когда процессор сбрасывает значение в буфере хранения в кэш-память L1? для получения некоторой информации о различных режимах Snoop и их влиянии на задержку между ядрами в одном сокете.

Кэши на нескольких ядрахявляются когерентными, используя MESI для поддержания когерентности.

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

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

...