Что означает атрибут [[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 (поскольку местоположение памяти доступа не согласовано с другим потоком, который выполнял присвоение).

Действительно, кажется, что в функциональных возможностях памяти (и ума) есть некоторые большие ошибки в определенных технических случаях.... > : -)