Это действительно вопрос компоновщика/объектного файла, но пометка сборкой, поскольку компиляторы никогда этого не делают. (Хотя, может, и могли!)
Рассмотрим эту функцию, где я хочу обработать один особый случай с блоком кода, который находится в той же строке I-кэша, что и точка входа функции. Чтобы не перепрыгивать через него в обычном быстром пути, безопасно ли (относительно компоновки / общих библиотек / других инструментов, о которых я не подумал) помещать код для этого перед глобальным символом функции? сильный>
Я знаю, что это глупо/излишне, см. ниже. В основном мне было просто интересно. Независимо от того, полезен ли этот метод для создания кода, который на практике работает быстрее, я думаю, что это интересный вопрос.
.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
(Кстати, я возился с asm для nextafter
, потому что я любопытно, как это реализовано в glibc. rel="nofollow">текущая реализация компилируется в какой-то действительно неприятный код с кучей ответвлений. Например, проверка обоих входных данных на наличие 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$, uop-кэш, предикторы ветвлений). И если что, оптимизируйте случай без кэширования с помощью предсказателей холодных переходов.
Это, вероятно, глупо, потому что случай без кэширования может происходить нечасто. Однако, если таким образом оптимизировать многие функции, общий размер кэша уменьшится, и, возможно, все они поместятся в кэш.
Обратите внимание, что последние процессоры Intel вообще не выполняют предсказание статических ветвлений, поэтому нет причин отдавать предпочтение прямым ветвям для обычно неиспользуемых ветвей.
Вместо того, чтобы по умолчанию принимать для обратных ветвей / не принимать для прямых для «неизвестных» ветвей, которых нет в BHT, мое понимание Микроархический документ Агнера Фога (глава о предсказании ветвления) заключается в том, что они не проверяют, является ли ветвь «новой» или нет. Они просто используют любую запись, которая уже есть в BHT, не очищая ее. Однако для Nehalem это может быть не совсем так .
gas
есть директива.size
, которуюgcc
действительно использует, аnm
показывает. Я забыл, для чего это на самом деле используется. Или, может быть, для функций важен только символ.type
, а для не-функций размер имеет значение. - person Jester   schedule 18.02.2016