Это все потому, что вы использовали volatile
, а GCC не оптимизирует его так агрессивно
Без энергозависимости, например, для одного ++*int_ptr
вы получаете добавление к месту назначения памяти. (И, надеюсь, не inc
при настройке для процессоров Intel; inc reg
- это хорошо, но inc mem
стоит лишних мопов против добавления 1. К сожалению, gcc и clang ошибаются и используют inc mem
с помощью -march=skylake
: https://godbolt.org/z/_1Ri20)
clang знает, что он может сложить volatile
доступ для чтения / записи в загрузочную и сохраненную части места назначения памяти add
.
GCC не знает, как выполнить эту оптимизацию для volatile
. Использование volatile
в GCC обычно приводит к отдельной загрузке и сохранению mov
, избегая возможности x86 сохранять размер кода с помощью CISCоперанды памяти для инструкций ALU На машине загрузки / хранения (как и любой RISC) вам все равно понадобятся отдельные инструкции загрузки и хранения, чтобы это не было проблемой.
TL: DR: различные внутренние компоненты компилятора вокруг volatile
, в частности пропущенная оптимизация GCC.
Эта пропущенная оптимизация едва ли имеет значение, потому что volatile
используется редко. Но вы можете сообщить об этом в bugzilla GCC, если хотите.
Без volatile
, туалетр конечно бы оптимизировал прочь. Но вы можете увидеть один пункт назначения памяти add
из GCC или clang для функции, которая просто выполняет ++*p
.
1) GCC что-то делает неправильно? Какой смысл копировать значение?
Это только копирование в регистр . Обычно мы не называем это «копированием», а просто помещаем его в регистр, где он может работать с ним.
Обратите внимание, что gcc и clang также отличаются в том, как они реализуют условие цикла, с помощью clang. оптимизация только до dec / jnz (на самом деле add -1
, но он будет использовать dec
с -march = skylake или что-то с эффективным dec
, то есть не Silvermont).
GCC тратит дополнительный уоп наусловие цикла (на процессорах Intel, где add/jnz
может слиться в один макрос). IDK, почему он так наивно компилирует это.
73% времени тратится на инструкцию add edx, 1
Счетчики перфорации обычно обвиняют команду, которая ждет для медленного результата, а не инструкция, которая на самом деле медленно его выводит.
add edx,1
ожидает перезагрузки value
. С задержкой пересылки в 4–5 циклов это главное узкое место в вашем цикле.
(будь то между несколькими мопами места назначения памяти add
или между отдельными инструкциямипо сути, не имеет значения. В вашем цикле нет других обращений к памяти, поэтому ни один из странных эффектов снижения задержки при пересылке магазина, если вы не пытаетесь слишком рано войти в игру: Добавление избыточного назначения ускоряет код, когдаскомпилировано без оптимизации или Цикл с вызовом функции быстрее, чем пустой цикл )
Почему другие инструкции добавления и перемещения занимают менее 1% времени?
Поскольку выполнение не по порядку скрывает их под задержкой критического пути. Они являются очень редко инструкцией, которую обвиняют, когда статистическая выборка должна выбрать одну из множества, которые находятся в полете сразу в любом данном цикле.
3) Почему можнопроизводительность отличается от gcc / clang в таком примитивном коде?
Я ожидаю, что оба этих цикла будут работать с одинаковой скоростью. Вы имели в виду производительность и то, насколько хорошо сами компиляторы работали при создании быстрого и компактного кода?