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

Иногда я вижу такой оптимизированный код:

if (matrix[t] != 0) {
    matrix[t] = 0;
}

В отличие от только этого кода:

matrix[t] = 0;

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

Что это означает для MESI-состояния: есть ли переход состояния, например, от передано измененному, если я запишу одно и то же значение обратно в строку кэша (запись, но без изменения)? Или это будет слишком сложно для обнаружения процессором?

Типичные процессоры (или, по крайней мере, некоторые) что-то оптимизируют в этом случае?


person dinfuehr    schedule 14.10.2017    source источник
comment
Чтение может быть медленнее, чем запись (процессору приходится ждать кругового пути), поэтому я не вижу в этом оптимизации. (затем увеличивая пропускную способность, выполняя запись, если чтение что-то произвело). Запись не обязательно увеличит пропускную способность оперативной/медленной памяти, замедляя всех до тех пор, пока они не будут вытеснены из кеша. Если записи проходили бы через/вокруг кеша и не приводили к кэшированию пространства, тогда, конечно, это оптимизация. (переписать).   -  person old_timer    schedule 17.10.2017


Ответы (1)


Насколько я знаю, ни одна микроархитектура x86 не пытается зафиксировать сохранение из буфера хранилища в L1D путем чтения, пока оно все еще находится в Shared MESI. состояние и проверка соответствия значения.

Обычно это происходит редко и стоит только дополнительных циклов доступа к кешу для горячих общих переменных, поэтому для микроархитектуры нет смысла делать это по умолчанию. Большинство хранилищ не относятся к общим переменным, и в буфере хранилища не известно, какие хранилища относятся к общим переменным, а какие нет.


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

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

В некоторых случаях стоимость неправильного прогноза нагрузки + ветвления может быть выше, чем экономия, особенно если он не дает хорошего прогноза. (Спекулятивный RFO может даже аннулировать другие копии до того, как будет обнаружен неправильный прогноз. Конечно, спекулятивное хранилище не может фактически зафиксировать L1D, но чтение для владения может произойти AFAIK.)

В качестве другого примера, в цикле повтора спин-блокировки вы всегда хотите вращаться с чистой нагрузкой (+ pause), а не с xchg. Вращение на xchg или lock cmpxchg будет продолжать забивать эту строку кеша и задерживать код, который фактически разблокирует ее.


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

// Example 12-1
state = true; // updates every time
var |= flag;

vs.

if (state != true) state = true;
if (!(var & flag)) var |= flag;

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

person Peter Cordes    schedule 17.10.2017
comment
Я задавался вопросом, может ли компилятор законно просто преобразовать этот цикл проверки, затем установить в memset из 0 (т.е. исключить проверку, поскольку это, вероятно, выгодно, за исключением необычных случаев совместного использования, как вы упомянули). Однако не похоже. Вы, конечно, не можете сделать это на произвольном int * неизвестного происхождения, поскольку его можно было бы определить const, и поэтому запись (но не чтение) будет UB. Я думаю, что правила гонки данных также делают введение записи там, где ее не было в исходной программе, очень сложно. - person BeeOnRope; 17.10.2017