Вопрос

Я помню, как где-то читал, что для оптимизации и ускорения определенного раздела кода программисты пишут этот раздел на языке ассемблера.Мои вопросы -

  1. Эта практика все еще практикуется?и как это сделать?
  2. Не кажется ли письмо на ассемблере слишком громоздким и архаичным?
  3. Когда мы компилируем код C (с флагом -O3 или без него), компилятор выполняет некоторую оптимизацию кода, связывает все библиотеки и преобразует код в двоичный объектный файл.Поэтому, когда мы запускаем программу, она уже находится в своей самой базовой форме, т.е.двоичный.Так чем же поможет использование «Языка ассемблера»?

Я пытаюсь понять эту концепцию, и любая помощь или ссылки очень ценятся.

ОБНОВЛЯТЬ: Перефразируя пункт 3 по просьбе dbemerlin. Потому что вы можете написать более эффективный ассемблерный код, чем генерирует компилятор, но если вы не являетесь экспертом по ассемблеру, ваш код, вероятно, будет работать медленнее, потому что часто компилятор оптимизирует код лучше, чем большинство людей.

Это было полезно?

Решение

Единственный случай, когда полезно вернуться к языку ассемблера, — это когда

  • инструкции ЦП не имеют функциональных эквивалентов в C++ (например,инструкции с одной инструкцией и несколькими данными, BCD или десятичные арифметические операции)

    ИЛИ

  • по какой-то необъяснимой причине оптимизатор не может использовать лучшие инструкции процессора

...И...

  • использование этих инструкций ЦП дало бы значительный и полезный прирост производительности для кода с узкими местами.

Простое использование встроенного ассемблера для выполнения операции, которую можно легко выразить на C++ (например, добавление двух значений или поиск в строке), является контрпродуктивным, потому что:

  • компилятор одинаково хорошо умеет это делать
    • чтобы убедиться в этом, посмотрите на выходные данные сборки (например, gcc -S) или дизассемблировать машинный код
  • вы искусственно ограничиваете его выбор относительно распределения регистров, инструкций ЦП и т. д., поэтому может потребоваться больше времени, чтобы подготовить регистры ЦП со значениями, необходимыми для выполнения вашей жестко запрограммированной инструкции, а затем больше времени, чтобы вернуться к оптимальному распределению для будущих инструкций
    • оптимизаторы компилятора могут выбирать между инструкциями с эквивалентной производительностью, определяя разные регистры, чтобы минимизировать копирование между ними, и могут выбирать регистры таким образом, чтобы одно ядро ​​могло обрабатывать несколько инструкций в течение одного цикла, тогда как принудительное прохождение всего через определенные регистры будет сериализовать его.
      • честно говоря, у GCC есть способы выразить потребности в определенных типах регистров, не ограничивая ЦП конкретным регистром, но при этом допуская такую ​​оптимизацию, но это единственная встроенная сборка, которую я когда-либо видел, которая решает эту проблему.
  • если в следующем году выйдет новая модель ЦП с другой инструкцией, которая на 1000% быстрее для той же логической операции, то поставщик компилятора с большей вероятностью обновит свой компилятор для использования этой инструкции и, следовательно, ваша программа получит выгоду от перекомпиляции, чем вы. (или кто-то, кто поддерживает программное обеспечение)
  • компилятор выберет оптимальный подход для целевой архитектуры, о которой он говорит:если вы жестко запрограммируете одно решение, то оно должно будет иметь наименьший общий знаменатель или #ifdef-ed для ваших платформ
  • Язык ассемблера не так переносим, ​​как C++, как между процессорами, так и между компиляторами, и даже если вы, казалось бы, переносите инструкцию, можно допустить ошибку в регистрах, которые можно безопасно уничтожить, соглашениях о передаче аргументов и т. д.
  • другие программисты могут не знать или не чувствовать себя комфортно в ассемблере

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

Другие советы

Прежде всего, вам нужно профилировать вашу программу. Затем вы оптимизируете наиболее используемые пути в C или C ++ код. Если преимущества не ясно, вы не переписываете в ассемблере. Отказ Использование ассемблера заставляет ваш код сложнее поддерживать и гораздо менее портативный - оно не стоит, кроме как в очень редких ситуациях.

(1) Да, самый простой способ попробовать это - использовать встроенный сборник, это зависит от компилятора, но обычно выглядит что-то подобное:

__asm
{
    mov eax, ebx
}

(2) Это очень субъективный

(3) Поскольку вы можете написать более эффективный код сборки, чем создает компилятор.

Вы должны прочитать классическую книгу Zen of Code Optimization и последующие работы Zen of Graphics Programming к Майкл Бабраш.

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

Одна мотивация этой смены ума заключалась в том, что он видел, что высоко оптимизированные программы для одного поколения процессора могут стать (несколько) медленным в следующем генерировании одного и того же процессора семьи по сравнению с кодом, скомпилированным с высокого уровня (возможно, компилятор, используя новые инструкции Например, или производительность и поведение существующих, меняющихся от генерации процессора в другой).

Другая причина заключается в том, что компиляторы довольно хорошие и агрессивно оптимизируются, обычно есть гораздо больше производительности, чтобы получить работу над алгоритмами, которые преобразуют C Code для сборки. Даже для программирования GPU (процессоров графических карт) вы можете сделать это с помощью C с помощью CUDA или OPENCL.

Есть еще некоторые (редкие) случаи, когда вы должны / должны использовать сборку, обычно, чтобы получить очень тонкий контроль на оборудовании. Но даже в ОС Kernel Code обычно очень маленькие детали, а не так много кода.

В наши дни очень мало причин использования языка сборки, даже более низкоуровневые конструкции, такие как SSE, а более старая MMX, встроенные встроенные в GCC, так и в MSVC (ICC, а я никогда не использовал его).

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

@Vjo, обратите внимание, что использование внутрисинов в высоком уровне C-кода C позволит вам выполнить те же оптимизацию, не используя одну инструкцию по сборке.

И за то, что стоит, были обсуждения о следующем компиляторе Microsoft C ++, и как они покинут встроенную сборку из него. Это говорит о томах о необходимости этого.

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

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

Многие люди в комментариях и ответах здесь говорят о «N раз ускорении», которые они получили перезаписывать что-то в сборе, но сам по себе слишком много значит. Я получил 13 раз ускорение от перезаписи функции C, оценивая уравнения динамики жидкости в C., Применив многие из одинаковых оптимизаций, сколько если бы вы записали его в сборке, узнав аппаратное обеспечение и профилированием. В конце, это было достаточно близко к теоретическим пиковым характеристикам ЦП, что будет Нет смысла в переписывании его в сборке. Обычно это не язык, который является ограничивающим фактором, но фактический код, который вы написали. Пока вы не используете «специальные» инструкции, с которыми компилятор испытывает трудности, сложно победить хорошо написанный C ++.

Сборка не волшебно быстрее. Это просто берет компилятор из цикла. Это часто плохое, если вы В самом деле Знайте, что вы делаете, так как компилятор выполняет много оптимизаций, которые действительно очень больно делать вручную. Но в редких случаях компилятор просто не понимает ваш код и не может генерировать эффективную сборку для него, а также тогда, может быть полезно написать некоторую сборку самостоятельно. Кроме развития водителя или тому подобное (где вам нужно напрямую манипулировать оборудованием), единственное место, которое я могу подумать о том, где написание сборки может быть стоить того, если вы застряли с компилятором, который не может генерировать эффективный код SSE из Внутрисины (такие как MSVC). Даже там, я все еще начнут использовать встроенные в C ++ и профилировать его и попробовать настроить его как можно больше, но потому что компилятор просто не очень хорош в этом, в конечном итоге это стоит переписать этот код в сборке.

Я не думаю, что вы указали процессор. Разные ответы в зависимости от процессора и окружающей среды. Общий ответ да, это все еще сделано, это не архаично, конечно. Общая причина - это компиляторы, иногда они делают хорошую работу при оптимизации в целом, но не очень хорошо для конкретных целей. Некоторые действительно хороши в одной цели, а не так хорошо у других. Большую часть времени это достаточно хорошо, в большинстве случаев вы хотите портативный C-код и не без портативный ассемблер. Но вы все еще находите, что библиотеки C все еще будут оптимизировать MEMCPY и другие процедуры, которые компилятор просто не может понять, что это очень быстрый способ реализации его. Отчасти, потому что этот угловой случай не стоит проводить время на создание оптимизации компилятора, просто решайте его в ассемблере, и система сборки имеет много, если эта цель, затем использует C, если эта цель использует C, если это целевое использование ASM, если это Цель использует ASM. Так что это все еще происходит, и я спорю должен продолжаться навсегда в некоторых областях.

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

Другие платформы, которые часто означает встроенные, ARM, MIPS, AVR, MSP430, PIC и т. Д. Вы можете или не могут работать операционную систему, вы можете или не могут работать с кэшем или другими такими вещами, которые имеет ваш рабочий стол. Таким образом, слабые стороны компилятора покажутся. Также обратите внимание, что языки программирования продолжают развиваться от процессоров, а не к ним. Даже в случае считается, что может быть низкоуровневым языком, он не соответствует набору инструкций. Всегда будут времена, где вы можете производить сегменты ассемблера, которые превосходят компилятор. Не обязательно сегмент, который является вашим узким местом, но по всей программе, которую вы часто можете сделать улучшения здесь и там. Вы все еще должны проверить значение этого. В встроенной среде она может и делает разницу между успехом и провалом продукта. Если ваш продукт имеет 25 долларов США за единицу, инвестированному в большей власти, голодный, доски недвижимости, более высокие скоростные процессоры, поэтому вам не нужно использовать ассемблер, но ваш конкурент тратит 10 или менее на единицу и готов смешать ASM с C для использования меньших воспоминаний, Используйте меньше энергии, более дешевые детали и т. Д. Ну до тех пор, пока Nre восстановится, то смешанный с раствором ASM будет в долгосрочной перспективе.

Настоящий встроенный является специализированным рынком со специализированными инженерами. Другой встроенный рынок, ваш встроенный Linux Roku, Tivo и т. Д. Встроенные телефоны и т. Д. Всю необходимую иметь портативные операционные системы для выживания, потому что вам нужны разработчики сторонним. Таким образом, платформа должна быть больше похожей на рабочий стол, чем встроенная система. Похоронено в библиотеке C, как уже упоминалось или операционная система может быть некоторые оптимизации ассемблера, но, как и в случае с рабочим столом, вы хотите попробовать бросить более аппаратное обеспечение, поэтому программное обеспечение может быть портативным вместо рук оптимизировано. И ваша линейка продуктов или встроенная операционная система потерпит неудачу, если ассемблер требуется для успеха третьего лица.

С самой большой проблемой у меня есть то, что эти знания теряются в тревожном уровне. Потому что никто не проверяет ассемблер, потому что никто не пишет в ассемблере и т. Д. Никто не замечает, что компиляторы не улучшаются, когда дело доходит до создаваемого кода. Разработчики часто думают, что они должны купить больше оборудования вместо того, чтобы осознавать, что либо зная компилятора, либо как лучше программировать, они могут улучшить их производительность на 5-6 сотен процентов с тем же компилятором, иногда с тем же исходным кодом. 5-10% Обычно с одинаковым исходным кодом и компилятором. GCC 4 не всегда производит лучший код, чем GCC 3, я сохраняю оба вокруг, потому что иногда GCC3 делает лучше. Целевые конкретные компиляторы могут (не всегда делать) проводить круги вокруг GCC, вы можете увидеть несколько сотен процентов улучшения иногда с одним и тем же исходным кодом различного компилятора. Откуда все это приходит? Люди, которые все еще пытаются смотреть и / или использовать ассемблер. Некоторые из этих людей работают на спинках компилятора. Передний конец и середины веселые и воспитательные, но бэкэнда - это то, где вы делаете или сломаете качество и производительность полученной программы. Даже если вы никогда не пишете ассемблером, но время от времени посмотрите на вывод от компилятора (GCC -O2 -S MyProg.c) Это сделает вас лучшим программистом высокого уровня и сохранит некоторые из этих знаний. Если никто не готов знать, и писать ассемблером, то по определению мы отказались в письменном виде, и поддержание компиляторов для языков высокого уровня и программного обеспечения в целом перестанет существовать.

Поймите, что с GCC, например, вывод компилятора является сборка, который передается на ассемблер, который превращает его в объектный код. Компилятор C обычно не производит двоичные файлы. Объекты при входящем в конечный двоичный двоич, выполняются линкером, но другой программой, которая вызывается компилятором, а не частью компилятора. Компилятор поворачивает C или C ++ или ADA или что-то еще в ассемблере, а затем инструменты ассемблера и линкера. Динамические компенсаторы, такие как TCC, например, должны иметь возможность как-то генерировать двоичные файлы на лету, но я вижу, что, как исключение, а не правило. LLVM имеет свое собственное решение выполнения, а также довольно заметно показывает высокий уровень к внутреннему коду для целевого кода в двоичный путь, если вы используете его в качестве перекрестного компилятора.

Так вернемся к точке, да, это делается, чаще всего, чем вы думаете. В основном связано с языком, не сравним непосредственно на набор инструкций, а затем компилятор не всегда выпускает достаточно быстрый код. Если вы можете сказать, что десятки раз улучшаются на тяжелых использоваемых функциях, таких как malloc или meMcpy. Или хотите иметь HD-видеоплеер на вашем телефоне без аппаратной поддержки, сбалансируйте плюсы и минусы ассемблера. Действительно встроенные рынки все еще используют ассемблер совсем немного, иногда это все C, но иногда программное обеспечение полностью закодировано в ассемблере. Для настольного компьютера X86 процессор не является узким местом. Процессоры микрокодируемые. Даже если вы делаете красивый ассемблер на поверхности, он не будет работать очень быстро на всех семейных процессорах x86, небрежный, достаточно хороший код, скорее всего, будет работать примерно одинаково через доску.

Я настоятельно рекомендую учиться ассемблером для Non X86 ISAS, как руки, большой палец / Thumb2, MIPS, MSP430, AVR. Цели, которые имеют компиляторы, особенно с поддержкой компилятора GCC или LLVM. Узнайте ассемблер, научитесь понимать вывод компилятора C, и докажите, что вы можете сделать лучше, на самом деле изменив этот выход и тестирование. Эти знания помогут сделать ваш рабочий стол высокого уровня намного лучше без ассемблера, быстрее и надежнее.

Посмотри здесь, где парень улучшил выступления 6 раз, используя код сборки. Итак, ответ: он все еще делается, но компилятор делает довольно хорошую работу.

На моей работе я использовал сборку на встроенной цели (микроконтроллер) для доступа к низкому уровню.

Но для программного обеспечения для ПК я не думаю, что это очень полезно.

У меня есть пример оптимизации сборки, которую я сделал, но опять же на встроенной цели. Вы можете увидеть несколько примеров программирования сборки для ПК, а также создает действительно небольшие и быстрые программы, но обычно не стоит усилий (ищите сборку для Windows », вы можете найти несколько очень маленьких и красивых программ).

Моим примером было, когда я писал контроллер принтера, и была функция, которая должна была вызвана каждые 50 микро секунд. Это должно сделать решение битов, более или менее. Использование C Я смог сделать это примерно в 35 микросекундах, а с сборкой я сделал это примерно в 8 микросекундах. Это очень конкретная процедура, но все же, что-то реальное и необходимо.

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

  1. "Эта практика все еще сделана?" -> Это делается в обработке изображений, обработки сигналов, Ai (например, эффективное матричное умножение), а другие. Я бы поспорил, что обработка жест прокрутки на моем MacBook TrackPad также является частично вспомогательным кодом, потому что он немедленно. -> Это даже сделано в приложениях C # (см. https://blogs.msdn.microsoft.com/winsdk/2015/02/09/c-and-fastcall-how-tomake-them-work-seher-without-ccli-shellcode/)

  2. «Не писать на языке сборки немного слишком громоздким и архаичным?» -> Это инструмент, похожий на молоток или отвертку, и некоторые задачи требуют отвертки часовщика.

    1. «Когда мы компилируем C-код (с флагом -O3 или без3), компилятор делает некоторую оптимизацию кода ... так как как побуждает помогать« языку сборки »?» -> Мне нравится, что сказал @jalf, что написание C-кода таким образом, когда вы пишете сборку уже приведут к эффективному коду. Однако для этого вы должны подумать, как вы пишете код на языке сборки, так, например,. Понимать все места, где данные копируются (и чувствуют боль каждый раз, когда это ненужно). С языком сборки вы можете быть уверены, какие инструкции генерируются. Даже если ваш C-код эффективен, нет гарантии, что полученная сборка будет эффективным с каждым компилятором. (видеть https://lucasmeijer.com/posts/cpp_unity/). С внутринами это также возможно в C ++ или .NET Core 3. (см. https://devblogs.microsoft.com/dotnet/using-net-hardware-intrinsics-ap-ato-accelerate-machine-learning-scenarios/)
  1. да. Используйте либо встроенные модули объекта встроенной сборки или ссылки. Какой метод вы должны использовать, зависит от того, сколько кода сборки вам нужно написать. Обычно это нормально, чтобы использовать встроенную сборку для нескольких строк и переключиться на отдельные объектные модули один раз, если это более одной функции.
  2. Определенно, но иногда это необходимо. Видный пример здесь будет программировать операционную систему.
  3. Большинство компиляторов сегодня оптимизируют код, который вы пишете на языке высокого уровня намного лучше, чем кто-либо может писать код сборки. Люди в основном используют его для записи кода, который в противном случае был бы невозможен писать на языке высокого уровня, такими как C. Если кто-то использует его для чего-то еще, означает, что он либо лучше на оптимизации, чем современный компилятор (я сомневаюсь в этом) или просто глупо , например, он не знает, какие флаги компилятора или функциональные атрибуты для использования.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top