Может ли кто-нибудь объяснить это на языке, который понимают простые смертные?
Что означает атрибут [[carry_dependency]]?
Ответ 1
[[carries_dependency]]
используется, чтобы позволить зависимостям переноситься через вызовы функций. Это потенциально позволяет компилятору генерировать лучший код при использовании с std::memory_order_consume
для передачи значений между потоками на платформах со слабоупорядоченными архитектурами, такими как архитектура IBM POWER.
В частности, если значение, считанное с помощью memory_order_consume
, передается функции, то без [[carries_dependency]]
, компилятору, возможно, придется выдать команду сохранения памяти, чтобы гарантировать, что соответствующая семантика упорядочения памяти будет поддержана. Если параметр аннотируется с помощью [[carries_dependency]]
, тогда компилятор может предположить, что тело функции будет правильно переносить зависимость, и этот забор больше не понадобится.
Аналогично, если функция возвращает значение, загруженное с помощью memory_order_consume
, или полученное из такого значения, то без [[carries_dependency]]
компилятору может потребоваться вставить команду ограждения, чтобы гарантировать, что соответствующая семантика упорядочения памяти будет поддержана. С аннотацией [[carries_dependency]]
этот забор может не понадобиться, поскольку вызывающий пользователь отвечает за ведение дерева зависимостей.
например.
void print(int * val)
{
std::cout<<*p<<std::endl;
}
void print2(int * [[carries_dependency]] val)
{
std::cout<<*p<<std::endl;
}
std::atomic<int*> p;
int* local=p.load(std::memory_order_consume);
if(local)
std::cout<<*local<<std::endl; // 1
if(local)
print(local); // 2
if(local)
print2(local); // 3
В строке (1) зависимость явно, поэтому компилятор знает, что local
разыменовывается и что он должен гарантировать, что цепочка зависимостей сохраняется, чтобы избежать ограждения от POWER.
В строке (2) определение print
непрозрачно (если оно не указано), поэтому компилятор должен выдать забор, чтобы убедиться, что чтение *p
в print
возвращает правильное значение.
В строке (3) компилятор может предположить, что хотя print2
также непрозрачен, тогда зависимость от параметра до разыменованного значения сохраняется в потоке команд, и в POWER не требуется никакого забора. Очевидно, что определение print2
должно фактически сохранить эту зависимость, поэтому атрибут также повлияет на сгенерированный код для print2
.
Ответ 2
Короче говоря, если есть атрибут carry_dependency, сгенерированный код для функции должен быть оптимизирован для случая, когда фактический аргумент действительно будет поступать из другого потока и несет зависимость. Аналогично для возвращаемого значения. Может быть недостаток производительности, если это предположение неверно (например, в однопоточной программе). Но также отсутствие [[carry_dependency]] может привести к плохой производительности в противоположном случае... Никаких других эффектов, кроме изменения производительности, не должно произойти.
Например, операция разыменования указателя зависит от того, как ранее был получен указатель, и если значение указателя p происходит из другого потока (посредством операции "потреблять" ), значение, ранее назначенное этим другим потоком на * p, приняты во внимание и видны. Может быть другой указатель q, который равен p (q == p), но поскольку его значение не исходит из этого другого потока, значение * q может рассматриваться как отличающееся от * p. На самом деле * q может спровоцировать своеобразное поведение undefined (поскольку местоположение памяти доступа не согласовано с другим потоком, который выполнял присвоение).
Действительно, кажется, что в функциональных возможностях памяти (и ума) есть некоторые большие ошибки в определенных технических случаях.... > : -)