Pull to refresh

Delinking и Lisp

Reading time9 min
Views5.7K

Экономический термин delinking впервые (насколько я смог отследить) использовал Самир Амин в работе 1984-го года Delinking: Towards a Polycentric World для обозначения процесса выхода из системы глобального разделения труда. По многочисленными (для нашего немногочисленного Lisp сообщества) просьбам сообщников делюсь своим частным рассуждением о потенциале Lisp-систем в условиях delinking-а с более широкой аудиторией. Это мнение из категории «просто подумалось на досуге», оно не является абсолютно объективной истиной, но, вероятно, может представлять некоторый интерес.

Говорят, что Linux пишут уже 30 лет тысячи программистов по всему миру, поэтому ни одна страна в мире не может повторить Linux локально. Но это утверждение не учитывает, что результатом работы программиста является не конечный продукт, а программа. Конечный продукт, некоторый физический процесс, по программе производит компьютер. В некотором смысле программа подобна чертежу автомобиля. Автомобили производят более 200 лет, меняя подходы, чертежи и конкретные планы производства, но на уровне нынешних знаний и технологий, разработать и произвести автомобиль можно быстрее, чем за 2 столетия. Аналогично, значительная часть труда программистов расходуется на переписывание кода. Программисты кодируют некоторую логику, понимают, что она не подходит или устаревает под давлением новых требований и задач, код переписывают. Знания о том, что и как следует делать, накапливаются.

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

Однако операционную систему необходимо писать на некотором языке программирования. Здесь дела с трудоёмкостью обстоят не так радужно.

Для написания современного системного ПО в основном используют два языка: С и С++. Сейчас к этой паре постепенно добавляют Rust. Из всех этих языков небольшими силами создать и поддерживать абы какой компилятор (впрочем, как и интерпретатор) можно только для C. Создать и поддерживать для любого из этих языков эффективный компилятор - задача весьма трудоёмкая, даже с учётом накопленного опыта в построении компиляторов (не во времена первой версии Fortran живём). В семантике этих языков много крайних случаев, каждый из которых приходится рассматривать отдельно. Кроме семантики, трудоёмкость создания и поддержки могут существенно увеличить выбор как языка программирования, на котором будет написан компилятор, так и архитектурных особенностей этого компилятора. Некоторые языки программирования и архитектурные решения позволяют архитектурным астронавтам удаляться от задачи на третьей космической скорости.

Но ядра операционных систем и их оболочки из базовых утилит не нужны сами по себе. Нам нужен многократно больший объём прикладного ПО. Для написания прикладного ПО используют зоопарк языков программирования высокого уровня: Python, Go, C#, Java, Kotlin, Scala, JavaScript, MATLAB, Haskell, TypeScript, Bash, Lua, Julia, Erlang и другие. Мы объективно в них нуждаемся, потому что для C и C++ есть определённые пределы сложности организации ПО, при выходе за которые трудоёмкость разработки начинает резко расти. Это связано со сложностью управления ресурсами. Самая трудоёмкая часть большинства программ (я говорю об инженерии, оставляя разработку алгоритмов за скобками) - это управление ресурсами, обработка ошибок, взаимодействие с окружением и прочие оргвопросы, обеспечивающие данными основные алгоритмы программы. Поэтому без языков, отчасти автоматизирующих эту работу, при преодолении некоторого барьера сложности не обойтись. Языки высокого уровня позволяют программистам сосредотачиваться на основной логике работы программы. Они снижают пороги входа и позволяют, например, математикам больше внимания уделять математическим моделям, а не техническим деталям работы с памятью или виртуальным множественным наследованием.

В развитие и поддержку некоторых из этих языков программирования вложено огромное количество труда: JVM (некоторые не любят эту систему, но нужно признать, это шедевр программистской мысли) нисколько не проще LLVM, хотя и спроектирована на мой вкус более элегантно. И труда не только программистов. Развитие систем типов Scala, Haskell, Rust - относительно крупные научные проекты, в которых задействовано много исследователей.

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

Часто производство ПО связано с существенными непродуктивными трудозатратами. Бывает, что вдохновлённые абстрактными текстами по теории категорий или о шаблонах проектирования менеджеры или программисты вместо решения задачи занимаются выводом на околосолнечную орбиту изящных архитектур. Проводя занятия по курсу "Операционные системы", я часто сталкивался с ситуацией, когда для решения задачи, которая по задумке полностью укладывалась в 50 строчек кода на C, студенты писали по 500 или даже по 1000 строк кода на C++. Эти студенческие решения безупречно следовали учению о шаблонах проектирования, но зачастую не решали задачу полностью, упуская важные аспекты решения. К сожалению, похожая ситуация наблюдается и в индустриальном ПО, когда разработчики самозабвенно реализуют фабрики фабрик фабрик или ищут монады там, где их нет. Я пишу об этом не с целью оспорить важность теории категорий для практики программирования (сам нередко к ней обращаюсь), но чтобы подчеркнуть, что даже такой уровень издержек с лихвой покрывается размерами и эффективностью системы разделения труда планетарного масштаба, в которую вовлечены программисты.

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

В этот непростой момент капитанам it-индустрии имеет смысл обратить внимание на Lisp-системы. Можно написать отдельный текст с обсуждением особенностей Lisp-систем, которые позволяют преодолевать кризис трудоёмкости. Но уместнее последовать призыву Линуса Торвальдса «Talk is cheap. Show me the code» и привести некоторые числа (замеры сделаны в Arch Linux).

  • Пакет компиляторов C/C++ со всеми вспомогательными инструментами документацией и необходимыми библиотеками, построенный поверх LLVM (clang 13.0.1-2, llvm-libs 13.0.1-2, compiler-rt 13.0.1-1) занимает примерно 322MiB.

  • Пакет GCC с необходимыми библиотеками и документацией (gcc 11.2.0-4, gcc-libs 11.2.0-4) - примерно 261MiB.

  • Пактет компилятора MIT Scheme вместе с текстовым редактором, стандартными библиотеками,системой отладки, системой символьной алгебры и библиотекой численной оптимизации для решения задач механики, документацией и исходными кодами этого всего (mit-scheme 11.2-3, scmutils 20220117) - примерно 143MiB.

  • Пакет компилятора Steel Bank Common Lisp вместе со стандартной библиотекой, отладчиком и исходными кодами (sbcl 2.2.2-1) - примерно 62MiB.

  • Компилятор Chez Scheme (chez-scheme 9.5.6-2) - примерно 4MiB.

Если взять только исполняемый машинный код самих компиляторов, то выяснится, что

  • Сlang (файл /usr/lib/libclang-cpp.so.13) занимает около 51MiB,

  • GCC (файл /usr/lib/gcc/x86_64-pc-linux-gnu/11.2.0/cc1plus) - около 28MiB.

  • MIT Scheme (.com-файлы каталога /usr/lib/mit-scheme-x86-64-11.2/compiler) - около 4MiB,

  • компилятор SBCL сложно отделить от образа всей системы, образ (файл /usr/lib/sbcl/sbcl.core) занимает около 37MiB.

Мне представляется, что это даёт некое представление об уровне сравнительных трудозатрат, требующихся на развитие и поддержку перечисленных нетривиальных систем программирования.

В 80-ых годах it-индустрия c Lisp-машинами была на почти текущем уровне развития ПО: с графическими интерактивными интерфейсами, векторной графикой, гипертекстовыми документами в PostScript, мультимедийными графическими редакторами 3D-графики, системами компьютерной алгебры и инженерии (см. видео).

Hidden text

В том числе, что особенно важно для создания самоподдерживающейся системы, существовали CAD системы для микроэлектроники, написанные на Lisp. В экосистеме Lisp были созданы прообразы современных GPU и разработаны методы их программирования.

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

Кроме этого, порог входа в Lisp-системы для новичков существенно ниже порога входа в C, C++, Rust, Haskell, Java, JavaScript и так далее. Проще разобраться, вероятно, с основами только ассемблера или Forth. А на случай совсем уж полного Апокалипсиса, в библиотеках хранятся бумажные книги о Lisp-системах, по которым даже студенты могут создать с нуля первые версии неплохих интерпретаторов Lisp за пару месяцев, и разработать процессоры (наверное) за пару лет. На этих интерпретаторах можно быстро раскрутить более сложный код.

Таким образом, представляется, что в случае потери связи с глобальной системой разделения труда может быть оправданным более интенсивное применение Lisp-систем (или их аналогов; впрочем, аналоги по уровню документированности, разработанности, простоты, выразительности и продуктивности мне не известны).

В разбушевавшейся кризисной ситуации может возникнуть желание последовать за модными течениями и сделать ставку на продвинутые системы программирования: Rust, Haskell, Scala, Kotlin. Реализация такой стратегии столкнётся с несколькими препятствиями. Во-первых, со сложностью развития и поддержки соответствующих инструментов.

  • Пакет Haskell с необходимым набором инструментов и библиотек (ghc 9.0.2-1, ghc-libs 9.0.2-1, llvm 13.0.1-2, llvm-libs 13.0.1-2) занимает около 779MiB. Без учёта LLVM - около 331MiB. Само ядро компилятора (видимо, файл /usr/lib/ghc-9.0.2/ghc-9.0.2/libHSghc-9.0.2-ghc9.0.2.so) - около 93MiB.

  • Пакет Rust без учёта LLVM (rust 1:1.59.0-1) занимает около 508MiB. Само ядро (видимо, /usr/lib/librustc_driver-7c0e7fab30354592.so) -- около 119MiB.

  • Пакет Scala (scala3 3.1.1-1, jre-openjdk-headless 17.0.3.u3-1) занимает около 203MiB. Без учёта JVM - около 35MiB (следует учитывать, что байткод JVM и формат .jar специально были разработан для как можно более компактного представления программ).

  • Пакет Kotlin без учёта JVM (kotlin 1.6.10-1) - около 73.89MiB.

Во-вторых, работа этих компиляторов и требует существенных аппаратных ресурсов. В то же время, Lisp в режиме относительно эффективной jit-компиляции может работать на процессорах Z80 (см. видео);

Hidden text

PICOBIT Scheme позволяет умещать http-серверы с кооперативной многозадачностью в несколько килобайтов и запускать их на микроконтроллерах; а простейший Lisp влезает даже в загрузочный сектор, позволяя раскручивать из пары сотен байтов полноценную систему.

В-третьих, не смотря на важные для индустрии достоинства более продвинутых языков программирования, они не позволяют быстро работать на широком фронте задач с минимальными затратами труда. Отчасти по тому, что эти системы требуют от программистов высочайшей технической квалификации, что снижает доступность таких инструментов для специалистов в других предметных областях. Видимо, поэтому за 30-лет развития в экосистеме Haskell так и не появились (я отыскать не смог) системы компьютерного проектирования, хотя бы, уровня ICAD, или системы компьютерной алгебры подобные Maxima.

С использованием же различных диалектов Lisp, наоборот, разработали и разрабатывают широкий спектр ПО.

Образ Mezzano Demo 4 содержал пару игр: Doom и Quake - которые сносно работали в виртуальной машине. Любопытно, что это были программы на Common Lisp, код которых был получен транспиляцией оригинальных исполняемых файлов. Это означает, что у современных Lisp-систем всё достаточно хорошо с производительностью.

Микро-тесты показывают, что скорость исполнения кода во многих случаях сопоставима со скоростью исполнения программ на Java. Иногда для этого приходится спускаться на уровень машинных инструкций, но современные Lisp позволяют это делать. Конечно, код, обгоняющий хорошо оптимизированные программы на Fortran, C, Rust или C++, компиляторы Lisp (пока?) генерировать не могут. Однако, как заметил Питер Норвиг, хорошо оптимизированный код на C++ ещё надо суметь написать, и не всегда на это есть время. А быстро написанный код на Lisp зачастую может быть эффективнее аналогичных кодов на C++ или Java.

Подытожить вышесказанное можно так: Lisp позволяет с меньшими трудозатратами разрабатывать сложный код, который может достаточно эффективно исполняться на широком спектре оборудования. Это подтверждается историей и состоянием дел в современном компиляторостроении (компиляторы - сложные программные системы): относительно компактные SBCL и Chez Scheme могут исполнять код с эффективностью уровня более громоздкой JVM. Мой личный опыт промышленного использования Scheme тоже свидетельствует в пользу этого. В эпоху delinking-а это свойство систем семейства Lisp может помочь не уронить продуктивность it-индустрии слишком низко.

Как-то так. Благодарю за внимание.

Tags:
Hubs:
Total votes 19: ↑15 and ↓4+13
Comments47

Articles