В технических дискуссиях периодически всплывает утверждение, что код на ассемблере может работать в 10–100 раз быстрее аналогичной реализации на C. Чаще всего такие примеры приводят разработчики FFmpeg — проекта, который занимается обработкой видео. Действительно ли ассемблер даёт такой выигрыш всегда? И если нет, то где проходит граница его применимости?
Для начала стоит разобраться, почему в случае с видеокодеками ассемблер оказывается столь эффективным. Видео по своей природе хорошо параллелится: кадры разбиваются на блоки, декодирование этих блоков можно выполнять независимо. Плюс сами пиксели — это множество однотипных элементов данных, над которыми выполняется одна и та же операция. Современные процессоры оснащены SIMD-инструкциями (Single Instruction, Multiple Data), которые позволяют за одну команду обработать несколько чисел. Компиляторы C умеют автоматически векторизовать код — то есть преобразовывать последовательные циклы в SIMD-операции. Однако на практике состояние этой техники таково, что человек, вручную пишущий на ассемблере, может сделать это заметно лучше.
Почему так происходит? Компилятор ограничен консервативными предположениями о возможных перекрытиях памяти, о выравнивании данных и о динамических условиях в циклах. Человек же видит полную картину и может жёстко задать нужные регистры, выстроить конвейер и избежать лишних проверок. В узких параллельных циклах — обработка пикселей, преобразование цветовых пространств, передискретизация — ассемблер действительно даёт многократный прирост. Цифра в 10–100 раз выглядит правдоподобно именно для такой, хорошо изолированной и высокопараллельной задачи.
Но это не означает, что взятие произвольной программы и переписывание её целиком на ассемблере даст тот же эффект. Общая программная логика — ветвления, вызовы функций, работа со сложными структурами данных — на ассемблере не ускорится кардинально. Более того, ручное написание таких частей скорее приведёт к ошибкам и снижению производительности из-за отсутствия глобальных оптимизаций, которые выполняет современный компилятор. Ассемблер — это инструмент для горячих точек, а не для всего кода.
Если отвлечься от ассемблера и посмотреть на современное состояние индустрии в целом, открывается гораздо более интересная картина. Почти любая программа, которой вы пользуетесь ежедневно, могла бы работать в сто раз быстрее уже сегодня — и для этого не нужно погружаться в ассемблер. Достаточно избавиться от странного культурного багажа, который накопился в программировании за последние десятилетия. Более того, бо́льшая часть программного обеспечения может ускориться в тысячу или даже десять тысяч раз, и это не преувеличение.
Как так вышло? Десятилетиями культивировалось отношение: «компьютеры становятся быстрее сами собой, можно не оптимизировать». Некоторые архитектурные и языковые решения принимались в ущерб производительности ради «чистоты» или «модности» подхода. Объектно-ориентированные абстракции, многослойные фреймворки, виртуальные вызовы и динамическая диспетчеризация — всё это плата за удобство разработчика, а не за скорость выполнения. Разработчиков часто убеждают, что «хорошая структура кода автоматически ведёт к быстрому коду», но это скорее самообман. Хорошая структура облегчает сопровождение, но не гарантирует производительности.
Исключения вроде FFmpeg подтверждают правило. Эти ребята всегда были озабочены скоростью, поэтому их проект уже относительно оптимизирован. Тот 100-кратный выигрыш от ассемблера, о котором они говорят, отсчитывается от их же уже неплохой C-реализации. Для большинства же другого софта даже простой «клининг» — удаление лишних слоёв, выбор адекватных алгоритмов и структур данных, отказ от бессмысленных копирований — даст куда больше, чем гипотетический перевод на ассемблер.
Таким образом, реальный вывод таков: ассемблер остаётся мощным, но узкоспециализированным инструментом для критических по производительности параллельных вычислений. Основной резерв скорости лежит не в нём, а в общем уровне технической культуры. Достаточно перестать верить в то, что «компьютер сам всё разгонит», и начать писать прямой, осмысленный код без избыточных абстракций. Это под силу любому программисту и не требует ни знания ассемблера, ни героических усилий.