Что означает атрибут [[несет зависимость]]?
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), определением
*p
нав строке (3), компилятор может предположить, что хотя
print2
также непрозрачен, тогда зависимость от параметра к разыменованному значению сохраняется в потоке команд, и никакой забор не требуется при питании. Очевидно, что определениеprint2
должен фактически сохранить эту зависимость, поэтому атрибут также повлияет на сгенерированный код дляprint2
.
короче говоря, я думаю, что если есть атрибут carries_dependency, сгенерированный код для функции должен быть оптимизирован для случая, когда фактический аргумент действительно придет из другого потока и несет зависимость. Аналогично для возвращаемого значения. Там может быть недостаток производительности, если это предположение не верно (например, в однопоточной программе). Но также отсутствие [[carries_dependency]] может привести к плохой работе в противоположном случае... Никаких других эффектов, кроме изменение производительности должно произойти.
например, операция разыменования указателя зависит от того, как указатель был ранее получен, и если значение указателя p поступает из другого потока (с помощью операции "потреблять"), значение, ранее присвоенное этому другому потоку *p, учитывается и отображается. Может быть другой указатель q, который равен p (q==p), но поскольку его значение не исходит из этого другого потока, значение *q может отличаться от *p. на самом деле * q может спровоцировать своего рода" неопределенное поведение " (потому что доступ к ячейке памяти из координации с другим потоком, который сделал назначение).
действительно, кажется, что есть некоторые большие ошибки в функциональности памяти (и ума) в некоторых инженерных случаях.... >: -)