Комментарии 59
Chicken не рассматривали?
После обсуждения с коллегой решил включить Python/Cython и MicroPython тесты. Добавлю ответ, когда закончу.
Добавил Python (с примером использования Cython: https://gitlab.com/nuald-grp/embedded-langs-footprint#user-content-using-cython ) и MicroPython. Как я и предполагал, Python достаточно тяжелый (и тестовая программа даже не включала всю стандартную библиотеку, а только базовый необходимый набор).
К моему удивлению, MicroPython очень тяжело встраивать, пришлось использовать различные трюки, чтобы заставить его работать без написания больших Makefile-ов.
Что касается других языков, я не говорил, что они мне не понравились. Везде есть свои плюсы и минусы, но если сравнивать по возможностям при минимальном размере, то Chibi-Scheme в лидерах. Lua + LuaRocks — достаточно интересный вариант тоже, но с другой стороны, он уже включен в проект и все желающие могут сами с ним поиграться (хотя я думаю, все заинтересованные лица уже давно знают про Lua).
Насколько мне известно, в lua можно спокойно все переносы строк заменять на пробелы (с обычным исключением в виде многострочных строковых литералов). Вот ещё и пробелы убрать часто будет нельзя, но сделать программу для удаления тех, каких можно, довольно легко.
А почему, кстати, вы говорите именно про переносы строк? Перенос строки, пробел, точка с запятой или ещё какой разделитель все занимают по одному байту, а вам важно уменьшить число служебных символов в принципе (чему больше мешает do…end
вместо фигурных скобок, чем какие‐то там новые строки), а не конкретно переносов строк.
Да и Forth никак особо на переносы строк не смотрит, а по компактности программы на форте одни из лучших даже без минимизации.
Как я понимаю, в Forth-е нельзя просто удалить новые строки (но, возможно, замена пробелами не сломает программу). Насчет компактности я согласен, но с другой стороны еще есть вопрос читабельности. Т.е. код пишется для человека, но потом минифицируется для машины. Компактный код на Forth-е не всегда будет достаточно читабельным.
И я поддерживаю, что минимификация исходного текста по всем параметрам — скорость исполнения, размер, используемая память, проигрывает байкоду при его наличии, и является неудачным требованием. Которое ко всему прочему еще и сильно ограничило выбор языков.
В форте новые строки точно можно убирать. А читабельность — смотря как писать. Лет пять назад я для развлечения писал решето Эратосфена на разных языках — по одному на каждую букву алфавита. Меньше всего получился скрипт для dc, но я его уже через день не понимал. Второй был форт — и он и сейчас с лёгкостью читается.
Ни до ни после я никогда на форте профессионально не писал, так что большого опыта чтения форта никак нет
Компактный код на Forth-е не всегда будет достаточно читабельным.
Мoжет такое дискуссионное обсуждение, не претендующее на полноту, кому то даст «пищу» для понимания возможностей и «выживаемости» языка в современных реалиях IT :)
Почему обречён язык Форт
Переносы строк — это просто один из примеров, конечно использование do/end тоже может сильно влиять. Более того, замена на пробел != полное удаление. В одном из моих проектов мы делали минификацию встроенного HTML, и простое удаление переносов позволило сэкономить 5% от суммарного размера исходных файлов.
Обновлено, 30% — полная минификация, удаление новых строк дало 5% (HTML был автогенерирован, так что там было достаточно много новых строк).
DOM — это отдельная история, переносы строк на сам рендеринг HTML не влияют (по-крайней мере, наши тестеры и клиенты из Fortune 500 не жаловались, при всем разнообразии поддерживаемых браузеров включая IE6). Естественно, могут быть нюансы, но кросс-браузерная разработка давно заставила всех использовать только безопасное API с правильными селекторами (или просто использовать библиотеки, где это все учитывается).
напомнило старую эпичную тему про "синтаксический оверхед"
Lua не поддерживает минификацию исходных скриптов.
Читаемость/редактируемость минимизированных скриптов при этом отпадает, почему бы тогда сразу в луа-байткод не "компилировать"?
Ну и раз сравнение именно по размеру, ванильная lua собранная TCC+UPX в 64кБ помещается.
QuickJS ещё добавить к сравнению можно было бы.
Кстати, формально Umka вполне удовлетворяет перечисленным требованиям. Любопытно мнение автора, почему он не попал в итоговый список.
Насколько я вижу, Umka чувствителен к новым строкам, их нельзя просто удалить, так что требования к возможности минификации скриптов не удовлетворяются. Насчет предпочтений процедурных vs функциональных — абсолютно нет. Если уж совсем захочется каких-то возможностей, то всегда можно организировать транспиляцию (и тогда возможность ЯП работать с машинно-генерируемым скриптом тоже пригодится, и я это включаю в категорию "дружелюбность к минификации").
Прошу заметить, что я не считаю, что у вашего продукта есть какие-то недостатки. Естественно, у него есть своя ниша и он там занимает достойное положение. Просто немного не подходит под мои требования. Впрочем, т.к. я уже включил Lua и собираюсь включить Python, я могу включить и ваш ЯП, если есть интерес.
Под «чувствителен к новым строкам» я подразумеваю, что если полность удалить переносы строк, код перестанет работать. Замены на пробелы потенциально может работать, но насколько я понимаю, это особенность Lua (признаюсь, про которую я не знал).
Мне всё же не совсем понятно, чем '\n' в данном случае будет принципиально отличаться от того же ';' как разделитель выражений. Какой-то символ-терминатор всё равно будет.
Думаю, лучше привести примеры кода (JS):
function f1() {}
function f2() {}
Можно объединить без разделителей (а ";" в выражениях и так нужна, хотя она и опциональная): function f1() {}function f2() {}
В Scheme/Lisp вообще нет разделителей:
(define a 1)
(define b 1)
Объединяются в одну строку: (define a 1)(define b 1)
Так что все зависит от ЯП.
Если уж искать в Umka препятствия к минимализации, то это будет необходимость указывать типы аргументов и результата функции — очевидная издержка статической типизации. Но тогда и само понятие о минимализации надо расширять и уточнять.
Umka пока не столь уж известен, чтобы я оскорбился его отсутствием в вашем списке :) Однако с первого прочтения я действительно не понял, какому именно требованию он не удовлетворил. И в целом, казалось странным, как эти требования оставили от пары сотен языков всего 8, причём довольно экзотических.
Под какую платформу это компилировалось?
И какая область применения? Для stm32 вроде жирновато, а для апельсинки и питон годится.
Под обычный Linux — некоторые ЯП достаточно экзотичны и имеют весьма скудную документацию. Собственно, некоторые я смог завести (чтобы подключить их в виде библиотеки) только изучив их исходный код, так что поддержка кросс-компиляции — это отдельная история, на которую у меня, к сожалению, нет времени.
Область применения — массовые потребительские устройства (включая бюджетные типа телефонов на KaiOS), микроконтроллеры и SoC у нас есть в планах, но далеких. Т.к. на некоторых устройствах (типа той же KaiOS) нет возможности исполнять нативный код, то одна из поддерживаемых target — это WASM, и засовывать туда 5 мегабайт пайтона сверху (которые надо будет пользователю скачать в условиях ограниченного хранилища и дорогого интернета) — это роскошь.
довольно бестолковое сравнение. На кой ляд учиывать переносы строк, если можно запускать уже скомпилированный байткод? Наприиегр в луа, при рравильном использовании local и переименовании global, это даст не только компактный размер, но и бесплатную обфускацию.
Но это все лирика. Главный минус статьи — отсутствие оценки потребления RAM. А это основное ограничение встраиваемых систем. В тот же STM32F103 луа поместится запросто, (особенно в верию с 1мб флеша), а вот оперативки свободной не будет уже после пары сотен строк реального кода. Почитайте как допиливали белку, чтобы ужать потребление ОЗУ на микроконтроллерах. Там и разделяемые (shared) строки, и жадное создание констант, и перепаковка полей обьектов, целый букет оптимизаций.
Не все встраиваемые ЯП поддерживают байткод. Более того, возможно мы немного по-разному понимаем обфускацию. Для меня это возможность минимизировать и запутать код (вплоть до изменения хода выполнения кода путем вставления и реорганизации вызовов). Байткод не обязательно обфусцированный, хотя конечно, он компактнее исходного кода. luac -l -l -p
дает достаточно читабельное представление байткода Lua, хотя, конечно, я верю, что есть специальные обфускаторы. Однако все это весьма специфично для конкретных ЯП и если есть возможность обфусцировать на уровне исходного кода, это предпочтительнее для нас (но, естественно, может быть неудобно для других и им лучше использовать байткод и сторонние утилиты).
Согласен, что неплохо было бы показать использование RAM. К сожалению, для "Hello world" примера числа будут весьма "бестолковые" тоже, т.к. только специфические конструкции или библиотеки могут вызывать большой расход памяти. Однако какие-то числа лучше чем вообще никакие, так что я добавлю это в тесты (https://gitlab.com/nuald-grp/embedded-langs-footprint/-/issues/4)
Ruby чувствителен к переносам строк, так что его реализации были исключены из списка. Но т.к. я включил Lua и собираюсь включить Python, могу и включить mruby в список, если интересно. Но парсер и компилятор будут включены, т.к. необходимо позволять редактировать скрипты в любое время, при этом не требуя дополнительных инструментов.
ЗЫ: я понимаю, что в нынешних масштабах это несущественно, и реальная задача скорей будет вроде «не больше 2 мегабайт, но максимально удобный нашим юзерам / максимально просто интегрирующийся с нашей системой».
Честно говоря, не ожидал, что для C такое множество решений для встраивания.
Но мне интересно, почему Opject Pascal остался обделённым?
Каким критериям автора он не удовлетворяет?
Не подскажите?
GPL-лицензия, не подходит для коммерческого использования.
libtcc — LGPL
Скриптом легко уронить хоста.
tcc уже несколько лет как фактически заморожен, т.к. его автор больше над ним не работает (https://bellard.org/tcc/):
[Note: I am no longer working on TCC. Check the mailing list to get up to date information.]
Плюс он компилирует в машинный код, что нам не особо подходит (например, не будет работать в WASM VM).
А что на счёт WebAssembly? Рантаймов разных много на любой вкус.
И ЯП под это дело можно выбрать по вкусу. Например https://vlang.io/
Мы поддерживаем компиляцию в WASM, но внедрение его VM — это совершенная другая история. Мы рассматривали это, но не подошло, в частности из-за того, что не нашли подходящую под наши требования VM. Все, что мы нашли, были реализованы:
- как надстройка к node.js -> V8. Слишком тяжелый рантайм, плюс сборка V8 — это отдельное приключение (возможно, Blink собирается проще, но все-равно тяжеловесный);
- на Go (например, https://github.com/perlin-network/life). Go добавляет свой собственный рантайм, плюс может иметь проблемы с биткодом (https://github.com/golang/go/issues/22395 — возможно, это решено сейчас, но зная Apple, они могут это сломать в любой момент)
- на Rust (например, https://github.com/bytecodealliance/wasmtime). Проблем с рантаймом нет, но зато есть с биткодом (https://github.com/rust-lang/rust/issues/35968)
- компилируют на ходу (например, https://github.com/WAVM/WAVM). Включают очень тяжелый LLVM.
К сожалению, мы не нашли реализацию на чистом C/C++, но если вы знаете какую-либо, дайте знать пожалуйста.
Есть один интересный проект: https://github.com/wasm3/wasm3. Это обычный интерпретатор. Некоторые фичи там еще не реализованы, но проект по мне очень перспективный. Однако в прод такое пока еще страшно отпускать.
Спасибо за ссылку, действительно интересный проект. Судя по всему, он удовлетворяет нас по всем требованиям, и есть возможность биндинга нативных функций, пусть даже еще не задокументированная (https://github.com/wasm3/wasm3/pull/71). Насчет прода я не особо переживаю, если проект стоящий, то можно найти ресурсы или спонсоров, чтобы его допилили.
Я постараюсь включить его в тесты, и дам вам знать результаты.
Добавил тест и обновил статью. Документации достаточно мало, плюс нет API для взаимодействия, поэтому написал небольшой менеджер памяти для обмена данных (у меня совсем вылетело из головы, что в WASM-е строк и вообще работы с памятью толком нет, и многие решения основаны на том, что все запускается в JS).
В принципе, качество работы вполне удовлетворительное (правда, C++ биндинги подкачали, но это дело наживное). Будем обсуждать с коллегами, но лично я вижу два главных препятствия для того, чтобы wasm3 стал первичным кандидатом для нас:
- большой расход памяти (проверил valgrind-ом, утечек нет, просто расход большой);
- [не специфично для wasm3] требуется большой рантайм в самом WASM для хорошей функциональности (я попробовал два варианта: 1) no_std дал размер в несколько десятков байт, но весьма скудная функциональность; 2) полноценный Rust добавил сразу большой рантайм, и хотя стало комфортно программировать, WASM-файл занял несколько сотен килобайт ). Я пытался урезать, но толку мало, все-равно достаточно большие файлы без no_std.
Конечно, компилятор Rust не единственный, можно и Emscripten, но он тоже добавляет свой рантайм, а функциональность будет намного хуже чем в Rust. Впрочем, к самому проекту это отношения не имеет, это отдельная история, и можно рассмотреть те же v-lang, nim и т.п. Дополнительно остается, конечно, вопрос производительности, но этим я буду заниматься в рамках других проектов. Еще раз спасибо за ссылку, при всех сомнениях wasm3 все-равно остается хорошим кандидатом для встраивания.
Не совсем понял, как это wasm перпендикулярен? Байт-код — это подмножество скриптов, какая разница это бинарный формат или строковой. Lua-байткод не подходил, потому что это проприетарный формат, а оригинальный код не совсем соответствовал требованиям. Wasm стандартизирован, и имеет богатый тулчайн, вплоть до того, что мы можем спокойно модифировать сам байткод, не беспокоясь, что это сломается завтра.
Какая то двойственность: этот байт код (wasm) нам подходит, а этот — не подходит; этот ЯП подходит, поскольку минимифицируется, а этот не подходит, потому что компилируется в байткод… да просто так захотелось.
Создается впечатление подгонки к определенному результату.
Squirrel достаточно компактный, но достаточно сложно встраивать (любая ошибка в работе со стеком вызовов приводит к краху приложения — у них недостаточно хорошая система обработки ошибок).
Используем в проекте его вот уже больше 10 лет, и я категорически не согласен с приведённым утверждением.
Но некоторые ошибки иногда бывает сложно диагностировать.
И, кстати, на Squirrel-е можно написать вот так:
fn <- @() "Hello, "+read();
Тесты скорости бы хотелось ещё увидеть
Для сравнения скорости необходимо намного больше трудозатрат для проекта, и это не входило в рамки оригинального PoC. Но могу сразу сказать, что Lua достаточно хорош - он и компактный и достаточно быстрый. Но если скорость - это первичный критерий, то надо смотреть на другие бенчмарки (например, после того как Google вбухал огромный деньги в JS, он стал одним из самых быстрых встраиваемых ЯП, но при этом он и весьма увесистый).
Сравнение встраиваемых ЯП по размеру в исполняемом файле