Flask --without-threads дает лучшую производительность, чем --with-threads для задач, связанных с процессором?

Я использую Apache JMeter для тестирования крошечного приложения Flask. Приложение выполняет какую-то задачу, связанную с процессором.

Удивительно, но запуск приложения Flask с --without-threads дает заметно лучшие результаты, чем запуск с --with-threads. Как это могло быть?

Вот некоторые из настроек Apache JMeter и соответствующие результаты:

Количество потоков (пользователей) Количество циклов Время без потоков (секунды) Время, затраченное на потоки (секунды) 5 1000 14 17 10 500 14 18 5 3000 43 51 10 1500 43 56

Я ожидаю, что в случае задачи, связанной исключительно с процессором, многопоточная версия должна быть по крайней мере такой же быстрой, как и однопоточная. Позволь мне объяснить:

С точки зрения выполнения фактической задачи ЦП, я ожидаю, что обе версии будут работать одинаково. Однако с точки зрения того, как быстро может быть обслужен следующий поток, я ожидаю, что многопоточная версия будет иметь небольшое преимущество, потому что запрос уже был обслужен Flask, и он застрял только в ожидании ЦП.

В однопоточной версии (т. е. --without-threads) одновременно обслуживается только один запрос, в то время как все остальные запросы ожидают обслуживания Flask. Другими словами, Flask вводит определенные «накладные расходы на обслуживание».

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

Я предполагаю, что мое понимание неверно. Где я не прав?

flask run предназначен только для целей разработки. На производительность не претендует. Вместо этого попробуйте это с реальным сервером WSGI, подходящим для производства. Если эти «потоки» являются реальными потоками (а не процессами), GIL Python объясняет, что вы видите.
Thomas 06.01.2023 17:28

@ Томас Спасибо за ответ. Не могли бы вы объяснить, как GIL объясняет, что я вижу, если эти «потоки» настоящие? Другими словами, если эти «потоки» являются реальными потоками, не должны ли однопоточные и многопоточные версии быть одинаково производительными?

powerful_clouds 09.01.2023 09:29

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

Thomas 09.01.2023 10:37
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как предложил @Thomas, я провел еще несколько тестов, используя готовый к работе сервер. Я выбрал сервер gunicorn, потому что его легко настроить с помощью Python 3.9.

gunicorn принимает два аргумента командной строки, относящиеся к этой теме:

  1. --workers - "Количество рабочих процессов для обработки запросов". Значение по умолчанию — 1.
  2. --threads — «Количество рабочих потоков для обработки запросов». Значение по умолчанию также равно 1.

Увеличение --workers до уровня, с которым может справиться мой процессор, действительно улучшило производительность. Увеличение --threads не произошло. Кроме того, запуск 8 рабочих процессов с 1 потоком дал лучшие результаты, чем запуск 8 рабочих процессов с 4 потоками.

Итак, я попытался смоделировать ввод-вывод, заснув на полсекунды. Наконец, увеличение количества потоков действительно улучшило производительность.

Мое приложение также связано с процессором. Я использовал режим gthread gunicorn от --worker-class=gthread, и результат лучше, чем в другом режиме. Можешь попробовать. Дополнительная информация: https://medium.com/building-the-system/gunicorn-3-means-of-concurrency-efbb547674b7

Другие вопросы по теме