предложение атомарной памяти - PullRequest
6 голосов
/ 15 июля 2011


При тестировании программы на масштабируемость я столкнулся с ситуацией, когда я должен сделать свою операцию memcpy как атомарную операцию. Я должен скопировать 64 байта данных из одного места в другое.
Я сталкивался с одним решением, которое использует вращение по переменной:

struct record{
    volatile int startFlag;
    char data[64];
    volatile int doneFlag;
};

и псевдокод следует

struct record *node;
if ( node->startFlag ==0 ) {  // testing the flag 
    if( CompareAndSwap(node->startFlag , 0 ,1 ) ) {  // all thread tries to set, only one will get success and perform memcpy operation 
        memcpy(destination,source,NoOfBytes);
        node->doneFlag = 1; // spinning variable for other thread, those failed in CompAndSwap 
    }
    else {
         while ( node->doneFlag==0 ) { // other thread spinning 
          ; // spin around and/or use back-off policy  
         }
   }}

Может ли это работать как атомарный memcpy? Хотя, если поток, выполняющий memcpy, прервется (до или после memcpy, но до установки doneFlag), другие будут продолжать вращаться. Или что можно сделать, чтобы сделать это атомным.
Ситуация похожа на то, что другой поток должен ждать, пока данные не будут скопированы, поскольку они должны сравнивать вставленные данные со своими собственными данными.
Я использую подход test-and-test-and-set в случае startFlag, чтобы уменьшить некоторые дорогостоящие атомарные операции. Спин-блокировки также масштабируемы, но я измерил, что атомарные вызовы дают лучшую производительность, чем спин-блокировки, более того, я ищу проблемы, которые могут возникнуть в этом фрагменте. И поскольку я использую свой собственный менеджер памяти, поэтому выделение памяти и бесплатные вызовы обходятся мне дорого, поэтому использование другого буфера и копирование в него содержимого делает установку указателя (так как размер указателя находится в атомарной операции) дорогостоящим, так как это требуется много вызовов mem-alloc и mem-free.

РЕДАКТИРОВАТЬ Я не использую мьютекс, потому что он не выглядит масштабируемым Более того, это всего лишь часть программы, поэтому критическая секция не такая уж мала что для большей критической секции трудно использовать атомарные операции).

Ответы [ 4 ]

5 голосов
/ 17 июля 2011

Ваш фрагмент кода определенно не работает. На узле-> startFlag

есть гонка

К сожалению, нет атомарного способа скопировать 64 байта. Я думаю, у вас есть несколько вариантов здесь.

  1. Доступ к узлу-> startFlag атомарным способом. Я написал пару постов на эту тему: здесь и здесь .
  2. Защита всего объекта с помощью спин-блокировки в пользовательском режиме. Вот пост на тему
  3. Использовать RCU-подобный подход. Вы можете прочитать о RCU здесь . В двух словах идея состоит в том, чтобы ссылаться на буфер, который вы хотите скопировать, используя указатель. Тогда вы делаете:
    1. Выделить новый буфер.
    2. Создайте его содержимое (memcpy из вашего источника).
    3. Атомно заменить буфер новым.
    4. Дождитесь истечения всех потоков, обращающихся к старому буферу, и освободите его.

Надеюсь, это поможет. Алекс.

2 голосов
/ 02 февраля 2015

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

Примечание: я изменил CAS на соответствующий атомарный встроенный в GCC. Нет необходимости в "volatile", CAS вводит барьер памяти.

// Simpler structure
struct record {
    int spin = 0;
    char data[64];
};



struct record *node;

while (node->spin || ! __sync_bool_compare_and_swap(&node->spin , 0 , 1)); // spin
memcpy(destination,source,NoOfBytes);
node->spin = 0; 

PS: я не уверен, что CAS вместо node-> spin = 0 может немного повысить эффективность.

2 голосов
/ 15 июля 2011

Используйте механизм синхронизации.Мьютекс кажется разумным.

Если вы беспокоитесь о масштабируемости, попробуйте использовать монитор.

1 голос
/ 22 июля 2011

Не используйте блокировку, используйте CriticalSection .Замки тяжелые, CriticalSections чрезвычайно, чрезвычайно быстрые (всего пара инструкций в зависимости от платформы).Вы не указали операционную систему, и информация, которую я публикую здесь, используется в Windows, хотя другие ОС должны быть похожими.

У вас было некоторое опасение, что CriticalSections может не быть масштабируемым достаточно для вашей цели, если они содержат много кода?Основная причина (и, вероятно, аргумент того, где вы это читали) заключается в том, что CriticalSection не может чередовать в нескольких потоках столь же мелкозернисто, если потоки удерживают CS в течение длительного времени.Вы можете избежать этого, просто оборачивая CS только той частью вашего кода, которая действительно должна быть атомарной.С другой стороны: если вы используете CS слишком мелкозернистый , то процентные накладные расходы , конечно, возрастут.Это компромисс , которого вы не можете избежать при любой синхронизации.

Вы говорите, что атомарная операция, которая вам нужна, является копией 64 байтов: в этом случае ваши накладные расходы на синхронизацию с CSбудет незначительным .Просто попробуйте.С гранулярностью, при которой вы синхронизируете (около одной копии в 64 байта или около 4, например, из этих копий), вы можете сбалансировать гранулярность с чередованием потоков и процентные издержки , выполнив некоторые эксперименты.Но в целом: CS достаточно быстрые и масштабируемые.

...