Intereting Posts
Матричное умножение с использованием нескольких streamов? Является ли указатель тегами в C неопределенным в соответствии со стандартом? Почему этот метод для перегрузки функций в C работает? гамма или логарифмическая гамма-функция в C или C Вложенные циклы в CUDA CTypes error loading DLL, вызывающая другую DLL Каким будет его выход и почему? Я знаком с Ruby / DL, но не уверен, как использовать вызовы функций C с указателями для возвращаемых параметров Почему в структуре нет прописных элементов только для членов типа «char»? Есть ли случай, когда включение одного и того же заголовка в два раза действительно полезно? Биты разрешения разделяемой памяти в системе V: значение и способы изменения Передайте char * прямо в printf C: «те же файловые дескрипторы всех клиентских подключений» (программирование клиентских серверов) C: dup2 () перед execv Какова функция звездочки перед именем функции?

Выравнивание кода в одном объектном файле влияет на производительность функции в другом объектном файле

Я знаком с выравниванием данных и производительностью, но я довольно новичок в выравнивании кода. Недавно я начал программирование на сборке x86-64 с NASM и сравнивал производительность с использованием выравнивания кода. Насколько я могу сказать, NASM вставляет nop инструкции для достижения выравнивания кода.

Вот функция, которую я пытался использовать в системе Ivy Bridge

 void triad(float *x, float *y, float *z, int n, int repeat) { float k = 3.14159f; int(int r=0; r<repeat; r++) { for(int i=0; i<n; i++) { z[i] = x[i] + k*y[i]; } } } 

Узел, который я использую для этого, приведен ниже. Если я не укажу выравнивание, моя производительность по сравнению с пиком составляет всего около 90%. Однако, когда я выровняю код перед циклом, а также внутренние петли до 16 байтов, производительность достигает 96%. Настолько ясно, что выравнивание кода в этом случае имеет значение.

Но вот самая странная часть. Если я выровняю самый внутренний цикл до 32 байт, он не имеет никакого значения в производительности этой функции, однако в другой версии этой функции, используя встроенные средства в отдельный объектный файл, я связываюсь с его перескакиванием от 90% до 95%!

Я сделал dump объекта (используя objdump -d -M intel ) версии, выровненной до 16 байтов (я отправил результат до конца этого вопроса) и 32 байта, и они идентичны! Оказывается, что внутренняя петля в целом совпадает с 32 байтами в обоих объектных файлах. Но должна быть какая-то разница.

Я сделал шестнадцатеричный дамп каждого объектного файла, и в объектных файлах есть один байт. Объектный файл, выровненный с 16 байтами, имеет байт с 0x10 а файл объекта, выровненный до 32 байтов, имеет байт с 0x20 . Что именно происходит! Почему выравнивание кода в одном объектном файле влияет на производительность функции в другом объектном файле? Как узнать, что является оптимальным значением для выравнивания моего кода?

Мое единственное предположение заключается в том, что когда код перемещается загрузчиком, 32-байтовый выровненный объектный файл влияет на другой файл объекта, используя встроенные средства. Вы можете найти код, чтобы проверить все это при получении максимальной пропускной способности на Haswell в кеше L1: получение 62%

Код NASM, который я использую:

 global triad_avx_asm_repeat ;RDI x, RSI y, RDX z, RCX n, R8 repeat pi: dd 3.14159 align 16 section .text triad_avx_asm_repeat: shl rcx, 2 add rdi, rcx add rsi, rcx add rdx, rcx vbroadcastss ymm2, [rel pi] ;neg rcx align 16 .L1: mov rax, rcx neg rax align 16 .L2: vmulps ymm1, ymm2, [rdi+rax] vaddps ymm1, ymm1, [rsi+rax] vmovaps [rdx+rax], ymm1 add rax, 32 jne .L2 sub r8d, 1 jnz .L1 vzeroupper ret 

Результат от objdump -d -M intel test16.o . Разборка идентична, если я изменяю align 16 чтобы align 32 в сборке выше как раз перед .L2 . Однако объектные файлы по-прежнему отличаются на один байт.

 test16.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: d0 0f ror BYTE PTR [rdi],1 2: 49 rex.WB 3: 40 90 rex xchg eax,eax 5: 90 nop 6: 90 nop 7: 90 nop 8: 90 nop 9: 90 nop a: 90 nop b: 90 nop c: 90 nop d: 90 nop e: 90 nop f: 90 nop 0000000000000010 : 10: 48 c1 e1 02 shl rcx,0x2 14: 48 01 cf add rdi,rcx 17: 48 01 ce add rsi,rcx 1a: 48 01 ca add rdx,rcx 1d: c4 e2 7d 18 15 da ff vbroadcastss ymm2,DWORD PTR [rip+0xffffffffffffffda] # 0  24: ff ff 26: 90 nop 27: 90 nop 28: 90 nop 29: 90 nop 2a: 90 nop 2b: 90 nop 2c: 90 nop 2d: 90 nop 2e: 90 nop 2f: 90 nop 0000000000000030 : 30: 48 89 c8 mov rax,rcx 33: 48 f7 d8 neg rax 36: 90 nop 37: 90 nop 38: 90 nop 39: 90 nop 3a: 90 nop 3b: 90 nop 3c: 90 nop 3d: 90 nop 3e: 90 nop 3f: 90 nop 0000000000000040 : 40: c5 ec 59 0c 07 vmulps ymm1,ymm2,YMMWORD PTR [rdi+rax*1] 45: c5 f4 58 0c 06 vaddps ymm1,ymm1,YMMWORD PTR [rsi+rax*1] 4a: c5 fc 29 0c 02 vmovaps YMMWORD PTR [rdx+rax*1],ymm1 4f: 48 83 c0 20 add rax,0x20 53: 75 eb jne 40  55: 41 83 e8 01 sub r8d,0x1 59: 75 d5 jne 30  5b: c5 f8 77 vzeroupper 5e: c3 ret 5f: 90 nop 

Запутанная природа эффекта (собранный код не меняется!), Который вы видите, обусловлен выравниванием раздела . При использовании макроса ALIGN в NASM на самом деле он имеет два отдельных эффекта:

  1. Добавьте 0 или более инструкций nop чтобы следующая команда была выровнена с указанной границей из двух сторон.

  2. Выдайте неявный макрокоманд SECTALIGN который установит директиву выравнивания секций на сумму выравнивания 1 .

Первая точка – это общепринятое поведение для выравнивания. Он выравнивает цикл относительно секции в выходном файле.

Вторая часть также необходима: представьте, что ваша петля была выровнена с 32-байтовой границей в собранном разделе, но затем загрузчик времени выполнения разместил ваш раздел в памяти по адресу, выровненному только до 8 байтов: выравнивание файлов совершенно бессмысленно. Чтобы исправить это, большинство исполняемых форматов позволяют каждому разделу указывать требование выравнивания , а загрузчик / компоновщик времени выполнения обязательно загрузит раздел по адресу памяти, который соответствует требованию.

Вот что делает скрытый макрос SECTALIGN – он гарантирует, что ваш макрос ALIGN работает.

Для вашего файла нет разницы в собранном коде между ALIGN 16 и ALIGN 32 потому что следующая 16-байтная граница также будет следующей 32-байтной границей (конечно, каждая другая 16-байтная граница представляет собой 32-байтовый один, так что это происходит примерно в половине случаев). SECTALIGN вызов SECTALIGN по-прежнему отличается, и это разница в байтах, которую вы видите в своем hexdump. 0x20 является десятичным числом 32, а 0x10 – десятичным.

Вы можете проверить это с помощью objdump -h . Вот пример в двоичном файле I, выровненном до 32 байтов:

 objdump -h loop-test.o loop-test.o: file format elf64-x86-64 Sections: Idx Name Size VMA LMA File off Algn 0 .text 0000d18a 0000000000000000 0000000000000000 00000180 2**5 CONTENTS, ALLOC, LOAD, READONLY, CODE 

2**5 в столбце Algn – это 32-байтовое выравнивание. При 16-байтовом выравнивании это изменяется на 2**4 .

Теперь должно быть ясно, что происходит – выравнивание первой функции в вашем примере изменяет выравнивание раздела, но не сборку. Когда вы связали свою программу вместе, компоновщик объединит различные секции .text и выберет самое высокое выравнивание.

Во время выполнения это приводит к выравниванию кода с 32-байтной границей, но это не влияет на первую функцию, поскольку она не чувствительна к выравниванию. Поскольку компоновщик объединил ваши объектные файлы в один раздел, большее выравнивание 32 изменяет выравнивание каждой функции (и инструкции) в этом разделе, включая ваш другой метод, и поэтому он изменяет производительность вашей другой функции, которая является выравниванием , чувствительные.


1 Чтобы быть точным, SECTALIGN изменяет выравнивание раздела только в том случае, если текущее выравнивание раздела меньше заданной величины, поэтому выравнивание финальной секции будет таким же, как и самая SECTALIGN директива SECTALIGN в этом разделе.

Ahhh, выравнивание кода …

Некоторые основы выравнивания кода.

  • Большинство архитектур Intel получают 16 байт инструкций за такт.
  • Проектор-ветвь имеет большее окно и обычно выглядит двойным, что за часы. Идея состоит в том, чтобы опередить введенные инструкции.
  • Как выравнивается ваш код, будет указывать, какие инструкции вы можете использовать для декодирования и outlookирования на любых заданных часах (простой аргумент локальности кода).
  • Большинство современных команд Intel разрабатывают инструкции кэширования на разных уровнях (либо на уровне макрокоманд перед декодированием, либо на уровне микроуровней после декодирования). Это устраняет эффекты выравнивания кода, если вы выполняете кеширование микро / макросов.
  • Кроме того, в большинстве современных архитектур Intel есть определенный вид детекторов streamа контуров, которые обнаруживают циклы, снова, выполняя их из некоторого кеша, который обходит механизм выборки переднего конца.
  • Некоторые архитектуры Intel скупают то, что они могут кэшировать, и то, что они не могут. Часто есть зависимости от количества инструкций / uops / alignment / branches / etc. Выравнивание может в некоторых случаях влиять на то, что кэшируется, а что нет, и вы можете создавать случаи, когда заполнение может предотвратить или заставить цикл получить кеширование.
  • Чтобы сделать вещи еще более сложными, адреса инструкций также используются предиктором ветки. Они используются несколькими способами, в том числе (1) для поиска в буфере предсказания ветвей для outlookирования ветвей, (2) в качестве ключа / значения для поддержания некоторой формы глобального состояния поведения ветвей для целей outlookирования, (3) как ключом к определению косвенных целевых показателей и т. д. Поэтому выравнивание может фактически иметь очень большое влияние на предсказание ветвей, в некоторых случаях, из-за псевдонимов или других плохих outlookов.
  • В некоторых архитектурах используются адреса инструкций, чтобы определить, когда предварительно выбирать данные, и выравнивание кода может помешать этому, если существуют только правильные условия.
  • Выравнивание циклов – это не всегда хорошая работа, в зависимости от того, как выкладывается код (особенно, если в цикле есть stream управления).

Сказав все, что бла-бла, ваш вопрос может быть одним из них. Важно рассмотреть parsingку не только объекта, но и исполняемого файла. Вы хотите узнать, какие конечные адреса после того, как все связано. Внесение изменений в один объект может повлиять на выравнивание / адреса инструкций в другом объекте после ссылки.

В некоторых случаях почти невозможно выровнять свой код таким образом, чтобы максимизировать производительность, просто из-за того, что многие архитектурные поведения низкого уровня трудно контролировать и outlookировать (что не обязательно означает, что это всегда так). В некоторых случаях лучше всего иметь страtagsю выравнивания по умолчанию (например, выровнять все записи на границах 16B, а внешние петли одинаковы), чтобы вы минимизировали объем вашей работы от изменения к изменению. В качестве общей страtagsи выравнивание записей функций является хорошим. Выравнивание циклов, относительно небольших, является хорошим, если вы не добавляете nops в свой путь выполнения.

Помимо этого, мне понадобится дополнительная информация / данные, чтобы точно определить вашу конкретную проблему, но подумал, что это может помочь. Удачи 🙂