Достаточно ли std::memory_order_relaxed для проверки доступности?

У меня есть параллельный объект, который может или может содержать указатель на функцию каждый раз. Схема объекта выглядит так:

struct ConcurrentObject{
  //variables
  std::atomic<void(*)()> callback;
} 

поэтому один поток может решить, что он хочет присоединить обратный вызов к этому объекту и передать его дальше:

ConcurrentObject* co = new ConcurrentObject(); //I'm using smart pointers, no worries.
//do some logic
co->callback = someCallback; //void(*)() , this may be difference callback every time

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

auto co = aquireConcurrentObject();
auto callback = co->callback.load();
if (callback){
    callback()
}

теперь мы знаем, что без указания какого-либо порядка памяти по умолчанию передается память в порядке memory_order_seq_cst, что говорит компилятору (в двух словах) «не путайте никакие инструкции чтения или записи, чтобы сделать программу быстрее, сохраняйте относительный порядок инструкций, как указано код и сделать его видимым через процессор».

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

Мой вопрос - достаточно ли std::memory_order_relaxed для этого действия?


person David Haim    schedule 23.01.2016    source источник


Ответы (3)


Да, вы правы, в вашем примере std::memory_order_relaxed безопасно использовать, потому что ваш код полагается только на тот факт, что обратный вызов является атомарным. На ваш код не влияет возможное изменение порядка операций с памятью

person Nicola Bizzoca    schedule 23.01.2016

Порядок обращения к указателю обратного вызова влияет на «видимость» переменных, используемых обратным вызовом.

Если ваш обратный вызов:

1) похож на constexpr, то есть не использует ничего, кроме своих аргументов и постоянных глобальных переменных, или

2) использует только те переменные, которые инициализированы до (происходит-прежде) возможного использования обратного вызова,

тогда использование std::memory_order_relaxed подходит как для сохранения, так и для загрузки.

Но если ваш код под //do some logic инициализирует некоторые переменные, используемые обратным вызовом, то вы должны использовать как минимум std::memory_order_release/std::memory_order_acquire для сохранения и загрузки соответственно. В противном случае при выполнении обратного вызова эти переменные могут быть неинициализированы (точнее, это будет гонка данных по смыслу стандарта C++11, то есть Undefined Behavior).

person Tsyvarev    schedule 23.01.2016

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

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

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

person user1708860    schedule 23.01.2016
comment
это больше подходит в качестве комментария, поскольку вы явно не отвечаете на вопрос. в любом случае, это может не быть узким местом, но может снизить производительность в целом, и вы можете измерить программу с использованием и без использования std::memory_order_relaxed, чтобы увидеть, насколько сильно это влияет. мой вопрос в том, могу ли я сделать это, чтобы профилировать правильную программу, а не программу с ошибками. - person David Haim; 23.01.2016