Встроенная сборка против математической библиотеки
Высокий, может ли кто-нибудь помочь мне понять, почему более эффективно вызывать функции библиотеки математики, чем писать код встроенной сборки для выполнения той же операции? Я написал этот простой тест:
#include <stdio.h>
#define __USE_GNU
#include <math.h>
void main( void ){
float ang;
int i;
for( i = 0; i< 1000000; i++){
ang = M_PI_2 * i/2000000;
/*__asm__ ( "fld %0;"
"fptan;"
"fxch;"
"fstp %0;" : "=m" (ang) : "m" (ang)
) ;*/
ang = tanf(ang);
}
printf("Tan(ang): %f\n", ang);
}
Этот код вычисляет тангенс угла двумя различными способами: один вызывает функцию tanf из динамически связанной библиотеки libm.a, а второй - с использованием встроенного кода сборки. Обратите внимание, что я комментирую части кода поочередно. Код выполняет операцию несколько раз, чтобы получить значимые результаты с помощью команды time y linux Terminal.
Версия, в которой используется математическая библиотека, занимает около 0,040 с. Версия, которая использует код сборки, занимает около 0,440 с; в десять раз больше.
Это результаты разборки. Оба были скомпилированы с опцией -O3.
LIBM
4005ad: b8 db 0f c9 3f mov $0x3fc90fdb,%eax
4005b2: 89 45 f8 mov %eax,-0x8(%rbp)
4005b5: f3 0f 10 45 f8 movss -0x8(%rbp),%xmm0
4005ba: e8 e1 fe ff ff callq 4004a0 <tanf@plt>
4005bf: f3 0f 11 45 f8 movss %xmm0,-0x8(%rbp)
4005c4: 83 45 fc 01 addl $0x1,-0x4(%rbp)
4005c8: 83 7d fc 00 cmpl $0x0,-0x4(%rbp)
4005cc: 7e df jle 4005ad <main+0x19>
КАК М
40050d: b8 db 0f c9 3f mov $0x3fc90fdb,%eax
400512: 89 45 f8 mov %eax,-0x8(%rbp)
400515: d9 45 f8 flds -0x8(%rbp)
400518: d9 f2 fptan
40051a: d9 c9 fxch %st(1)
40051c: d9 5d f8 fstps -0x8(%rbp)
40051f: 83 45 fc 01 addl $0x1,-0x4(%rbp)
400523: 83 7d fc 00 cmpl $0x0,-0x4(%rbp)
400527: 7e e4 jle 40050d <main+0x19>
Любая идея? Благодарю.
Я думаю, у меня есть идея. Просматривая код glibc, я обнаружил, что функция tanf реализована с помощью полиномиального приближения и с использованием расширения sse. Я полагаю, что это быстрее, чем микрокод для инструкции fptan.
2 ответа
Существует большая разница в реализации этих функций.
fptan
является устаревшей инструкцией 8087, использующей стек с плавающей запятой. Даже изначально инструкции 8087 были микрокодированы. Вызов fptan
инструкция вызвала запуск предопределенной программы в процессоре 8087, который использовал бы основные возможности процессора, такие как сложение с плавающей запятой или даже умножение. Микрокодирование обходит некоторые этапы "естественной" конвейерной обработки, например, предварительную выборку и декодирование, и это ускоряет процесс.
Алгоритм, выбранный для тригонометрических функций в 8087 году, был CORDIC.
Несмотря на то, что микрокодирование делает fptan быстрее, чем явный вызов каждой инструкции, это не было концом разработки процессора с плавающей запятой; Можно сказать, что разработка 8087 г. закончена. В будущих процессорах fptan, вероятно, должен быть реализован как есть, как IP-блок, который ведет себя идентично исходной инструкции с некоторой связующей логикой для получения побитового точного вывода как оригинала.
Позднее процессоры сначала переработали стек FP для "MMX". Затем был введен совершенно новый набор регистров (XMM) вместе с набором команд (SSE), способным к параллельному выполнению основных операций с плавающей запятой. Во-первых, поддержка плавающей запятой с расширенной точностью (80 бит) была прекращена. С другой стороны, 20-летний закон Мура позволил выделить гораздо более высокое число транзисторов, например, для наращивания, например, 64x64-битных параллельных умножителей, увеличивающих пропускную способность умножения.
Другие инструкции тоже пострадали: loop
когда-то был быстрее, чем sub ecx, 1; jnz
сочетание. aam
сегодня, вероятно, медленнее, чем условное добавление 10 к какому-то куску eax - эти 20 с лишним лет закона Мура позволили миллионам транзисторов ускорить стадию предварительной выборки: в 8086 году каждый байт в кодировке команд считался еще одним циклом. Сегодня несколько инструкций выполняются в течение одного цикла, потому что инструкция уже извлечена из памяти.
При этом, вы также можете попробовать, если одна инструкция, такая как aam
на самом деле быстрее, чем реализация его содержимого с использованием эквивалентного набора более простых инструкций, которые оптимизированы. Это преимущество библиотеки: они могут использовать инструкцию fptan, но они не нужны, если архитектура процессора поддерживает более быстрый набор инструкций, больший параллелизм, более быстрый алгоритм или все эти.
Здесь (Fedora 20, gcc-4.8.2-7.fc20.x86_64, процессор Intel® Core® TM i7-2670QM @ 2,20 ГГц, скомпилированный с -O2) я вижу время пользователя 0,161 с (asm) против 0,076 с (libm).
И хотя компилятору будет позволено избавиться от цикла в версии библиотеки (он знает, tanf(3m)
это чистая функция), сборка показывает, что цикл есть. И функция не встроена, это вызов функции здесь. Еще быстрее Странный.
Хорошо, похоже, разница в том, чтобы перетаскивать аргумент asm()
фрагмент кода (он помещается в локальную переменную и используется оттуда). Я не эксперт по x86_64, и мои ограничения GCC ASM-фу проржавели...
(В любом случае вам придется вычесть всю for
цикл и вычисление угла. Для такой простой операции, как эта, это может быть очень значительная часть от общего объема).