[Перевод] Гниение программного обеспечения
В книге «Электромагнитная эпоха: работа, любовь и жизнь, когда роботы правят миром» Робин Хэнсон кратко обсуждает программную гниль:
Программное обеспечение изначально было разработано для одного набора задач, инструментов и ситуаций. Но оно медленно изменяется, чтобы справиться с постоянным потоком новых задач, инструментов и ситуаций. Такой софт становится более сложным, хрупким, в него труднее вносить полезные изменения (Леман и Биледи, 1985)1. В конце концов, лучше начать всё сначала и написать с нуля новые подсистемы, а иногда и полностью новые системы.
Я уверен, что это правда. Как правило, грамотная адаптация программного обеспечения к новым условиям занимает больше времени и усилий, чем написание нового программного обеспечения с нуля. Программисты не любят признавать это, но доказательства очевидны. В проектах open source есть несколько известных примеров.
Изначально Mozilla Firefox запускал все задачи в одном процессе. После выхода Google Chrome стало ясно, что модель с несколькими процессами повышает безопасность и производительность. Вскоре разработчики Mozilla начали планировать, как реализовать в Firefox многопроцессность. Это было в 2007 году.
Почти десять лет спустя Mozilla, наконец, выпустила мультипроцессный Firefox на массовую аудиторию. Эта задержка произошла вовсе не от недостатка желания. У Mozilla талантливые и целеустремленные разработчики. Тем не менее, Chrome был написан с нуля за гораздо меньшее время, чем потребовалось Firefox для изменения. У этого две основные причины:
- Переделка однопроцессной архитектуры в многопроцессную предполагает множество мелких изменений. Некоторые вызовы функций нужно заменить межпроцессными коммуникациями. Общее состояние нужно обернуть в мьютексы. Кэши и локальные БД должны поддерживать параллельный доступ.
- Firefox должен сохранить совместимость с существующими расширениями (или заставить разработчиков их обновить). Chrome создавал API для расширений с нуля, не имея таких ограничений.
Но ситуация ещё хуже. Ограничения противоречат друг другу: нужно перестроить внутреннюю архитектуру, но оставить почти без изменений общедоступные API. Неудивительно, что Mozilla понадобилось десять лет на такой подвиг.
Изначально Apache httpd работал по модели «один процесс на соединение». Один процесс прослушивал порт 80, затем делал accept()
и fork()
. Дочерний процесс потом выполнял read()
и write()
на сокете. Когда запрос завершён, дочерний процесс закрывал сокет close()
и выполнял exit()
.
Такая архитектура простая, лёгкая в реализации на многих платформах и… всё. Она абсолютно ужасна для производительности, особенно при обработке долгоживущих соединений. Справедливости ради: это был 1995 год. И вскоре Apache перешёл на потоковую модель, которая улучшила производительность. Тем не менее, он не мог обрабатывать 10 000 одновременных соединений. Архитектура «один поток на соединение» требует 1000 потоков для обслуживания 1000 одновременных подключений. У каждого потока собственный стек и состояние, его должен запланировать и запустить шедулер операционной системы. Это отнимает время.
Напротив, Nginx с самого начала использовал шаблон реактора. Это позволило обрабатывать больше одновременных соединений и сделало его невосприимчивым к атакам Slowloris.
Nginx вышел в 2007 году, и его преимущество в производительности было очевидным. За несколько лет до выхода Nginx разработчики Apache начали перепроектировать httpd для улучшения производительности. Многопроцессный модуль event вышел с Apache 2.2 в 2005 году. Но обнаружились проблемы совместимости. Хуже всего, что он сломал совместимость с популярными модулями, такими как mod_php. Проблему не могли исправить до 2012 года, когда Apache 2.4 вышел с этим модулем (MPM) по умолчанию. Хотя он работал гораздо лучше, чем предыдущий prefork и MPM-воркер, но так и не смог сравняться по производительности с Nginx. Вместо шаблона реактора Apache использовал отдельные пулы потоков для прослушивания/приёма соединений и обработки запросов. Архитектура примерно эквивалентна запуску балансировщика нагрузки или обратного прокси перед воркером MPM httpd2.
Python — хороший язык программирования. Он выразителен, прост в освоении (по крайней мере, для языка программирования) и поддерживается на разных платформах. Но в течение последних двух десятилетий у самой популярной реализации Python была серьёзная проблема: она не могла легко воспользоваться преимуществами нескольких процессорных ядер.
Причина в глобальной блокировке интерпретатора или GIL. Из питоновского вики:
В CPython глобальная блокировка интерпретатора — это мьютекс, который блокирует одновременное выполнение нескольких потоков кода. Такая блокировка необходима главным образом потому, что управление памятью CPython не является потокобезопасным. (Но поскольку GIL существует, другие функции стали зависеть от него).
Первоначально GIL не представлял особой проблемы. При создании Python многоядерные системы встречались редко. А GIL было легко написать и это простая, логичная система. Но сегодня многоядерные процессоры работают даже в наручных часах. GIL — очевидный и вопиющий дефект во всех отношениях приятного языка. Несмотря на популярность CPython, несмотря на талантливых разработчиков, несмотря на спонсоров, таких как Google, Microsoft и Intel, исправлять GIL даже не планируется.
Даже при наличии талантливых инженеров, большого количества денег и чёткого плана зрелое программное обеспечение чрезвычайно трудно изменить. Я пытался найти случаи, которые опровергают гниение ПО, но их, похоже, не существует. Робин Хэнсон попросил привести контрпримеры, но никто не предложил ничего убедительного. Есть много старых программных проектов, но им не пришлось особо адаптироваться. Я реально хочу найти хорошие контрпримеры, потому что без них создаётся слишком мрачная картина перспектив программного обеспечения.
Дополнительные материалы
1. Цитата из статьи «Эволюция программ: процессы изменения программного обеспечения». Работа старше меня, и я не могу найти онлайн-версию. Я купил физическую копию и с трудом осилил её. Терминология странная, но выводы не слишком удивительны. ↑
2. Любой, кто знает устройство httpd, возразит против такого сравнения, но здесь мы жертвуем точностью ради краткости. Прошу прощения за это. ↑