Атомарное сравнение, многопроцессорность, C/C++ (Linux)

У меня есть переменная в общей памяти x в многопроцессорной системе.

void MyFunction(volatile int* x) {
  if (*x != 0) {
     // do something
  }
}

Другие процессы (возможно, на других процессорах) будут записывать в x, используя встроенные атомарные операции gcc, такие как __sync_bool_compare_and_swap и т. д.

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

Я хочу своего рода atomic_compare (без свопа), если такая вещь существует? Или «атомарное чтение». Какой самый быстрый способ сделать это? (избегая мьютексов, блокировок и т. д.)

Спасибо

Редактировать:

Я только что понял, что несколько хакерским обходным путем будет использование __sync_val_compare_and_swap со значением, которое, как я знал, никогда не может быть. Решит ли это проблему? (Есть ли более чистый способ?)


person Switch    schedule 30.06.2012    source источник
comment
Просто к вашему сведению, volatile на самом деле не будет делать все, что вы хотите, для многопоточных программ. См.: stackoverflow.com/questions/2484980/   -  person argentage    schedule 30.06.2012


Ответы (3)


Новый стандарт C, C11, имеет _Atomic типов данных и операций для решения этой проблемы. Этот стандарт еще не реализован, но gcc и clang близки к нему, они уже реализуют функционал. И на самом деле функция __sync_bool_compare_and_swap является его частью. Я включил это в набор заголовков в P99, который позволяет программировать уже с интерфейсами C11.

Функция C11 для выполнения того, что вы хотите, будет atomic_load или, если у вас есть особые требования к согласованности, atomic_load_explicit. И неудивительно, как вы подозревали, P99 отображает это на __sync_val_compare_and_swap(&x, 0, 0). Затем, если вы посмотрите на ассемблер, который он генерирует на большинстве архитектур, он просто преобразуется в простую операцию загрузки в случае, когда x является int. Но это не гарантируется языком, компилятор должен знать такие вещи и синтезировать инструкции, которые гарантированно будут атомарными.

person Jens Gustedt    schedule 30.06.2012

Какой самый быстрый способ сделать это? (избегая мьютексов, блокировок и т. д.)

Я почти уверен, что вы не хотите избегать мьютексов. Фьютексы Linux позволяют вам использовать преимущества сравнения и замены (в большинстве случаев), сохраняя при этом классическую семантику мьютекса (происходит «своп», который является одним из мьютексов, а не кодом/данными, защищенными им). Я настоятельно рекомендую вам попробовать их и профилировать решение (perf, oprofile, VTune и т. д.), чтобы увидеть, действительно ли ваше узкое место связано с самим механизмом блокировки, а не с такими вещами, как использование кеша, пропускная способность памяти, циклы ЦП, доступ к IO, удаленный -node доступ к памяти и т. д.

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

Что ж, давайте предположим, что вам действительно нужно взаимодействовать между процессорами, и вы измерили задержку, которую вы получаете от фьютексов, и определили, что она не будет соответствовать потребностям вашего приложения. Итак, если это так, относительно разумный способ действий может быть таким: создать массив 32-битных целых чисел, дополненных расстоянием, большим или равным размеру строки кэша вашей цели. Используйте текущий размер процессора и строки кэша в качестве индекса реальных значений в этом списке (поэтому, если ваша строка кэша составляет 64 байта, вы должны масштабировать номер процессора на 16, чтобы перепрыгнуть через заполнение). Вы должны записывать эти значения только с соответствующего ЦП, и вы можете опрашивать его с любого другого ЦП (вероятно, следует вызвать одну из инструкций «паузы» вашего ЦП в теле занятого ожидания). Это был бы эффективный механизм для проверки того, достигли ли/удовлетворили ли различные потоки выполнения заданное условие.

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

person Brian Cain    schedule 30.06.2012
comment
никакие мьютексы не являются излишними, даже в Linux. у современного C есть ответ на этот вопрос, который гораздо ближе к оценке Switch, чем вы думаете. - person Jens Gustedt; 30.06.2012

Я хочу своего рода atomic_compare (без свопа), если такая вещь существует? Или «атомарное чтение».

Сравнение уже атомарно. Это одно чтение.

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

person Adam    schedule 30.06.2012
comment
нет, нет никакой гарантии, что одна операция загрузки на уровне C преобразуется только в одно чтение на низком уровне. На большинстве архитектур это будет int, но это не гарантируется. - person Jens Gustedt; 30.06.2012