Хабрахабр

[Перевод] День смерти стандартной библиотеки

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

Что такое ABI

ABI — набор соглашений, определяющих, как ваша программа представляется в бинарном виде, как в ней объявляются имена, задается разметка у классов и определяются соглашения о вызове функций. По сути, это своеобразный двоичный протокол, однако он не является версионным.
Чтобы не запутывать вас в терминологии, давайте рассмотрим на примерах, что влечет за собой изменение ABI и его поломка в рамках программы:

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

  • Добавили новое поле в уже существующий класс
  • Поменяли шаблонные параметры у класса/функции, превратили шаблонную функцию в нешаблонную или наоборот, добавили variadic template
  • Применили спецификатор inline к чему-либо
  • Добавили параметры по умолчанию в объявлении функции
  • Объявили новый виртуальный метод

И многое, многое другое, однако примеры выше — основные, что упоминаются комитетом, и те самые, благодаря которым большинство предложений в стандарт уничтожают на месте. Я опустил варианты нарушения ABI, в ходе которых ломается и исходный код программы (к примеру, удаление или изменение функций). Однако, и это не всегда верно. К примеру, удаление функции не всегда влечет за собой поломку исходного кода. std::string имеет оператор преобразования в std::string_view, от которого я бы с радостью избавился, и хоть его удаление и сломает ABI, но не приведет к значительным проблемам в исходном коде.

Почему мы должны сломать ABI

Прежде всего, есть несколько полезных изменений в реализации стандартной библиотеки, которые можно внедрить, если нарушить текущий ABI:

  • Сделать ассоциативные контейнеры (намного) быстрее
  • Ускорить работу std::regex (На данный момент быстрее запустить PHP и выполнить на нем поиск по регулярному выражению, чем использовать стандартный std::regex)
  • Некоторые изменения в std::string, std::vector, а также в разметке других контейнеров
  • Единство интерфейса классов: на данный момент некоторые их реализации намеренно не соответствуют единому интерфейсу в угоду стабильности ABI

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

  • std::scoped_lock был добавлен для того, чтобы не сломать ABI изменением lock_guard
  • int128_t не может быть стандартизирован, потому что меняет intmax_t. Однако, если бы спросили меня, я бы просто сказал, что intmax_t должен стать deprecated
  • std::unique_ptr мог бы храниться в регистре с помощью некоторых модификаций в языке, что сделало бы его zero-overhead по сравнению с обычным указателем
  • Большинство изменений для error_code были отклонены как раз из-за того, что они влекли за собой поломку ABI
  • status_code также вызывает проблемы с ABI
  • Предложение добавить фильтр в recursive_directory_iterator было отклонено, потому что сломало бы ABI
  • Предложение сделать большинство функций из библиотеки <cstring> constexpr (включая strlen) скорее всего тоже будет отклонено, ведь ломает ABI
  • Добавление поддержки UTF-8 в std::regex — поломка ABI
  • добавление поддержки в realloc и возврат размера аллоцированной памяти является поломкой ABI для полиморфных аллокаторов
  • Создание неявных виртуальных деструкторов для полиморфных типов
  • возвращаемый тип у push_back может быть изменен, если сломать текущий ABI
  • А вообще, действительно ли нам нужен и push_back, и emplace_back?
  • Улучшение std::shared_ptr также влечет за собой поломку ABI
  • [[no_unique_address]] мог бы выводиться компилятором, если бы мы не заботились о сохранении текущего ABI

И на этом список не заканчивается. Я думаю WG21 стоит стараться поддерживать актуальным список таких вот вещей. А я же буду брать на заметку каждого, кто произносит «это сломает ABI», находясь со мной в одной комнате.

Что еще мы хотим изменить?

Я не знаю. И я не знаю того, чего не знаю. Но если бы меня попросили угадать, я бы сказал следующее:

  • Во время разработки модулей для C++23 мы снова столкнемся с проблемами ABI, потому что все не-экспортируемые символы должны будут оставаться в глобальной части модуля, чтобы не сломать ABI. Этот факт в принципе уничтожает весь смысл модулей как таковых
  • Существует очень много людей, которые уверены, что стоимость исключений может быть значительно снижена в тех местах, где хромает качество реализации, однако все это опять же скорее всего потребует сломать ABI
  • Дальнейшие улучшения корутин могут привести к проблемам с ABI, и их как раз можно существенно улучшить
  • Перемещение объектов требует явного оператора, опять же по причинам ABI
  • Предложения Tombstone точно приведут к проблемам с ABI

Обсуждение ABI в Праге

В течение прошедшего обсуждения ABI, в Праге был проведен ряд опросов, которые, однако, не говорят нам ни о чем. В зависимости от того, пессимист вы или оптимист, текущие результаты могут быть интерпретированы вами по-разному.

Основные факты:

  • WG21 не желает ломать ABI в 23 году
  • WG21 считает нужным сломать ABI в следующих версиях C++ (С++26 или далее)
  • WG21 потребуется время на рассмотрение предложений, которые нарушают ABI
  • WG21 не обещает вечную стабильность
  • WG21 считает важным сохранить приоритет на производительность, даже в ущерб стабильности языка

В этих заявлениях есть много важных моментов, но нет единого мнения. Комитет, как ни странно, разделился пополам.

Гадание по кофейной гуще

В какой версии C++ ждать изменений

Очевидный недостаток всех этих опросов заключается в том, что мы так и не определились, когда именно мы хотим сломать ABI.

в C++23? Нет, уже точно нет.

в C++26? Часть людей намерены голосовать за, но другая часть скорее всего проголосует за C++41 или за то время, когда они уйдут на пенсию и им не придется поддерживать свой текущий проект. Один из вопросов просто упоминал C++-какой-то; очень удобно!

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

Я не думаю, что в опросе, проведенном через три года что-то изменится. Это как глобальное потепление: все согласны, что нужно когда-нибудь заняться этой проблемой. А давайте тогда запретим дизельное топливо в 2070?

Все, что не планируется сделать в ближайшие пять лет, скорее всего никогда не произойдет.

О предложениях, которые нарушают ABI

WG21 проголосовало за то, чтобы уделить больше времени тем предложениям, что нарушают текущий ABI. Это означает несколько вещей:

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

Производительность важнее стабильности ABI

Это все равно, что спросить, хочет ли пятилетний ребенок конфету. Конечно мы проголосуем за важность производительности. Однако, меня настораживает, что часть людей все же проголосовала против.

Мне кажется, комитет одновременно хочет усидеть сразу на двух стульях. А это невозможно:

Bryce Adelstein Lelbach @blebach
— Производительность
— Стабильность ABI
— Возможность что-либо менять

Выберите два варианта из предложенных.

стабильность языка и ABI в конечно счете сталкиваются друг с другом, заставляя нас задаться таким фундаментальным вопросом — Чем является C++ и чем является его стандартная библиотека?

Обычно в таком случае вспоминают термины «производительность», "zero-cost абстракции", "do not pay for what you do not use". А стабильность ABI стоит поперек всему этому.

Далеко идущие последствия

image

Я глубоко убежден, что решение не ломать ABI в 23-м году — это самая большая ошибка, которую когда-либо делал комитет. И я знаю, что некоторые люди верят в обратное. Но вот что скорее всего случится в скором времени:

Кошмар обучения

Давайте будем честны. Все программы, которые полагаются на ABI, скорее всего где-то нарушают принципы ODR или используют несовместимые флаги, которые по счастливому стечению обстоятельств все еще работают.

Новые программы должны собираться из исходного кода, нам нужны инструменты, построенные на сборке из исходников, а не коллекция библиотек, которые откуда-то достали и как-то вставили в проект.

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

Отказываясь нарушать ABI, комитет открыто заявляет, что будет поддерживать плохо написанные программы на протяжении всего их существования. Даже если вы не будете линковаться с библиотеками, полученными через apt-install (которые на самом деле предназначены для системы), то другие люди будут, ведь комитет дал им на это свое благословение.

Это огромный шаг назад. Как мы можем учить других хорошим практикам языка, если для этого у нас нет никакого стимула?

Потеря интереса к стандартной библиотеке

Предполагаемая потеря в производительности библиотеки из-за нашего нежелания нарушать ABI оценивается в 5-10%. Это число будет только расти со временем. Давайте посмотрим на примерах:

  • Если вы — большая компания, то можете купить себе новый датацентр или платить команде программистов, которые бы поддерживали свою собственную библиотеку
  • Если вы разработчик встраиваемых систем, то эти 5% могут стать гранью между работающим продуктом и необходимостью купить более дорогой и мощный чип, а он может стоить миллионы
  • Если вы игровая компания, то эти 5-10% могут решить, станет ли ваша игра крутой или будет ли ваших пользователей тошнить от нее в VR-шлеме
  • Если вы трейдинговая компания, то скорее всего для вас эти проценты — успешно проведенная или проваленная транзакция

Я думаю, тут волей-неволей встает вопрос между: «Мне однозначно стоит использовать C++ и его стандартную библиотеку!» и «Быть может, мне не стоит использовать стандартную библиотеку? Или может, мне не стоит использовать C++ в принципе? Возможно, .NET, julia или Rust будут лучшим решением?». Конечно, есть много факторов, которые влияют на ответ, но мы видим, что происходит в последнее время.

Множество разработчиков игр относятся крайне скептически к стандартной библиотеке. Они скорее разработают свою альтернативу, к примеру EASTL, чем воспользуются STL. У Facebook есть folly, у Google — abseil и так далее.

Это как снежный ком. Если люди не используют стандартную библиотеку, у них нет и интереса ее улучшать. Производительность — это тот ключевой фактор, который удерживает библиотеку на плаву. Без гарантии производительности намного меньше усилий будет приложено в ее развитие.

>> Jonathan Müller @foonathan
В чем смысл использовать контейнеры из стандартной библиотеки, если они не предоставляют лучшую производительность?

Titus Winters @TitusWinters
Возможно, потому что они распространены и легкодоступны? (эти два факта не значат одно и то же).
Голосовать за сохранение ABI это все равно что сказать, что стандартная библиотека должна стремиться быть МакДональдсом — он также есть повсюду, он стабильный и технически решает поставленные задачи.

Как комитету рассматривать предложения, ломающие ABI?

Несколько вариантов предлагается в качестве облегчения боли, вызванной невозможностью принимать предложения, если те нарушают ABI:

Добавление новых имен

image

Это первое и очевидное решение. Если мы не можем поменять std::unordered_map, может нам просто добавить std::fast_map? Есть несколько причин, почему так делать плохо. Добавление типов в стандартную библиотеку обходится дорого как с точки зрения расходов на поддержку, так и с точки зрения образования. После введения нового класса неизбежно появятся тысячи статей, в которых объясняется, какой контейнер стоит использовать. К примеру, мне следует использовать std::scoped_lock или std::lock_guard? А я понятия не имею! Мне нужно каждый раз гуглить. Также есть проблема, что хорошие имена рано или поздно заканчиваются. Еще мы получаем некоторый оверхед во время выполнения программы, так как все контейнеры должны постоянно преобразовываться друг в друга, огромное число перегрузок конвертации у класса становится трудно контролировать и т.п.

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

Но ведь мы могли принять это предложение в стандарт!

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

Мне, как циничному человеку, немного трудно в это поверить. Дело в том, что раньше таких предложений не было, да и сценарии, в которых они могут быть применены весьма ограничены. ABI Review Group (ARG) могла бы помочь в этом вопросе, но они скорее всего снова порекомендуют придумать другое имя для класса / функции.

Частичное нарушение ABI

Основная идея заключается в том, чтобы не ломать весь ABI, а только изменить его для определенного класса или функции. Проблема такого подхода заключается в том, что вместо ошибки на этапе линковки, мы будем видеть проблему уже во время запуска программы, и это будет неприятно нас удивлять. Комитет уже попробовал такой подход в С++11, когда поменял разметку std::string. Ничего хорошего из этого не вышло. Все было настолько плохо, что этот факт до сих пор используется как аргумент в пользу сохранения текущего ABI.

Еще один уровень индирекции

Решением части проблем с ABI было бы возможность обращаться к данным класса через указатель, тогда бы и разметкой класса был всего лишь этот указатель. Идея очень близка к идиоме PIMPL, которая активно используется в Qt из-за его ABI. Да, это бы решило проблему с членами классов, но что делать с виртуальными методами?

Рассматривая проблему с более критической точки зрения, мы говорим о добавлении еще одного уровня косвенности (индирект по указателю) и дополнительной аллокации памяти в куче для всего того, что заключено в рамках ABI. В STL, по факту, все заключено в этих рамках, потому что это набор обобщенных классов.

В итоге цена такого подхода будет огромной.

В качестве решения данной проблемы уже есть несколько предложений в стандарт. Часть из них хотят сделать PIMPL одной из фич языка, чтобы вы могли выбрать между стабильностью ABI и высокой производительностью.

Иронично, однако, но чтобы превратить библиотечные типы в PIPML-типы, нам нужно… сломать ABI.

Пересобирать весь код каждые три года

Просто мои мысли вслух.

Все текущие предложения в стандарт должны быть уничтожены

Парадоксально, но C++ никогда не был настолько живым, как сейчас. В Праге 250 человек работали над множеством вещей для него, среди которых:

  • Numerics
  • Линейная алгебра
  • Аудио
  • Юникод
  • Асинхронный I/O
  • 2D и 3D Графика
  • Множество других фич

Все эти предложения объединяет один общий факт — они намного опциональней по сравнению с тем, что мы имеем в стандарте на данный момент. Люди просто пытаются стандартизировать вещи из своей области исследования и работы, или то, что постоянно развивается и меняется.
В частности, алгоритмы работы с Unicode являются крайне нестабильными и быстро меняются с течением времени.

А потом, над горизонтом нависает такой ужас, как networking. Очень и очень безответственно пытаться стандартизировать что-либо, что может повлечь за собой проблемы безопасности, и при этом не иметь возможности это впоследствии изменить (вспоминаем про ABI).

Поскольку C++ решили сделать стабильным, все эти предложения должны быть уничтожены и сожжены. Я бы не хотел, чтобы их уничтожали, но это необходимо сделать.

Но этого все равно не сделают.

В лучшем случае, мы не наделаем ошибок и стандартизируем текущее состояние вещей в одной из новых версий C++, а затем позволим всему медленно разлагаться, так как починить это уже не будет возможности (В случае с Networking TS мы похоже не сможем вообще ничего изменить, поэтому нам придется стандартизировать то, что существовало десять лет назад. Тогда библиотеку конечно еще можно будет значительно улучшить, но давайте оставим эту историю на другой раз).

Но конечно, мы совершим еще много, много ошибок.

>> Ólafur Waage @olafurw
(Это очень хорошо сработало в прошлый раз, поэтому я попробую снова)

День добрый, твиттер программистов!

Я собираюсь выступать перед амбициозными молодыми людьми через пару недель. Какой по вашему мнению самый важный совет, который можно дать разработчику ПО (к примеру: паттерны, архитектура, алгоритмы)?

Hyrum Wright @hyrumwright
Рано или поздно любое решение, принятое во время разработки, придется изменить. Предусматривайте возможность изменения в вашей экосистеме — как в ваших инструментах, так и в самом процессе разработки.

Некоторые ошибки делаются намеренно, так как являются трейд-оффом, тогда как другие остаются незамеченными многие годы.

Время идет, но стандартная библиотека стоит на месте. Ранее сделанные трейд-оффы постепенно начинают нам мешать, а в последствие становятся настоящими «бутылочными горлышками» в существующем коде.

Часть вещей действительно невозможно изменить, так как они вшиты в API. У нас у всех есть представление о том, насколько сложно менять существующий API. Но часть кода все же может быть исправлена и улучшена, если бы мы могли сломать ABI.

C++ все еще будет на плаву в ближайшие 40 лет. Если мы не сможем осознать необходимость менять его непредсказуемым образом в любое время, то единственным верным ходом останется только не играть в эту игру в принципе.

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

Ваше предложение в стандарт будет уничтожено, мое точно также будет уничтожено.

Может ли комитет в принципе сломать ABI?

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

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

Много людей полагаются на ABI совершенно случайно, не сделав осознанный выбор. Много людей также полагаются на стабильность, потому что, конечно, все хотят на нее полагаться. Но как и у любой другой вещи, у стабильности есть своя цена, и сейчас вся экосистема C++ за нее платит.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»