Как и когда выравнивать размер строки кеша?

В Дмитрий Вьюков отличная ограниченная очередь mpmc, написанная на С++ См.: http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue

Он добавляет некоторые переменные заполнения. Я предполагаю, что это должно привести его к строке кэша для производительности.

У меня есть некоторые вопросы.

  • Почему это делается таким образом?
  • Это переносимый метод, который всегда работать
  • В каких случаях лучше было бы использовать __attribute__ ((aligned (64))).
  • зачем было заполняться до указателя буфера с помощью производительности? это не только указатель, загруженный в кеш, так что на самом деле это только размер указателя?

    static size_t const     cacheline_size = 64;
    typedef char            cacheline_pad_t [cacheline_size];
    
    cacheline_pad_t         pad0_;
    cell_t* const           buffer_;
    size_t const            buffer_mask_;
    cacheline_pad_t         pad1_;
    std::atomic<size_t>     enqueue_pos_;
    cacheline_pad_t         pad2_;
    std::atomic<size_t>     dequeue_pos_;
    cacheline_pad_t         pad3_;
    

Будет ли эта концепция работать под gcc для c-кода?

Ответ 1

Это делается так, что различным ядрам, изменяющим разные поля, не придется отказываться от строки кэша, содержащей обе их между кешами. В общем случае, чтобы процессор мог получить доступ к некоторым данным в памяти, вся строка кэша, содержащая его, должна находиться в локальном кеше процессора. Если он изменяет эти данные, этот элемент кэша обычно должен быть единственной копией в любом кеше в системе (Исключительный режим в протоколах когерентности кеша MESI/MOESI). Когда отдельные ядра пытаются изменить разные данные, которые происходят в одной и той же строке кэша, и, таким образом, тратить время на перемещение всей строки взад и вперед, что называется ложным совместным использованием.

В конкретном примере, который вы даете, одно ядро ​​может включать в себя запись (чтение (общий) buffer_ и запись (только исключение) только enqueue_pos_), а другой dequeues (общий buffer_ и эксклюзивный dequeue_pos_) без либо остановка ядра в строке кэша, принадлежащей другой.

Заполнение в начале означает, что buffer_ и buffer_mask_ заканчиваются в одной и той же строке кэша, а не разбиваются на две строки и, следовательно, требуют двойного доступа к трафику памяти.

Я не уверен, что эта техника полностью переносима. Предполагается, что каждый cacheline_pad_t сам будет выровнен по границе строки кеша на 64 байта (его размер), и, следовательно, все последующее будет в следующей строке кэша. Насколько я знаю, стандарты языка C и С++ требуют только целого ряда структур, чтобы они могли прекрасно жить в массивах, не нарушая требований к выравниванию любого из своих членов. (см. Комментарии)

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

Такая же концепция применяется как в C, так и в С++.