Выравнивание элементов данных и функций-членов для производительности

Верно ли, что выравнивание членов данных структуры / класса больше не дает преимуществ, которые он использовал, особенно в отношении nehalem из-за аппаратных улучшений? Если да, то так ли это, чтобы выравнивание всегда улучшало производительность, только незначительные заметные улучшения по сравнению с предыдущими процессорами?

Распространяется ли выравнивание переменных-членов на функции-члены? Я полагаю, что однажды прочитал (это может быть в викибуках «Производительность C ++»), что существуют правила для «упаковки» функций-членов в различные «единицы» (т.е. исходные файлы) для оптимальной загрузки в кэш инструкций? (Если здесь у меня неправильная терминология, поправьте меня).

1

Решение

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

Это означает, что как пространственная, так и временная местность имеют значение много (т.е. как вы упаковываете вещи, и как вы получаете к ним доступ). Хорошая упаковка конструкций (отсортированная по требованиям заполнения / выравнивания), в отличие от упаковки их в некотором случайном порядке, обычно приводит к меньшим размерам конструкции.

Меньшие размеры структуры означают, если у вас есть множество данных:

  • больше структур помещается в одну строку кэша (пропуск кэша = 50-200 циклов)
  • требуется меньше страниц (ошибка страницы = 10-20 миллионов циклов ЦП)
  • требуется меньше записей TLB, меньше промахов TLB (промах TLB = 50-500 циклов)

Линейное перемещение по нескольким гигабайтам плотно упакованных данных SoA может быть на 3 порядка быстрее (или на 8-10 порядков, если возникают ошибки страницы), чем делать то же самое наивным способом с плохой компоновкой / упаковкой.

Ли вы или нет, выровнять руки индивидуальный 4-байтовые или 2-байтовые значения (скажем, типичные int или же short) на 2 или 4 байта очень мало влияет на последние процессоры Intel (едва заметно). Поэтому может показаться заманчивым «оптимизировать» это, но я настоятельно рекомендую не делать этого.
Обычно это то, о чем лучше всего не беспокоиться, и компилятору остается разобраться. Если по какой-либо другой причине, то потому, что выгоды в лучшем случае незначительны, но некоторые другие архитектуры процессоров вызовут исключение, если вы ошибетесь. Поэтому, если вы попытаетесь быть слишком умным, у вас внезапно возникнут необъяснимые сбои после компиляции на какой-либо другой архитектуре. Когда это произойдет, вы пожалеете.

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

4

Другие решения

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

Выравнивание функций в единицах кеша кажется для меня чем-то вроде красной селедки. Для маленьких функций вы действительно хотите встроить, если это вообще возможно. Если код не может быть встроен, то он, вероятно, в любом случае больше, чем строка кэша. [Если это не виртуальная функция, конечно]. Я не думаю, что это когда-либо было огромным фактором — либо код обычно вызывается часто и, следовательно, обычно в кеше, либо он вызывается не очень часто и не очень часто в кеше. Я уверен, что можно придумать некоторый код, в котором при вызове одной функции func1 () будет также перетаскивать func2 () в кеш, поэтому, если вы всегда будете вызывать func1 () и func2 () в короткой последовательности, это приведет к какая-то выгода Но это действительно не то, что приносит большую пользу, если у вас нет большого количества функций с парами или группами функций, которые вызываются близко друг к другу. [Между прочим, я не думаю, что компилятор гарантированно размещает код вашей функции в каком-либо конкретном порядке, независимо от того, в каком порядке вы размещаете его в исходном файле].

Выравнивание кэша — это немного другой вопрос, так как строки кэша могут по-прежнему иметь ОГРОМНЫЙ эффект, если вы все сделаете правильно или неправильно. Это более важно для многопоточности, чем обычная «загрузка данных». Ключевым моментом здесь является предотвращение совместного использования данных в одной строке кэша между процессорами. В проекте, над которым я работал около 10 лет назад, в тесте была функция, которая использовала массив из двух целых чисел для подсчета количества итераций, выполненных каждым потоком. Когда это было разделено на две отдельные строки кэша, тест производительности улучшился с 0,6x на одном процессоре до 1,98x на одном процессоре. Тот же эффект будет происходить на современных процессорах, даже если они намного быстрее — эффект может быть не таким же, но это будет значительное замедление (и чем больше процессоров обмениваются данными, тем больше эффект, поэтому четырехъядерная система будет хуже чем двухъядерный и тд). Это происходит потому, что каждый раз, когда процессор обновляет что-либо в строке кэша, все остальные процессоры, которые прочитали эту строку кэша, должны перезагружать это из процессора, который обновил это [или из памяти в старые времена].

2

По вопросам рекламы [email protected]