Перевод публикации Тита Винтерса в рабочей группе 21 (WG21) — комитета по стандартизации языка C++. Автор обсуждает важный вопрос: поддержку обратной бинарной совместимости или ABI (application binary interface).
В течение последних лет в WG21 я активно пропагандировал то, что прогресс важнее обратной совместимости. Но я сам перестаю в это верить, особенно в отношении поддержания бинарной совместимости (ABI). В трех последних релизах (C++14, C++17 и C++20) ABI было настолько стабильно, насколько нам это удавалось. Даже если WG21 решит нарушить обратную совместимость ABI в C++23, мы обеспечивали бинарную совместимость на многих платформах более 10 лет. По моему мнению, в масштабных переделках программных систем доминирует закон Хайрума. Сейчас и не скажешь, у скольких пользователей предположение о стабильности ABI стандартной библиотеки (неважно, сколь мудрое или сколь явное или неявное) твердо «прошито в подкорке», возможно у половины разработчиков на C++ в мире.
Я веду список того, что WG21 могла бы исправить в языке, если мы решим «поломать» ABI. И я не могу уверенно утверждать, что общая стоимость переделок, которое повлечет выполнение только этого списка сравнима с тем, в какую цену обойдется нарушение ABI всей экосистеме. Мы видели много мелких улучшений в консистентности API, качестве кода стандартной библиотеки и т.д., но без сомнения нет ни одного такого «прорывного» изменения, которое оправдало бы эту стоимость для рядового разработчика. Возможно, мы бы получили лучшее соответствие реализаций стандарту, дали бы шанс решить проблемы реализациям, сегодня не выполняющим спецификации стандарта. Но ни одно улучшение из моего списка явно не стоит таких затрат.
Что важнее, из-за ограничений ABI мы не можем устранить существенные потери производительности. Мы не можем избавиться от существенной стоимости передачи unique_ptr по значению [Выступление Chandler’а на CppCon 2019, будет опубликовано позже], не можем изменить std::hash или размещение класса в памяти для unordered_map без того, чтобы заставить всех перекомпилировать все повсюду. Производительность хэшей интенсивно изучалась в течении многих лет и с учетом оптимизации поиска в таблице и собственно хэширования, мы уверенны, что можем предоставить реализацию unordered_map/std::hash, совместимые по API и дающие 200—300% прироста производительности. Но ограничения ABI этого не позволяют. Дополнительные исследования по оптимизации и настройке SSO для std::string предполагают нетривиальное увеличение производительности (1% в микробенчмарках и при масштабировании) – API при этом не затронуто, но ограничения ABI не позволяют этого.
Суммарно заблокированные исключительно ABI потери производительности достигают нескольких процентных пунктов – возможно, до 5—10%. Это не то, без чего не может обойтись экосистема в целом, но может быть неприемлемо для некоторых организаций (Google среди них). Это, безусловно, большие потери производительности, чем допустимо для C++: вспомните, что это язык, утверждающий, что он не оставляет места для более производительного конкурента. Большинство пользователей не кажутся обеспокоенными таким снижением производительности: есть другие реализации хэш-таблиц для озабоченных абсолютной производительностью. Общая неэффективность, связанная с передачей unique_ptr по значению и другие проблемы ABI языка, выступают на первый план в очень небольшом числе задач. Организации, нуждающиеся в максимальной производительнсти могут идти своим собственным путем (и делают это), используя нестандартные библиотеки и нестандартную конфигурацию инструментария. Это закономерно и это нужно ясно понимать.
Изменение ABI затронет сравнительно большее число пользователей. Подозреваю, значительная доля этих пользователей и не подозревает, как сильна их зависимость от ABI. В экосистеме серверов Google’а почти все собирается из исходников, внешних зависимостей довольно мало и есть лучшая, чем в среднем возможность предпринять масштабный рефакторинг. Но даже для нас недавние ломающие ABI изменения стандартной библиотеки стоили 5-10 инженеро-лет. Суммарная стоимость нарушение обратной совместимости ABI для всей экосистемы C++ можно консервативно оценить в «инженер—тысячилетие»: координация пересборки для каждого в мире поставщика всех на свете плагинов, .so или dll потребует огромных затрат человеческих ресурсов. Совместно с разделением экосистемы из-за C++20 модулей, изменение ABI во временном окне разработки и внедрения C++23 может привести к жесткому разделению экосистемы.
С этой дискуссией связано много вопросов, на которые невозможно ответить. Как долго можем мы продолжать до того момента, когда изменение ABI из просто полезного станет критической необходимостью? Если мы явно выберем поддержку стабильности ABI, насколько дорогим окажется изменение в момент, когда и если возникнет такая критическая необходимость? Если проблемы безопасности вроде Spectre и Meltdown потребуют изменить соглашение о вызовах, во что обойдется C++ преодоление этого рубежа? Какая доля разработчиков использует C++ из-за того, что мы утверждаем, что ставим производительность выше всего остального? Хуже того: как долго сможет C++ утверждать, что он самый быстрый язык и не заниматься такими оптимизациями?
Если мы сознательно не можем позволить или не хотим менять ABI, то это решение надо громко озвучить. Мы должны ясно сказать, что это язык, который ставит стабильность ABI выше нескольких последних процентов производительности. Я готов спорить, что на практике так и было в течение последних нескольких лет. Мы должны дать пользователям знать, что от нас ожидать, и дать им понять, что библиотеки вроде Boost, Folly или Absail ожидаемо правильный выбор, если нужна производительность. Но это ничем не помогает с такими связанными с ABI ограничениями в самом языке, как затраты на передачу unique_ptr. Стандартная библиотека сохраняет значение в такой модели развития: стандартная библиотека — это то, что мы используем для совместимости и стабильности. Это может потребовать изменения фокуса и направления развития: нам может хотеться проектировать для большей гибкости в изменяющихся условиях, а не для «чистой» производительности.
Если же мы утверждаем, что производительность важнее стабильности ABI, мы должны немедленно решить, когда именно мы «сломаем» обратную совместимость и сделать все возможное для того, чтобы экосистема приняла такие изменения. И ясно и громко заявить, что мы идем этим путем. Необходимо понять, что чем больше времени будет проходить между такими изменениями, тем более дорогими они станут – поскольку с течением времени будет все больше неподдерживаемой зависимости от ABI. Наши «реализаторы» очень ясно дали понять, что ломающие совместимость изменения C++11 были болезненными и дорогими. Желание избежать повторения таких расходов естественно, но требуется выбрать: либо мы не повторяем их, поскольку не изменяем ABI, либо сделав расходы меньше.
По сути, для WG21 существуют три возможности:
Полагаю, что вариант выбора №1 лучше подходит для пользователей, которым требуется максимальная производительность, но с ним связаны невероятная стоимость для экосистемы и он может в дальнейшем привести к фрагментации языка. Вариант выбора №2 – скучный, ответственный и достойный выбор: с печалью признать, что мы закрасили себя в углу комнаты и постараться минимизировать связанные с этим потери. Выбрать вариант №3 – значит не справиться с управлением, и я молюсь за то, чтобы этого избежать: любой явный выбор лучше, чем текущий диссонанс и неспособность достичь согласия в выборе долговременных целей.
Понимаю, что мы достигли нынешнего положения благодаря множеству мелких актов кажущегося разумным бездействия. За прошедшие 10 лет не было сделанного ни одного изменения, которое могло бы оправдать нарушение бинарной совместимости, но неявная политика поддержания обратной совместимости сделалась разрушительной для экосистемы. Однако, приняв такую политику явно, мы откроем другую возможность для постепенного ухода C++ со сцены: нельзя быть системным языком, ориентированным на производительность, оставляя столько места для более производительного языка. В теории, каждый вендор может сам решить «сломать» ABI в любом будущем релизе, но общее направление мысли кажется другим. Уверен, требуется обсуждение и достижение консенсуса между реализаторами стандарта и WG21: какие приоритеты выбрать?
В течение последних лет в WG21 я активно пропагандировал то, что прогресс важнее обратной совместимости. Но я сам перестаю в это верить, особенно в отношении поддержания бинарной совместимости (ABI). В трех последних релизах (C++14, C++17 и C++20) ABI было настолько стабильно, насколько нам это удавалось. Даже если WG21 решит нарушить обратную совместимость ABI в C++23, мы обеспечивали бинарную совместимость на многих платформах более 10 лет. По моему мнению, в масштабных переделках программных систем доминирует закон Хайрума. Сейчас и не скажешь, у скольких пользователей предположение о стабильности ABI стандартной библиотеки (неважно, сколь мудрое или сколь явное или неявное) твердо «прошито в подкорке», возможно у половины разработчиков на C++ в мире.
Я веду список того, что WG21 могла бы исправить в языке, если мы решим «поломать» ABI. И я не могу уверенно утверждать, что общая стоимость переделок, которое повлечет выполнение только этого списка сравнима с тем, в какую цену обойдется нарушение ABI всей экосистеме. Мы видели много мелких улучшений в консистентности API, качестве кода стандартной библиотеки и т.д., но без сомнения нет ни одного такого «прорывного» изменения, которое оправдало бы эту стоимость для рядового разработчика. Возможно, мы бы получили лучшее соответствие реализаций стандарту, дали бы шанс решить проблемы реализациям, сегодня не выполняющим спецификации стандарта. Но ни одно улучшение из моего списка явно не стоит таких затрат.
Что важнее, из-за ограничений ABI мы не можем устранить существенные потери производительности. Мы не можем избавиться от существенной стоимости передачи unique_ptr по значению [Выступление Chandler’а на CppCon 2019, будет опубликовано позже], не можем изменить std::hash или размещение класса в памяти для unordered_map без того, чтобы заставить всех перекомпилировать все повсюду. Производительность хэшей интенсивно изучалась в течении многих лет и с учетом оптимизации поиска в таблице и собственно хэширования, мы уверенны, что можем предоставить реализацию unordered_map/std::hash, совместимые по API и дающие 200—300% прироста производительности. Но ограничения ABI этого не позволяют. Дополнительные исследования по оптимизации и настройке SSO для std::string предполагают нетривиальное увеличение производительности (1% в микробенчмарках и при масштабировании) – API при этом не затронуто, но ограничения ABI не позволяют этого.
Суммарно заблокированные исключительно ABI потери производительности достигают нескольких процентных пунктов – возможно, до 5—10%. Это не то, без чего не может обойтись экосистема в целом, но может быть неприемлемо для некоторых организаций (Google среди них). Это, безусловно, большие потери производительности, чем допустимо для C++: вспомните, что это язык, утверждающий, что он не оставляет места для более производительного конкурента. Большинство пользователей не кажутся обеспокоенными таким снижением производительности: есть другие реализации хэш-таблиц для озабоченных абсолютной производительностью. Общая неэффективность, связанная с передачей unique_ptr по значению и другие проблемы ABI языка, выступают на первый план в очень небольшом числе задач. Организации, нуждающиеся в максимальной производительнсти могут идти своим собственным путем (и делают это), используя нестандартные библиотеки и нестандартную конфигурацию инструментария. Это закономерно и это нужно ясно понимать.
Изменение ABI затронет сравнительно большее число пользователей. Подозреваю, значительная доля этих пользователей и не подозревает, как сильна их зависимость от ABI. В экосистеме серверов Google’а почти все собирается из исходников, внешних зависимостей довольно мало и есть лучшая, чем в среднем возможность предпринять масштабный рефакторинг. Но даже для нас недавние ломающие ABI изменения стандартной библиотеки стоили 5-10 инженеро-лет. Суммарная стоимость нарушение обратной совместимости ABI для всей экосистемы C++ можно консервативно оценить в «инженер—тысячилетие»: координация пересборки для каждого в мире поставщика всех на свете плагинов, .so или dll потребует огромных затрат человеческих ресурсов. Совместно с разделением экосистемы из-за C++20 модулей, изменение ABI во временном окне разработки и внедрения C++23 может привести к жесткому разделению экосистемы.
С этой дискуссией связано много вопросов, на которые невозможно ответить. Как долго можем мы продолжать до того момента, когда изменение ABI из просто полезного станет критической необходимостью? Если мы явно выберем поддержку стабильности ABI, насколько дорогим окажется изменение в момент, когда и если возникнет такая критическая необходимость? Если проблемы безопасности вроде Spectre и Meltdown потребуют изменить соглашение о вызовах, во что обойдется C++ преодоление этого рубежа? Какая доля разработчиков использует C++ из-за того, что мы утверждаем, что ставим производительность выше всего остального? Хуже того: как долго сможет C++ утверждать, что он самый быстрый язык и не заниматься такими оптимизациями?
Если мы сознательно не можем позволить или не хотим менять ABI, то это решение надо громко озвучить. Мы должны ясно сказать, что это язык, который ставит стабильность ABI выше нескольких последних процентов производительности. Я готов спорить, что на практике так и было в течение последних нескольких лет. Мы должны дать пользователям знать, что от нас ожидать, и дать им понять, что библиотеки вроде Boost, Folly или Absail ожидаемо правильный выбор, если нужна производительность. Но это ничем не помогает с такими связанными с ABI ограничениями в самом языке, как затраты на передачу unique_ptr. Стандартная библиотека сохраняет значение в такой модели развития: стандартная библиотека — это то, что мы используем для совместимости и стабильности. Это может потребовать изменения фокуса и направления развития: нам может хотеться проектировать для большей гибкости в изменяющихся условиях, а не для «чистой» производительности.
Если же мы утверждаем, что производительность важнее стабильности ABI, мы должны немедленно решить, когда именно мы «сломаем» обратную совместимость и сделать все возможное для того, чтобы экосистема приняла такие изменения. И ясно и громко заявить, что мы идем этим путем. Необходимо понять, что чем больше времени будет проходить между такими изменениями, тем более дорогими они станут – поскольку с течением времени будет все больше неподдерживаемой зависимости от ABI. Наши «реализаторы» очень ясно дали понять, что ломающие совместимость изменения C++11 были болезненными и дорогими. Желание избежать повторения таких расходов естественно, но требуется выбрать: либо мы не повторяем их, поскольку не изменяем ABI, либо сделав расходы меньше.
По сути, для WG21 существуют три возможности:
- Решить, в каком релизе будет изменено ABI, неважно в C++23 или C++26. Предупредить людей и немедленно разработать инструменты и диагностики, помогающие идентифицировать места, которые сломаются. Сосредоточится на более последовательной практике и инструментарии для поддержки изменений ABI в будущем. Не в интересах конкретного реализатора подвергнуть своих пользователей последствиям изменения ABI, если другие реализации этого не делают – смена ABI должна быть скоординированной активностью для пользы будущих пользователей. В идеале нужно ломать все – дать ясно понять, что код, скомпилированный в C++23 режиме несовместим с кодом, скомпилированным в предыдущих режимах. Если кто-то сможет обойтись без пересборки, а у других будут ошибки компановки или во время выполнения – это только усилит непонимание и разочарование.
- Решить, что мы стремимся к стабильности ABI, формализовав сегодняшнюю практику. Так было уже много лет, когда реализаторы стандарта имели право вето на ломающие изменения ABI – мы уже ставили обратную совместимость ABI выше чистоты дизайна и производительности. Если мы это признаем и ясно скажем пользователям, то экосистема станет лучше. Значение дополнительных библиотек вырастет для тех, кому нужно выжимать последние капли производительности, но не требуется стабильность, даваемая стандартом. Другие, ориентированные на производительность языки, могут в будущем оспорить наше положение.
- Не суметь выбрать направление и сохранить status quo. Для меня это худший сценарий: продолжаем и дальше неявно уделять больше внимания обратной совместимости ABI. Говорим «производительность» и голосуем «бинарная совместимость». Такой диссонанс вредит экосистеме и предполагает отсутствие согласия о приоритетах языка. Я искренне надеюсь, что усилиями реализаторов и DG, мы достигнем нужного консенсуса.
Полагаю, что вариант выбора №1 лучше подходит для пользователей, которым требуется максимальная производительность, но с ним связаны невероятная стоимость для экосистемы и он может в дальнейшем привести к фрагментации языка. Вариант выбора №2 – скучный, ответственный и достойный выбор: с печалью признать, что мы закрасили себя в углу комнаты и постараться минимизировать связанные с этим потери. Выбрать вариант №3 – значит не справиться с управлением, и я молюсь за то, чтобы этого избежать: любой явный выбор лучше, чем текущий диссонанс и неспособность достичь согласия в выборе долговременных целей.
Понимаю, что мы достигли нынешнего положения благодаря множеству мелких актов кажущегося разумным бездействия. За прошедшие 10 лет не было сделанного ни одного изменения, которое могло бы оправдать нарушение бинарной совместимости, но неявная политика поддержания обратной совместимости сделалась разрушительной для экосистемы. Однако, приняв такую политику явно, мы откроем другую возможность для постепенного ухода C++ со сцены: нельзя быть системным языком, ориентированным на производительность, оставляя столько места для более производительного языка. В теории, каждый вендор может сам решить «сломать» ABI в любом будущем релизе, но общее направление мысли кажется другим. Уверен, требуется обсуждение и достижение консенсуса между реализаторами стандарта и WG21: какие приоритеты выбрать?