Это действительно вопрос с компоновщиком/объектным файлом, но пометка с сборкой, поскольку компиляторы никогда этого не делают. (Хотя возможно, они могли бы!)Выполняет ли функция с инструкциями перед меткой точки входа проблемы для чего-либо (связывание)?
Рассмотрите эту функцию, где я хочу обрабатывать один специальный случай с блоком кода, который находится в той же строке I-cache, что и точка входа функции. Чтобы избежать перескакивания по нему в обычном быстром пути, безопасно (например, ссылки/общие библиотеки/другие инструменты, о которых я не думал), чтобы поставить код для него перед глобальным символом функции?
Я знаю, что это глупо/overkill, см. Ниже. В основном мне было просто любопытно. Независимо от того, полезен ли этот метод для создания кода, который на самом деле работает быстрее на практике, я думаю, что это интересный вопрос.
.globl __nextafter_pjc // double __nextafter_pjc(double x, double y)
.p2align 6 // unrealistic 64B alignment, just for the sake of argument
// GNU as local labels have the form .L...
.Lequal_or_unordered:
jp .Lunordered
movaps %xmm1, %xmm0 # ISO C11 requires returning y, not x. (matters for -0.0 == +0.0)
ret
######### Function entry point/global symbol here #############
// .p2align something // tuning for Sandybridge, maybe best to just leave this unaligned, since it's only 6B from the alignment boundary
nextafter_pjc:
ucomisd %xmm1, %xmm0
je .Lequal_or_unordered
xorps %xmm3, %xmm3
comisd %xmm3, %xmm0 // x==+/0.0 can be a special case: the sign bit may change
je .Lx_zero
movq %xmm0, %rax
... // some mostly-branchless bit-ninjutsu that I have no idea how I'd get gcc to emit from C
ret
.Lx_zero:
...
ret
.Lunordered:
...
ret
(BTW, я возиться с ассемблером для nextafter
, потому что мне было интересно, как Glibc реализовала. Оказывается current implementation компилирует некоторые действительно неприятный код с тонной ветвей. Например, проверкой оба входа для NaN должно быть сделано с FP сравнить, потому что это супер-быстрый особ. в не-NaN случае.)
в разборке выходе, инструкции перед этикетке сгруппированы после инструкции предыдущей функции. например
0000000000400ad0 <frame_dummy>:
...
400af0: 5d pop %rbp
400af1: e9 7a ff ff ff jmpq 400a70 <register_tm_clones>
400af6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
400afd: 00 00 00
400b00: 7a 56 jp 400b58 <__nextafter_pjc+0x52>
400b02: 0f 28 c1 movaps %xmm1,%xmm0
400b05: c3 retq
0000000000400b06 <__nextafter_pjc>:
400b06: 66 0f 2e c1 ucomisd %xmm1,%xmm0
400b0a: 74 f4 je 400b00 <frame_dummy+0x30>
400b0c: 0f 57 db xorps %xmm3,%xmm3
400b0f: 66 0f 2f c3 comisd %xmm3,%xmm0
400b13: 74 4b je 400b60 <__nextafter_pjc+0x5a>
400b15: 66 48 0f 7e c0 movq %xmm0,%rax
...
Обратите внимание, что 4-й команд в основном корпусе, comisd
, начинается в 400b0f
(и не полностью содержится в первом 16B-выровненного блока, который содержит функцию точку входа). Таким образом, это может быть не совсем оптимально для команды-выборки и декодирования для быстрого пути, который не был принят, чтобы сделать это именно таким образом. Это всего лишь пример.
Значит, появляется, чтобы работать, даже в начале файла. Он путает objdump
и не идеален в gdb
(но это не большая проблема). Объектные файлы ELF в любом случае не записывают размеры символов, поэтому nm --print-size
ничего не делает. (И nm --size-sort --print-size
, который пытается рассчитать размеры символов, странно не включил мою функцию.)
Я мало знаю о объектных файлах Windows. Что-нибудь хуже происходит?
Я немного обеспокоен правильностью здесь: что-нибудь пытается скопировать отдельные функции из объектных файлов, взяв байты с их символьного адреса на следующий адрес символа? Обычные библиотечные архивы (ar
для статических библиотек) и компоновщики копируют целые файлы объектов, верно? В противном случае они не были уверены, что они копируют все необходимые статические данные.
Эта функция, вероятно, называется нечасто, и мы хотим, чтобы свести к минимуму загрязнение кэша (I $, УОП-кэш, филиал-предикторы). И если что-нибудь, оптимизируйте для случая без кэширования с предикторами холодной ветви.
Это, вероятно, глупо, потому что случай без кэширования может произойти нечасто. Однако, если многие функции все оптимизированы таким образом, общий размер кеша уменьшится, и, возможно, они будут все вписываются в кеш.
Обратите внимание, что последние процессоры Intel не выполняют статического предсказания ветвлений вообще, поэтому нет причин поощрять ветви вперед для обычно не принятых ветвей.
Вместо того, чтобы не принимать во внимание отстающие ветви/не приняты для пересылки для «неизвестных» ветвей, которые не находятся в BHT, мое понимание Agner Fog's microarch doc (the branch prediction chapter) заключается в том, что они не проверяют, является ли ветка «новой» или не. Они просто используют любую запись уже в BHT, не очищая ее. This may not be exactly true, though, for Nehalem.
Размер функции важен для общих библиотек, хотя есть и директива '.size' в' gas', которую 'gcc' действительно использует, и' nm' показывает. Я забыл, для чего это фактически используется. Или, может быть, для функций только символ «.type» важен, а размер имеет значение для не-функций. – Jester