Кризис современных языков. Как абстракции, «наслаиваясь» с 1972 года, убивали производительность

30 июня 2018, 09:00

Брент Бессемер написал статью о кризисе современных языков программирования. dev.by приводит её перевод.

Читать далее...

Когда-то давным-давно не существовало понятия «язык программирования». Существовало только «программирование голого железа» (bare metal programming) и шутка на xkcd о «магнитной игле и твёрдой руке» — очень близкая к истине. Разве что магнитный жёсткий диск по-прежнему оставался достаточно непрактичным устройством, а преимущественным носителем информации были перфокарты.

Taken By Me Photography, Flickr

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

Бесспорно, такой вид программирования был невероятно трудным, нудным и не защищённым от ошибок.

Языки ассемблера на основе текста появились уже во времена перфокарт, дав программистам что-то по крайней мере читаемое для человека (в самом общем смысле слова «читаемый», да и слова «человек» — тоже), но соответствие между программой и аппаратной платформой было практически 1:1.

Настоящим прорывом стало появление языков высокого уровня: Fortran в 1957 году, ALGOL в 1958, BASIC в 1964, Pascal в 1968 и наконец Си в 1972 году, — причём с появлением последнего в программировании произошли поистине коренные изменения.

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

Секрет успеха Си больше, чем просто удача. Он состоит в том, что создавался язык с целью написания операционной системы — задача, которая раньше поручалась языку ассемблера. Поэтому Си с его небольшим набором элементарных конструкций предоставлял возможность слаженно взаимодействовать с аппаратной основой на довольно примитивном уровне (обеспечивая плавный перевод c ассемблерного кода и на него), а также создавать абстракции почти произвольной сложности. Мне неизвестно их количество и как его оценить в принципе, но рискну предположить, что 99,999 процента всего кода, работающего на всех компьютерах в мире —

  • либо на Си,
  • либо на языках, произошедших от Си, и его надмножествах, например, С++,
  • либо интерпретируется виртуальной машиной, которая подпадает под одну из первых двух категорий.

Си — потрясающе эффективный язык, но и он в конце концов начал считаться трудным, нудным и не защищённым от ошибок, и над ним начали появляться абстракции.

С++, разработанный в 1985 году, был одним из первых, но в определённом смысле — самых простых: по сути, С++ — это надмножество Си, дающее программисту свободу использовать объектно-ориентированные конструкции. С++ компилируется в тот же самый машинный код, что и Си, и может (по крайней мере в теории) достаточно просто скомпилироваться в Си, что первым компиляторам С++ и было под силу. Но С++ больше не единственный язык, устроенный таким образом: в последние годы возрождается популярность категории (2), включающей более высокоуровневые, чем Си, языки системного программирования. В основном это происходит из-за бесконтрольного раздувания стандарта С++, а также растущего признания проблемы, о которой я расскажу ниже.

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

Но что касается обычных потребительских приложений (не игр — хотя…), современные устройства настолько мощны, что оглядываться на производительность приходится лишь изредка. Разработка приложений подпитывается маркетингом и ленью, а повышенной производительности, о которой свидетельствуют происходящие изменения, в большинстве случаев недостаточно для укрепления на рынке.

На деле результатом стал взрывной и (с чем можно поспорить) неустойчивый рост категории (3) — интерпретируемых языков. Если забыть о BASIC, начало ему в 1995 году положил Java. Идея была проста и разумна: интерпретация (в данном случае байткода) не должна зависеть от ОС и аппаратной основы, благодаря чему разработчики смогут поставлять один «двоичный код» всем своим клиентам. Абстрагирование от ОС в дальнейшем улучшило безопасность, хотя и не настолько, чтобы можно было свободно исполнять сомнительный код по сети.

myfear, Flickr

Но на другой стороне медали производительность: в 1995 она была гораздо более существенной проблемой, чем сегодня, но до сих пор остаётся досадным минусом для тех, кто имел несчастье, например, познакомиться с Eclipse.

(По той же причине инструменты командной строки на Java даже хуже: скорость виртуальной машины Java в процессе весьма неплоха, но для её запуска требуется масса времени.)

Один нюанс Java в том, что он почти ничем не превосходит С++ в плане абстракции. Он фактически независим от платформы, но с появлением современных кросс-платформенных библиотек и инструментов DevOps, отсутствие этого свойства у С++ практически нивелируется. Разрабатывать на Java не проще, управление памятью на нём ненамного больше автоматизировано (куда делись динамически растущие массивы?), типизация, пожалуй, лучше, чем в Си (в Java можно получить сообщение об ошибке, если попытаться преобразовать класс к самому себе), и ещё в Java есть эти жуткие нулевые указатели — единственная самая ненадёжная в отношении ошибок деталь из всех, что есть в Си. Безопасность едва ли лучше той, которой обладала Unix где-то за 20 лет до того, как мир узнал о «байткоде», и абсолютно недостаточна с учётом того, как люди исполняют код сегодня. (То есть ненадёжный, непроверенный код и часто удалённо, по всей сети.)

Однако в результате упадка Java не вернулись к прежним, нативным стандартам, а перешли на ещё более высокий уровень абстракции.

JavaScript, абсолютно не связанный с Java, тоже появился в 1995 году, но почти полтора десятилетия томился в безызвестности, пока Apple не отказалась от установки Flash на iPhone, чем активировала разработку новых стандартов web 2.0, не требующих дополнительной установки.

Современноевеб-приложение в некотором смысле решало все проблемы Java и даже больше: полностью кросс-платформенное, предоставляется автоматически, ничего не меняет в системе пользователя, даже если бы он и захотел. Кроме того, такие приложения проще разрабатывать. В JavaScript тяжелее ошибиться (хотя писать правильно намного сложнее, но это уже никого не волнует), а также он гораздо больше подходит для проектирования (и особенно тематического оформления) UI на языке разметки, стоящего особняком от «настоящего» программирования.

Сегодня в вебе огромную роль играют Chrome OS and Electron, а серверы на Node используют JavaScript. Нравится нам это или нет (а есть множество аргументов в пользу обоих мнений), но именно в этом направлении развивается современная разработка приложений. Только веб, всегда. Здесь можно задать вполне уместный вопрос: если у вас есть коммерчески жизнеспособная ОС с единственным приложением — браузером, то это минималистичная ОС или непомерно раздутый браузер?

Называть Chrome операционной системой не будет преувеличением. По количеству строк кода он почти сравним с ядром Linux. Как и у ядра Linux, у него есть API для самых разных аппаратных основ, включая OpenGL, VR, MIDI. В него интегрированы SQLite, менеджер памяти и собственный диспетчер задач. На macOS у него есть даже драйвер USB для контроллеров Xbox 360. (Я знаю это, потому что сам его написал.) В Slack, например, нет моего кода для контроллеров Xbox.

На самом деле, как операционная система Chrome может быть даже лучше, чем большинство дистрибутивов Linux — по крайней мере, он проще для разработчиков. В нём нет проблем с унаследованным мусором и всяких фишек, которые так раздражают в API большинства ОС. Эти API даже построены на стандарте, и потребители могут свободно выбирать любой браузер, не создавая лишней работы разработчикам. Веб — просто лучшая из всех имеющихся платформ разработки для потребительских приложений, поэтому все переходят на неё.

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

Не то чтобы этого никто не сделал. Самый известный и, возможно, самый быстрый движок JavaScript сегодня — это V8 от Google, движок, который является важным компонентом Chrome (а также Node и Electron). Его название, несомненно, должно ассоциироваться с мощным автомобильным двигателем, но если бы V8 был автомобильным двигателем, то не был бы V8. Если отталкиваться от сложности и затрат на обслуживание, ему больше подошло бы название «W16» — как у 8-литрового с четырьмя турбинами двигателя Bugatti Veyron, в котором замена масла обходится в $21 тысячу. Возможно, использовать V8 Google не совсем то же самое, что разъезжать на гиперкаре стоимостью $1,7 млн, но расход топлива (оперативной памяти) будет сопоставим. Но в данном случае вам хотя бы не вы будете менять масло.

Но если бы топливо (и Bugatti) были бесплатны, мы вполне могли бы стали пользоваться ими для повседневных поездок.

По сути, в такую ситуацию и поставил нас закон Мура: если 10-процентый прирост производительности или снижение расхода ресурсов (а это нелёгкое дело для крупного проекта) достигается за три месяца, оптимальным решением будет отказаться от оптимизации и просто расширять программный стек. Закон Мура пока что спасал нас от последствий, показанных на этом графике (количество строк в исходном коде ядра Linux):

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

Но закон Мура не может выполняться вечно, и не в нём дело.

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

И когда этот момент настанет (предсказывают, что это произойдёт уже в 2021 году), то бурный рост кодовой базы действительно станет опасным. Кроме того, закон Мура позволил не только ускорить, но и удешевить компьютеры. У людей, которые прежде никогда не могли бы позволить себе домашний компьютер, теперь есть как минимум смартфон, и ассортимент предлагаемых сегодня устройств широк, как никогда. Вместе эти два детали и знаменуют надвигающийся кризис: концепция «запланированного устаревания» товаров вскоре рухнет.

Что в это время будет с вами?

Веб-разработчики не будут к этому готовы, потому что общий курс веб-разработки, кроме несколько обнадёживающих исключений, — не на упрощение кода или уменьшение уровней абстракции. Напротив, многие веб-приложения (если не большинство) сегодня строятся в гигантских фреймворках, из-за чего появляется ещё больше уровней абстракции и сложности поверх 4 турбин вашего W16. Люди хотят строить приложения за полчаса, и в этом им помогают фреймворки. Но даже если отбросить минусы, связанные с производительностью, идёт ли это на пользу разработке программного обеспечения?

Не сказать, чтобы вопрос с производительностью был решён.

Использование CPU уже не (самая большая) проблема, потому что на вебе код необходимо скачивать при каждом его запуске.  В приложении на React должно быть 79 905 строк на JavaScript, прежде чем разработчик сможет начать писать код. Пусть React и не самый лучший пример: это продукт Facebook, и всем известно о её проблемах в этой сфере, но он невероятно популярен, поэтому появилась уйма приложений, которые скачивают 80 тысяч строк кода при каждом запуске.

(Шокирующая статистика из этой статьи — что в приложении Facebook под iOS содержится 18 тысяч классов Objective-C — объясняется принципом работы React, но это уже отдельная тема).

Возможно, есть фреймворки получше, но насколько они лучше? Даже jQuery весит 86 килобайт: крошечный по сегодняшним меркам, он всё равно зачастую тяжелее кода, который его использует.

Говоря о массивности некоторых из этих фреймворков, нужно иметь в виду, что причиной тому — именно JavaScript, выполняемый в виртуальной машине, которая в свою очередь занимает миллионы строк в операционной системе размером в десятки миллионов строк. Не все они должны исполняться для каждой отдельной строки JavaScript — многие могут вообще никогда не исполняться, но и та небольшая доля, что исполняется, всё равно составляет порядочное количество кода. Сколько займёт на Си 80-тысячый код на JavaScript? И сколько строк займёт код достойного аналога React.js с простым языком разметки UI и API на С++? Может и больше 80 тысяч.

(Qt, который может всё, что может React, и много чего ещё, и едва ли отличается простотой, занимает около 470 тысяч строк на С++ — это почти в 6 раз больше 80 тысяч, но в 4 раза меньше размера V8 и в 20 раз — Chrome.)

Но жребий брошен. Было бы здорово, если бы приложения опять начали разрабатывать на низкоуровневых языках, однако этого не случится. А вместо этого, возможно, есть способ сделать шаг назад и отступить от угрозы экспоненциального взрыва бесполезного кода, не оказавшись в 90-х или даже в 2010-х.

Первым делом нужно устранить наихудшее, что есть в культуре веб-фреймворков. Если честно, возможность строить приложения моментально или даже бы за 24 часа — бесполезная вещь, от которой больше вреда, чем пользы. Вряд ли специалисты Google могут быть настолько самоуверенными, чтобы думать, что смогут построить настоящий полноценный продукт за полчаса, но и демонстрировать проект полному залу разработчиков тоже кажется безответственным — вдруг будут шпионить сотрудники Facebook. Когда технологические гиганты открывают все свои изысканные кастомные решения — это одно, и совсем другое — предоставлять их в готовом виде всем подряд студентам с недалёкой задумкой приложения, которые думают лишь о деньгах.

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

Далее, поставщикам ОС и браузеров нужно усовершенствовать свои API. Например, jQuery, по сути, стал стандартом в современном вебе — его API далеко превосходят слабенькие стандартные интерфейсы DOM. Очевидно, создателям браузеров нужно просто встроить в них (по крайней мере ключевое подмножество) jQuery — так этот достаточно частый код можно будет использовать вместо JavaScript и избавиться от загрузки фактически миллиардов страниц по 86 килобайт каждая. То же самое касается и сторонних библиотек на десктопе: нет примера более наглядного, чем jQuery, но они определённо есть, возможно, SDL. (По сути так стандартизаторы С++ отреагировали на Boost, и теперь селектор $(...) доступен в большинстве браузеров без jQuery, так что это только начало.)

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

WebAssembly наконец стал прямо поддерживаемой альтернативой JavaScript в браузере, далее переход на WebAssembly должен стать главной и в конечном итоге единственной целью. Чтобы сделать это с минимальными потерями, нужно найти способ скомпилировать существующий JavaScript в WASM, а это непросто — вспомните проблему с W16 выше. И всё же существующие JS-движки компилируют JavaScript, поэтому не составит особой трудности делать дамп кода на диске (наверно, в виде байткода LLVM) вместо того, чтобы сразу же запускать его. AssemblyScript уже может компилировать TypeScript (расширение JavaScript от Microsoft с возможностью статического назначения типов) в WebAssembly, так что и это скоро станет осуществимо. Но помимо этого интерфейсы WebAssembly-JavaScript наверняка понадобится полностью перепроектировать, так как WASM на данный момент слишком изолирован, и поэтому не так полезен.

Наконец, нужно убрать всё лишнее между аппаратной основой и непосредственно написанием ПО. В знаменитом выступлении «Рождение и смерть JavaScript» Гари Бернар не без иронии предлагал заменить ядро виртуальной машиной WebAssembly, и некоторые этой идеей решили заняться всерьёз, хотя это и заходит уже слишком далеко. Зато вполне реально было бы заменить менеджер окон и оболочку рабочего стола полноэкранным, невидимым окном браузера с виртуальным кадровым буфером и/или контекстами GL для унаследованных приложений и игр. Это может показаться ещё затратнее, но на деле — наоборот: новый браузер станет встроенным фреймом, как и все приложения на Electron. (Фактически, сама по себе оболочка Electron была бы хорошим началом для подобного проекта.) Больше не понадобится запускать отдельный инструментарий для рендеринга для каждого приложения. Всё пишется с опорой на стандарт и рендерится одним стандартным рендером, и потребление оперативной памяти падает вдвое. Общим стандартом не обязательно должно стать что-то, уже сегодня используемое в вебе, хотя есть все основания, чтобы это было так, и очень мало — чтобы это было не так.

Мир, который я изобразил, не идеален. Я не удивлюсь, если он испугает многих разработчиков на С++. Он даже меня пугает (а я разрабатываю на С/С++). Но меня он пугает гораздо меньше, чем тот мир, который есть сейчас, где ПО похоже на луковицу с наросшими на 1972 год слоями.

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

Разработке нужно не более и не менее того, что было в 1972 году: система, в которой разработчики могут строить приложения с использованием простых API, настолько близко к аппаратной платформе, насколько это позволяет безопасность.

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

Хотя этого можно было избежать. И надеюсь, ещё не поздно исправить.

подписка на главные новости 
недели != спам
# ит-новости
# анонсы событий
# вакансии
Обсуждение