Ранняя попытка удалить Python GIL привела к плохой производительности: почему?

13

В этом посте от создателя Python, Гвидо Ван Россума, упоминается ранняя попытка удалить GIL из Python:

Это было опробовано ранее, и результаты оказались неутешительными, поэтому я не хотел бы приложить к этому много усилий сам. В 1999 году Грег Стейн (вместе с Марком Хэммондом?) Выпустил форк Python (я полагаю, 1.5), который удалил GIL, заменив его мелкозернистыми блокировками на всех изменяемых структурах данных. Он также представил патчи, которые убрали многие из опор глобальных изменяемых структур данных, которые я принял. Однако после сравнительного анализа было показано, что даже на платформе с самым быстрым блокирующим примитивом (в то время Windows) он замедлял однопоточное выполнение почти в два раза, а это означает, что на двух процессорах вы можете получить чуть больше работы сделано без GIL, чем на одном процессоре с GIL. Этого было недостаточно, и патч Грега исчез в забвении. (См. Рецензию Грега на представление.)

Я не могу спорить с реальными результатами, но мне действительно интересно, почему это произошло. Предположительно, главная причина, по которой удаление GIL из CPython является настолько сложной, заключается в системе управления памятью подсчета ссылок. Типичная программа Python будет вызывать Py_INCREFи Py_DECREFтысячи или миллионы раз, что делает его ключевой момент конкурирующего если мы должны были обернуть пряди вокруг него.

Но я не понимаю , почему добавление атомных примитивов бы замедлить единую резьбовую программу. Предположим, мы только что изменили CPython, чтобы переменная refcount в каждом объекте Python была атомарным примитивом. И тогда мы просто делаем атомарный инкремент (инструкция извлечения и добавления), когда нам нужно увеличить счетчик ссылок. Это сделало бы подсчет ссылок на Python безопасным для потока и не привело бы к снижению производительности однопоточного приложения, поскольку не было бы конфликта блокировок.

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

Сайлер
источник
1
Обратите внимание, что операция refcount не будет единственным местом, где требуется синхронизация. Цитата упоминает «тонкозернистые блокировки всех изменяемых структур данных», которые, как я предполагаю, включают в себя как минимум мьютекс для каждого объекта списка и словаря. Кроме того, я не думаю, что целочисленные атомарные операции так же эффективны, как неатомарный эквивалент, независимо от конкуренции, есть ли у вас источник для этого?
просто, потому что атомарные операции медленнее, чем неатомарные эквиваленты. То, что это единственная инструкция, не означает, что под капотом она тривиальна. Смотрите это для обсуждения
Móż

Ответы:

9

Я незнаком с Greg Stein Python fork, поэтому, если хотите, обесцените это сравнение как умозрительную историческую аналогию. Но это был именно исторический опыт многих инфраструктурных кодовых баз, переходящих от однопоточных к многопоточным реализациям.

Практически каждая реализация Unix, которую я изучал в 1990-х годах - AIX, DEC OSF / 1, DG / UX, DYNIX, HP-UX, IRIX, Solaris, SVR4 и SVR4 MP, - все проходила именно через этот тип, который мы ввели более тонкая блокировка - теперь она медленнее !! проблема. СУБД, за которыми я следовал - DB2, Ingres, Informix, Oracle и Sybase - тоже прошли через это.

Я слышал, что «эти изменения не замедлят нас, когда мы запускаем однопоточную» миллион раз. Это никогда не сработает. Простой акт условной проверки "многопоточный ли у нас или нет?" добавляет реальные накладные расходы, особенно на высокотрубных процессорах. Атомарные операции и случайные спин-блокировки добавляются для обеспечения целостности общих структур данных, которые нужно вызывать довольно часто, и они очень медленные. Примитивы блокировки / синхронизации первого поколения также были медленными. Большинство команд по внедрению в конечном итоге добавляют несколько классов примитивов с различными «сильными сторонами», в зависимости от того, сколько защиты от блокировки требуется в разных местах. Затем они понимают, что изначально они закрывали блокирующие примитивы на самом деле не в том месте, поэтому им пришлось профилировать дизайн вокруг найденных узких мест, и систематически рото-до. Некоторые из этих препятствий в конечном итоге получили ускорение ОС или оборудования, но вся эта эволюция заняла 3-5 лет, минимум. Между тем, версии MP или MT хромали в плане производительности.

В противном случае сложные команды разработчиков утверждают, что такие замедления в основном являются постоянным, неразрешимым фактом жизни. Например, IBM отказалась от AIX с поддержкой SMP, по крайней мере, в течение 5 лет после соревнования, утверждая, что однопоточность была просто лучше. Sybase использовал одни и те же аргументы. Единственная причина, по которой некоторые команды в конечном итоге пришли в себя, заключалась в том, что однопотоковая производительность больше не могла быть разумно улучшена на уровне процессора. Они были вынуждены либо пойти MP / MT или принять все более неконкурентоспособный продукт.

Активный параллелизм труден. И это обманчиво. Каждый врывается в это, думая, что «это не будет так плохо». Затем они попали в зыбучие пески, и им пришлось пробиться сквозь них. Я видел это по крайней мере с дюжиной известных брендов, хорошо финансируемых, умных команд. Как правило, после выбора многопоточности потребовалось не менее пяти лет, чтобы «вернуться туда, где они должны быть, с точки зрения производительности» с продуктами MP / MT; большинство из них все еще значительно улучшали эффективность / масштабируемость MP / MT даже через десять лет после перехода.

Таким образом, мое предположение состоит в том, что, без одобрения и поддержки GvR, никто не взял на себя долгий шаг за Python и его GIL. Даже если бы они сделали это сегодня, это был бы период Python 4.x, прежде чем вы сказали бы: «Ух ты! Мы действительно преодолели горб MT!»

Возможно, есть какая-то магия, которая отделяет Python и его среду выполнения от всего остального программного обеспечения для инфраструктуры - все языковые среды выполнения, операционные системы, мониторы транзакций и менеджеры баз данных, которые были раньше. Но если так, это уникально или почти так. Всем остальным, кто удаляет GIL-эквивалент, потребовалось пять с лишним лет упорных, самоотверженных усилий и инвестиций, чтобы перейти от MT-not к MT-hot.

Джонатан Юнис
источник
2
+1 Такого времени потребовалось многопоточному Tcl с довольно небольшой командой разработчиков. До этого код был МТ-безопасным, но имел неприятные проблемы с производительностью, в основном в управлении памятью (что, я подозреваю, является очень горячей областью для динамических языков). Впрочем, этот опыт не переносится на Python ни в чем, кроме самых общих терминов; два языка имеют совершенно разные модели потоков. Просто ... ожидайте утомительного и ожидайте странных ошибок ...
Донал Феллоуз
-1

Еще одна дикая гипотеза: в 1999 году Linux и другие Unices не имели производительной синхронизации, как сейчас futex(2)( http://en.wikipedia.org/wiki/Futex ). Те пришли примерно в 2002 году (и были объединены в 2.6 около 2004 года).

Поскольку все встроенные структуры данных должны быть синхронизированы, блокировка стоит дорого. Ᶎσᶎ уже указывал, что атомные операции не нужны дешево.

сагиб
источник
1
У вас есть что-нибудь, чтобы поддержать это? или это почти предположение?
1
Цитата GvR описывает производительность «на платформе с самым быстрым примитивом блокировки (в то время Windows)», поэтому медленные блокировки в Linux не имеют значения.