Что означает атрибут [[несет зависимость]]?


2 65

2 ответа:

[[carries_dependency]] используется для разрешения переноса зависимостей между вызовами функций. Это потенциально позволяет компилятору генерировать эффективный код при использовании std::memory_order_consume для передачи значений между потоками на платформах со слабо упорядоченными архитектурами, такими как архитектура питания IBM.

в частности, если значение, прочитанное с memory_order_consume передается в функцию, а затем без [[carries_dependency]], то компилятор, возможно, придется выдать инструкцию ограждения памяти, чтобы гарантировать, что поддерживается соответствующая семантика упорядочения памяти. Если параметр аннотируется с помощью [[carries_dependency]] тогда компилятор может предположить, что тело функции будет правильно нести зависимость, и этот забор может больше не понадобиться.

аналогично, если функция возвращает значение, загружаемое с memory_order_consume, или производное от такого значения, то без [[carries_dependency]] компилятору может потребоваться вставить инструкцию fence, чтобы гарантировать, что соответствующая семантика упорядочения памяти поддерживается. С элемент [[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 разыменован, и что он должен гарантировать, что цепочка зависимостей сохраняется, чтобы избежать забора на власть.

в строке (2), определением print непрозрачен (при условии, что он не встроен), поэтому компилятор необходимо оформить забор для того, чтобы чтение *p на print возвращает правильное значение.

в строке (3), компилятор может предположить, что хотя print2 также непрозрачен, тогда зависимость от параметра к разыменованному значению сохраняется в потоке команд, и никакой забор не требуется при питании. Очевидно, что определение print2 должен фактически сохранить эту зависимость, поэтому атрибут также повлияет на сгенерированный код для print2.

короче говоря, я думаю, что если есть атрибут carries_dependency, сгенерированный код для функции должен быть оптимизирован для случая, когда фактический аргумент действительно придет из другого потока и несет зависимость. Аналогично для возвращаемого значения. Там может быть недостаток производительности, если это предположение не верно (например, в однопоточной программе). Но также отсутствие [[carries_dependency]] может привести к плохой работе в противоположном случае... Никаких других эффектов, кроме изменение производительности должно произойти.

например, операция разыменования указателя зависит от того, как указатель был ранее получен, и если значение указателя p поступает из другого потока (с помощью операции "потреблять"), значение, ранее присвоенное этому другому потоку *p, учитывается и отображается. Может быть другой указатель q, который равен p (q==p), но поскольку его значение не исходит из этого другого потока, значение *q может отличаться от *p. на самом деле * q может спровоцировать своего рода" неопределенное поведение " (потому что доступ к ячейке памяти из координации с другим потоком, который сделал назначение).

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