Компиляция и бенчмарки AsmX для хейтеров

Компиляция и бенчмарки AsmX для хейтеров

Илон Маск

У AsmX есть три варианта компиляции: EXE, APP, ARM. Разберём их работоспособность и производительность на последней версии языка от 18.08.23 13.00.

Часть 1. EXE

Самый недоделанный вариант компиляции, несмотря на разработку с 16 июня 2023 года, до сих пор полностью неработоспособна.

Первое найденное упоминание о разработке EXE-компиляции.

Часть 2. ARM

Компиляция под ARM в AsmX создаёт .s файл с ассемблерным кодом под ARM. Скомпилируем программу, состоящую из одной инструкции @add 1, 1.

Выходной файл .s

Мало того, что этот способ компиляции работает в отрыве от характеристик конкретного ARM ядра, которые очень разнообразны(от дешёвых микроконтроллеров STM32F103 до мобильных процессоров серии Qualcomm Snapdragon). Микроконтроллерных действий, как например, настройки частоты ядра и переферии или назначения режима работы и подтяжек по напряжению пинам чипа не выполнено. Имена регистров не подходят ни одному существующему процессору. Есть очень условная оптимизация в сложении аргументов во время компиляции, но зачем тогда здесь исползована команда add с одним нулевым аргументом?

Так же крайне ограничен набор компилируемых инструкций:

Переменные и константы
Арифметичские операции

Таким образом скомпилировать даже Hello, world! на AsmX под ARM невозможно.

Часть 3. APP

Уникальный авторский бинарник и единственный рабочий способ компиляции и последующего выполнения AsmX на данный момент.

Авторское описание формата APP


Этот формат также страдает от проблемы малого количества поддерживаемых инструкций. Их до сих пор всего четыре:

Как видно, все инструкции, кроме четырёх - игнорируются.

Для проверки компиляции этих инструкций скомпилируем простой вывод переменной в APP.

Код теста
Текстскомпилированного .app файла

Что мы тут видим? Странный заголовок, напоминающий заголовок ARM компилята из прошлой части:

Заголовок .s файла

Затем имя переменной обычным текстом, её значение, тоже, конечно, текстом и вызов прерываний просто указанием номера инструкции invoke, их номера и аргумента. Прошу заметить десятичность номеров прерываний и их текстовость.

Примечание: Обычно @invoke 16 не компилируется, я удалил из компилятора ошибку, связанную с номером прерывания. для доказательсятва десятичности номеров.

Теперь поговорим о возможностях этих четырёх инструкций. @set - создание переменной, @define - создание константы, @route - push в стэк, @invoke - вызов авторских прерываний, в основном, предназначенных для ввода/вывода. То есть нет возможности компилировать даже арифметические операции.

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

Код теста
Размер исходника


Размер компилята

Трансляция кода размером в 50 байт создаёт файл размером 675 килобайт.

Теперь к деталям исполнения этого формата файлов. При исполнении команды node kernel.js asmx-cli tun app ./file.app код будет ТРАНСЛИРОВАН ОБРАТНО в AsmX и затем интерпретирован! Смысл такого формата мне не ясен. Шифровки нет, можно обратно перевести его в исходник командой консоли, ускорения интерпретации тут тоже нет из-за обратной трансляции и затем уже обычной интерпретации, список команд ограничен. Обычный исходный код будет выиграшнее по всем параметрам.

Часть 4. Производительность

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

Методология.

Для измерения времени интерпретации AsmX будет использована встроенная библиотека времени Javascript, измерим время выполнения kernel.js. При таком измерении мы встретимся с проблемой - интерпретация происходит асинхронно.

Придётся немного изменить исходный код kernel.js. Изменение 1 - поднимем класс CompilerAsmX в самый верх файла, прямо после импортов. Изменение 2 - заменим этот код:

Асинхронная интерпретация в методе callCompiler

На:

Ожидание конца интерпретации

Для бенчмарков на Javascript используется та же библиотека, для бенчмарков C++ будем использовать встроенную библиотеку времени std::chrono.

Бенчмарк 1. Сложение:

Javascript, миллион сложений.
AsmX, сто сложений.
C++, миллион сложений

Результаты:

Javascript, миллион сложений.


AsmX, сто сложений.
С++, миллион сложений, компиляция с флагом -Ofast

Asmx в 5905714 раз(4134/7 * 1000000/100, 5.9 миллионов раз) медленнее Javascript и в 898695652173(4134/0.000046 * 1000000/100, 8.9 миллиардов раз) медленнее ВЫСОКОУРОВНЕВОГО языка C++.

Бенчмарк 2. Десять сложений:

Javascript, 10 миллионов сложений
AsmX, тысяча сложений
C++, 10 миллионов сложений

Результаты:

Javascript, 10 миллионов сложений


AsmX, тысяча сложений
C++, 10 миллионов сложений


Asmx в 327492307 раз(425740/13 * 10000000/1000, 327.5 миллионов раз) медленнее Javascript и в 72159322030000(425740/0.000059 * 10000000/1000, 72.2 ТРИЛЛИОНА раз) медленнее ВЫСОКОУРОВНЕВОГО языка C++.

Вывод:

AsmX тотально медленнее как сверхвысокоуровневых, так и высокоуровневых языков. Так же асимптотика времени выполнения AsmX хуже, чем у других: для js и cpp асимптотика меньше, либо равна O(n), а для AsmX - ближе к O(n^2), где n - количество итераций.

Дальнейшие бенчмарки считаю бесполезными, вот вам на последок обещания авторов о скорости AsmX:

Смотрите пункт 7

Часть 5. Post Scriptum

Здесь я хочу объяснить выбор языков и отсутствие классического ассемблера. От самого AsmX ожидалась скорость хотя бы на уровне языка его реализации и продвиутые возможности интерпретатора для оптимизации кода. Именно на проверку этих двух взможностей и был нацелен такой выбор языков для сравнения. Начнём с Javascript, он выбран как бэйзлайн для всех тестов, так как он является бэкенд языком AsmX. С++ выбран, как язык демонстрирующий в данных тестах возможности компилятора по оптимизации, если заглянуть в бинарный код, сгенерированный C++, то можно заметить, что сложения были выполнены компилятором, результат записан в константу и при запуске просто выведен в консоль.

Сравнение со стандартными ассемблерами по скорости считаю непоказательным, так как AsmX на несколько порядков медленнее jit-компилируемого языка Javascript, а сверять возможности компилятора по оптимизации кода просто не с чем, у стандартных ассемблеров полноценного компилятора нет.

Часть 6. Циклы

С новым обновлением было выяснено, что проблема производительности AsmX - в циклах. Без использования условного перехода производительность вырастает вплоть до 100 раз, а асимптотика становится линейной или даже процентов на 10 лучше, чем строго линейная. Это убирает весёлые результаты с 72.2 триллиона раз разницы в производительности(заменяя их на жалкие 300 миллиардов), но пораждает новый вопрос: как можно было так запороть условный переход?

Часть 7. APP v2

Стандарт вышел буквально несколько минут назад и, справедливости ради, он значительно лучше APP v1. Он поддерживает все инструкции, кроме ветвлений (Рискну предположить, что причина в тотально сломанных условных переходах). У него асимптотика чуть лучше линейной(1000 сумм - 300 миллисекунд, 10000 сумм - 2700 миллисекунл), но есть интересный момент: исполнение APP v2 в полтора раза медленнее интерпретации исходного кода:

Интерпретация 1000 сложений
Исполнение 1000 сложений APP v2

Так что смысл этого формата мне так же непонятен, а разница во времени хоть и сильно сократилась(72 триллиона -> 450 миллиардов по сравнению с C++), не дотягивает даже до бэкенд-языка Javascript.

Так же хочу заметить, что в связи с возможностью тестировать APP файлы с миллионами сложений, была найдена утечка памяти. Привожу пример на APP v2 с 1000 сложений:

Выполнение с ограничением памяти в 15 МБ

Прошу заметить замедление на 20%. При выполнении с меньшим размерам выходит ошибка переполнения кучи:

Выполнение с ограничением в 14 МБ

Внимание! Пятнадцать МЕГАБАЙТ памяти на 1000 сложний! посмотрим, как потребление зависит от количества итераций:

На 10000 сложений требуется 100 МБ

Простим 5 мегабайт памяти на работу самого node.js и получим линейную асимптоту памяти! Получается миллион сложений потребует около 10 гигабайт оперативной памяти для работы! Для сравнения миллиону сумм на Javascript нужно не более 2 МБ памяти:

Исполнение старого теста Javascript в пределах 2 МБ без замедления работы

Миллион сумм на C++ же вообще не требует выделения памяти из кучи.

Часть 8. Вывод

Последним обновлением автор значительно улучшил формат APP, однако создал здоровую утечку памяти, всё ещё не догадался писать байты памяти в БИНАРНИК, а не символы текста или то, что выдаёт Javascript'овский buffer.toString(). Циклы сломаны напрочь, интерпретация все ещё самый быстрый способ выполнения, поддерживающий все инструкции. Люблю запах интерпретируемого ассемблера с утечкой памяти и без условных переходов по утрам.


Report Page