Как на самом деле работает mtune?
Есть такой связанный вопрос: GCC: чем марш отличается от mtune?
Однако существующие ответы не идут намного дальше, чем само руководство GCC. Максимум, мы получаем:
Если вы используете
-mtune
затем компилятор сгенерирует код, который работает на любом из них, но будет отдавать предпочтение последовательностям команд, которые выполняются быстрее всего на указанном вами ЦП.
а также
-mtune=Y
опция настраивает сгенерированный код так, чтобы он работал быстрее на Y, чем на других процессорах, на которых он может работать.
Но как именно GCC предпочитает одну конкретную архитектуру, когда строит, и в то же время способен выполнять сборку на других (обычно более старых) архитектурах, хотя и медленнее?
Я знаю только одну вещь (но я не специалист по компьютерам), которая была бы способна на это, и это диспетчер процессора. Тем не менее, мне кажется, что mtune
генерирует диспетчера за кулисами, и вместо этого, вероятно, действует какой-то другой механизм.
Я чувствую это по двум причинам:
- Поиск "gcc mtune cpu dispatcher" не находит ничего релевантного; а также
- Если бы это было основано на диспетчере, я думаю, что это может быть умнее (даже если по какой-либо опции, кроме
mtune
) и тест дляcpuid
обнаруживать поддерживаемые инструкции во время выполнения, вместо того, чтобы полагаться на именованную архитектуру, которая предоставляется во время сборки.
Так как это работает на самом деле?
1 ответ
-mtune
не создает диспетчер, он ему не нужен: мы уже сообщаем компилятору, на какую архитектуру мы нацелены.
Из документов GCC:
-mtune = CPU-типа
Настройтесь на тип процессора все, что применимо к сгенерированному коду, кроме ABI и
набор доступных инструкций.
Это означает, что GCC не будет использовать инструкции, доступные только для cpu-type 1, но будет генерировать код, который оптимально работает на cpu-type.
Чтобы понять это последнее утверждение, необходимо понять разницу между архитектурой и микроархитектурой.
Архитектура подразумевает ISA (архитектура набора инструкций), и это не зависит от -mtune
,
Микро-архитектура - это то, как архитектура реализована аппаратно. Для равного набора команд (читай: архитектура) кодовая последовательность может оптимально выполняться на ЦП (читай микроархитектура), но не на другом из-за внутренних деталей реализации. Это может привести к тому, что кодовая последовательность будет оптимальной только для одной микроархитектуры.
При генерации машинного кода часто GCC имеет некоторую свободу выбора, как заказать инструкции и какой вариант использовать.
Он будет использовать эвристику для генерации последовательности инструкций, которые быстро выполняются на самых распространенных процессорах, иногда он жертвует 100% -ным оптимальным решением для CPU x, если это накажет CPU y, z и w.
Когда мы используем -mtune=x
мы тонко настраиваем вывод GCC для CPU x, создавая таким образом код, который на 100% оптимален (с точки зрения GCC) для этого CPU.
В качестве конкретного примера рассмотрим, как этот код компилируется:
float bar(float a[4], float b[4])
{
for (int i = 0; i < 4; i++)
{
a[i] += b[i];
}
float r=0;
for (int i = 0; i < 4; i++)
{
r += a[i];
}
return r;
}
a[i] += b[i];
при нацеливании на Skylake или Core2 различается векторизация (если векторы не перекрываются):
Skylake
movups xmm0, XMMWORD PTR [rsi]
movups xmm2, XMMWORD PTR [rdi]
addps xmm0, xmm2
movups XMMWORD PTR [rdi], xmm0
movss xmm0, DWORD PTR [rdi]
Core2
pxor xmm0, xmm0
pxor xmm1, xmm1
movlps xmm0, QWORD PTR [rdi]
movlps xmm1, QWORD PTR [rsi]
movhps xmm1, QWORD PTR [rsi+8]
movhps xmm0, QWORD PTR [rdi+8]
addps xmm0, xmm1
movlps QWORD PTR [rdi], xmm0
movhps QWORD PTR [rdi+8], xmm0
movss xmm0, DWORD PTR [rdi]
Основное отличие заключается в том, как xmm
регистр загружен, на Core2 он загружен двумя загрузками, используя movlps
а также movhps
вместо использования одного movups
,
Подход с двумя нагрузками лучше подходит для микроархитектуры Core2, если вы посмотрите на таблицы инструкций Agner Fog, то увидите, что movups
декодируется в 4 мопа и имеет задержку 2 цикла, в то время как каждый movXps
1 моп и 1 цикл задержки.
Вероятно, это связано с тем, что 128-битные обращения были разделены на два 64-битных доступа в то время.
На Skylake верно обратное: movups
работает лучше, чем два movXps
,
Таким образом, мы должны подобрать один.
В общем, GCC выбирает первый вариант, потому что Core2 является старой микроархитектурой, но мы можем переопределить это с помощью -mtune
,
1 Набор команд выбирается другими переключателями.