Будет ли установка мьютекса вручную улучшать производительность? - PullRequest
2 голосов
/ 04 ноября 2019

Моя программа на Python определенно привязана к процессору, но от 40% до 55% затрачиваемого времени выполняется в коде C в решателе z3 (который ничего не знает против gil), где каждый отдельный вызов функции C (z3_optimize_check) для завершения требуется почти минута (пока параметр parallel_enable все еще приводит к тому, что эта функция работает в однопоточном режиме и блокирует основной поток).

Я не могу использовать многопроцессорность, поскольку z3_objects aren '• Сериализуется дружелюбно (за исключением случаев, когда кто-то может доказать обратное). Поскольку это несколько задач (где каждая задача добавляет больше работы z3 в поле для других задач), я изначально настроил многопоточность напрямую. Но Gil определенно снижает производительность больше, чем есть преимущество (особенно с гиперпоточностью), несмотря на огромное время, затрачиваемое на решатель.

Но если я настрою блокирующий мьютекс вручную (через threading.Lock.aquire()) в z3pyмодуль сразу после переключения из кода C, который позволил бы другому потоку работать только в том случае, если все другие потоки выполняют решающую работу, это уберет снижение производительности gil (так как их будет только 1 поток во время выполнения кода Python, и это всегда будетто же самое, пока блокировка не будет снята до z3_optimize_check)?
Я имею в виду, что использование threading.Lock.aquire() вызывает вызовы к PyEval_SaveThread(), как если бы z3 делал это напрямую?

1 Ответ

0 голосов
/ 04 ноября 2019

пока параметр parallel_enable по-прежнему приводит к тому, что эта функция работает в однопоточном режиме и блокирует основной поток

Я думаю, вы неправильно это поняли. Запуск z3 в параллельном режиме означает, что вы вызываете его из одного потока Python, а затем он порождает несколько потоков уровня ОС для себя, выполняя работу, очищая потоки и возвращая результат для вас. Он не позволяет чудесным образом запускать Python без GIL.
С точки зрения Python, он все равно выполняет одну вещь за раз, и одна вещь делает вызов z3. И он держит GIL на все время. Поэтому, если вы видите, что более одного ядра / потока ЦП используется во время выполнения вычислений, это является эффектом параллельного режима z3, внутренне разветвляющегося на несколько потоков.

Есть еще одна вещь, освобождающая GIL, например, чтоблокировать операции ввода / вывода. Это не происходит по волшебству, для этого есть пара вызовов:

PyThreadState * PyEval_SaveThread ()
Снять глобальную блокировку интерпретатора (если она была создана)) и сбросить состояние потока на NULL, возвращая предыдущее состояние потока (которое не равно NULL). Если блокировка была создана, текущий поток должен ее получить.

void PyEval_RestoreThread (PyThreadState * tstate)
Получить глобальную блокировку интерпретатора (если она была создана) иустановите состояние потока на tstate , которое не должно быть NULL. Если блокировка была создана, текущий поток не должен был ее получить, в противном случае возникает взаимоблокировка.

Это вызовы C, поэтому они доступны для разработчиков расширений. Когда разработчики знают, что код будет работать долго, без необходимости доступа к внутренним компонентам Python, можно использовать PyEval_SaveThread(), а затем Python сможет продолжить работу с другими потоками Python. И когда долго, что бы ни было сделано, поток может заново представиться и подать заявку на GIL, используя PyEval_RestoreThread().
Но эти вещи случаются, только если разработчики заставляют их происходить. А с z3 это может быть не так.

Чтобы дать прямой ответ на ваш вопрос: нет, код Python не может освободить GIL и сохранить его освобожденным, поскольку GIL - это блокировка, которую должен содержать поток Pythonкогда это продолжается. Поэтому всякий раз, когда Python возвращает «инструкцию», GIL снова удерживается.


Видимо, каким-то образом мне удалось не включить ссылку, которую я хотел, поэтому они находятся на странице https://docs.python.org/3/c-api/init.html#thread-state-and-the-global-interpreter-lock (ив связанном параграфе обсуждается то, что я кратко суммировал).

Z3 является открытым исходным кодом (https://github.com/Z3Prover/z3), и исходный код не содержит ни PyEval_SaveThread, ни последовательности символов-оберток Py_BEGIN_ALLOW_THREADS.


Но у него есть параллельный пример Python, кстати. https://github.com/Z3Prover/z3/blob/master/examples/python/parallel.py, с

from multiprocessing.pool import ThreadPool

Так что я бы предположил, что это может быть проверенои работает с multiprocessing.

...