среда, 8 июня 2011 г.

Про опрос, futures и параллельное исполнение

Источник: Of polling, futures and parallel execution

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

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

Python 3.2 поставляется с новым стандартным модулем, который запускает параллельные задачи и ожидает окончания их работы: модуль concurrent.futures. Внимательно изучая код модуля, я заметил, что в некоторых рабочих потоках и процессах используется опрос. Я говорю "в некоторых", поскольку реализации классов ThreadPoolExecutor и ProcessPoolExecutor отличаются. Если первый проводит опрос в каждом рабочем потоке, то последний делает это только в одном потоке, который назвается потоком управления очередью и используется для связи с рабочими процессами.

В данном случае опрос используется с единственной целью: определить, когда должна запуститься процедура обработки события завершения процесса. Остальные задачи, такие как построение очереди вызываемых объектов или сбор результатов из ранее помещенных в очередь вызываемых объектов, используют объекты типа синхронизированная очередь. Эти объекты типа очередь импортируются или из модуля threading, или из multiprocessing, в зависимости от того, какой используется Executor.

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

В первоначальном патче по-прежнему есть таймаут опроса, настолько большой (10 минут), чтобы все рабочие процессы успели пробудиться. Большой таймаут существует по той причине, что код содержит дефекты и процессы не получают вовремя уведомление о необходимости завершения от вышеупомянутой сигнальной метки. Из любопытства я углубился в исходный код модуля multiprocessing и пришел к другому интересному наблюдению: под Windows, метод multiprocessing.Queue.get() при ненулевом и конечном таймауте использует...опрос (по этому вопросу я открыл issue 11668). Этот метод использует интересный высокочастотный вид опроса, он начинается с таймаута в одну милисекунду, а затем увеличивается с каждой итерацией.

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

Раньше, до версии Python 3.2, опрос использовался для всех реализаций таймаута в модуле threading, а также и в большей части модуля multiprocessing, поскольку последний использует рабочие потоки для различных задач. Это было исправлено в issue 7316.

Комментариев нет:

Отправить комментарий