Когда сборка происходит быстрее, чем C? - PullRequest
440 голосов
/ 23 февраля 2009

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

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

Может ли кто-нибудь предоставить конкретных примеров случаев, когда сборка будет быстрее, чем хорошо написанный код C с использованием современного компилятора, и можете ли вы подтвердить это утверждение профилирующим доказательством? Я вполне уверен, что эти случаи существуют, но я действительно хочу точно знать, насколько эзотеричны эти случаи, так как это, кажется, является предметом некоторого спора.

Ответы [ 39 ]

6 голосов
/ 30 июня 2017

Я прочитал все ответы (более 30) и не нашел простой причины: ассемблер работает быстрее, чем C, если вы прочитали и применили Справочное руководство по оптимизации архитектур Intel® 64 и IA-32 , поэтому причина, по которой сборка может быть медленнее, заключается в том, что люди, которые пишут такую ​​медленную сборку, не читали Руководство по оптимизации .

В старые добрые времена Intel 80286 каждая инструкция выполнялась с фиксированным числом циклов ЦП, но после выпуска Pentium Pro, выпущенного в 1995 году, процессоры Intel стали суперскалярными, используя сложную конвейерную обработку: выполнение и регистр вне порядка Переименование. До этого на Pentium, выпущенном в 1993 году, существовали конвейеры U и V: линии с двумя конвейерами, которые могли выполнять две простые инструкции за один такт, если они не зависели друг от друга; но это было ничто в сравнении с тем, что «Выполнение вне очереди» и «Переименование регистров» появилось в Pentium Pro и почти не изменилось.

Чтобы объяснить в двух словах, самый быстрый код - это когда инструкции не зависят от предыдущих результатов, например Вы всегда должны очищать целые регистры (по movzx) или использовать add rax, 1 вместо этого или inc rax, чтобы удалить зависимость от предыдущего состояния флагов и т. д.

Вы можете прочитать больше о выполнении заказа и переименовании регистра, если позволяет время, в Интернете достаточно информации.

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

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

6 голосов
/ 23 февраля 2009

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

5 голосов

Простой ответ ... Тот, кто знает сборку хорошо (он же имеет ссылку на него, использует все функции кеша, конвейера и т. Д.), Гарантирован быть способным генерировать гораздо более быстрый код, чем любой компилятор.

Однако в наши дни разница не имеет значения в типичном приложении.

5 голосов
/ 23 февраля 2009

Инструкция по сборке LInux , задает этот вопрос и дает плюсы и минусы использования сборки.

4 голосов
/ 24 мая 2009

GCC стал широко используемым компилятором. Его оптимизации в целом не так хороши. Гораздо лучше, чем средний программист, пишущий на ассемблере, но для реальной производительности это не так хорошо. Есть компиляторы, которые просто невероятны в коде, который они производят. Так что в качестве общего ответа будет много мест, где вы можете перейти к выводу компилятора и настроить ассемблер для повышения производительности и / или просто переписать подпрограмму с нуля.

4 голосов
/ 23 февраля 2009

http://cr.yp.to/qhasm.html имеет много примеров.

4 голосов
/ 23 февраля 2009

При правильном программировании программы на ассемблере всегда могут быть выполнены быстрее, чем их аналоги на С (по крайней мере, незначительно). Было бы трудно создать C-программу, в которой вы не могли бы вытащить хотя бы одну инструкцию Ассемблера.

4 голосов
/ 23 февраля 2009

Одной из возможностей версии PolyPascal для CP / M-86 (брат Turbo Pascal) было заменить функцию «использовать биос для вывода символов на экран» процедурой машинного языка. которым по сути были даны x, y и строка для размещения.

Это позволило обновлять экран намного быстрее, чем раньше!

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

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

Насколько мне известно, нет компиляторов C, которые могли бы объединять несколько значений в регистр, выполнять SIMD-инструкции для них и разбивать их позже (и я не думаю, что машинные инструкции все равно будут короче).

4 голосов
/ 23 февраля 2009

Один из наиболее известных фрагментов сборки взят из цикла отображения текстур Майкла Абраша ( подробно описан здесь ):

add edx,[DeltaVFrac] ; add in dVFrac
sbb ebp,ebp ; store carry
mov [edi],al ; write pixel n
mov al,[esi] ; fetch pixel n+1
add ecx,ebx ; add in dUFrac
adc esi,[4*ebp + UVStepVCarry]; add in steps

В настоящее время большинство компиляторов выражают расширенные специфичные для процессора инструкции в виде встроенных функций, то есть функций, которые компилируются вплоть до фактической инструкции. MS Visual C ++ поддерживает встроенные функции для MMX, SSE, SSE2, SSE3 и SSE4, поэтому вам не нужно беспокоиться о переходе к сборке, чтобы воспользоваться преимуществами инструкций для конкретной платформы. Visual C ++ также может использовать фактическую архитектуру, на которую вы ориентируетесь, с соответствующей настройкой / ARCH.

4 голосов
/ 19 апреля 2015

Как насчет создания машинного кода во время выполнения?

Мой брат однажды (около 2000 г.) реализовал чрезвычайно быструю трассировку лучей в реальном времени, генерируя код во время выполнения. Я не помню деталей, но был какой-то основной модуль, который проходил по объектам, затем он готовил и выполнял некоторый машинный код, который был специфичен для каждого объекта.

Однако со временем этот метод был отменен новым графическим оборудованием и стал бесполезным.

Сегодня я думаю, что с помощью этого метода можно оптимизировать некоторые операции с большими данными (миллионы записей), такие как сводные таблицы, детализация, вычисления на лету и т. Д. Вопрос: стоит ли это усилий?

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