#python #multithreading #async-await #multiprocessing #python-asyncio
#python #многопоточность #async-await #многопроцессорность #python-asyncio
Вопрос:
Я пытаюсь запустить фрагмент кода с использованием asyncio и сократить время выполнения всего кода. Ниже приведен мой код, для полного выполнения которого требуется около 6 секунд
Обычные вызовы функций — (подход 1)
from time import time, sleep
import asyncio
def find_div(range_, divide_by):
lis_ = []
for i in range(range_):
if i % divide_by == 0:
lis_.append(i)
print("found numbers for range {}, divided by {}".format(range_, divide_by))
return lis_
if __name__ == "__main__":
start = time()
find_div(50800000, 341313)
find_div(10005200, 32110)
find_div(50000340, 31238)
print(time()-start)
Результат приведенного выше кода — это просто общее время выполнения, которое составляет 6 секунд.
Многопоточный подход — (подход 2) При этом использовалась многопоточность, но, на удивление, время увеличилось
from time import time, sleep
import asyncio
import threading
def find_div(range_, divide_by):
lis_ = []
for i in range(range_):
if i % divide_by == 0:
lis_.append(i)
print("found numbers for range {}, divided by {}".format(range_, divide_by))
return lis_
if __name__ == "__main__":
start = time()
t1 = threading.Thread(target=find_div, args=(50800000, 341313))
t2 = threading.Thread(target=find_div, args=(10005200, 32110))
t3 = threading.Thread(target=find_div, args=(50000340, 31238))
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print(time()-start)
Вывод приведенного выше кода составляет 12 секунд.
Подход с многопроцессорной обработкой — (подход 3)
from time import time, sleep
import asyncio
from multiprocessing import Pool
def multi_run_wrapper(args):
return find_div(*args)
def find_div(range_, divide_by):
lis_ = []
for i in range(range_):
if i % divide_by == 0:
lis_.append(i)
print("found numbers for range {}, divided by {}".format(range_, divide_by))
return lis_
if __name__ == "__main__":
start = time()
with Pool(3) as p:
p.map(multi_run_wrapper,[(50800000, 341313),(10005200, 32110),(50000340, 31238)])
print(time()-start)
Вывод многопроцессорного кода составляет 3 секунды, что лучше, чем при обычном подходе к вызову функции.
Подход Asyncio — (подход 4)
from time import time, sleep
import asyncio
async def find_div(range_, divide_by):
lis_ = []
for i in range(range_):
if i % divide_by == 0:
lis_.append(i)
print("found numbers for range {}, divided by {}".format(range_, divide_by))
return lis_
async def task():
tasks = [find_div(50800000, 341313),find_div(10005200, 32110),find_div(50000340, 31238)]
result = await asyncio.gather(*tasks)
print(result)
if __name__ == "__main__":
start = time()
asyncio.run(task())
print(time()-start)
Приведенный выше код также занимает около 6 секунд, что соответствует обычному вызову функции выполнения, который является подходом 1.
Проблема-
Почему мой подход Asyncio не работает должным образом и сокращает общее время?
Что не так в коде?
Комментарии:
1. Я предполагаю, что это просто пример кода? Потому что есть ГОРАЗДО более эффективный способ получить список всех кратных числу, чем проверка по модулю для каждого числа в диапазоне
2. Ваши «асинхронные» функции ничего не ожидают, поэтому они даже не пытаются переключить выполнение, они просто выполняются как обычный код синхронизации. Если вы добавите что-то вроде
await asyncio.sleep(0)
в цикл, они начнут выдавать результаты и выполняться одновременно. Обратите внимание, что таким образом вы не получите ускорения, может даже оказаться, что async — самый медленный из всех протестированных вариантов. Как объясняется в ответе, это связано с тем, что асинхронное выполнение предназначено для параллельного ожидания, а не для параллельного выполнения, и ваш код явно привязан к процессору.3. Если вы хотите использовать asyncio для других задач в вашей программе (например, для сетевого взаимодействия), но у вас есть привязанный к процессору код, который должен выполняться на нескольких ядрах, вы можете объединить многопроцессорную обработку и asyncio с помощью
run_in_executor
.4. Макс, да, это всего лишь пример кода. пользователь4815162342, спасибо, эта ссылка кажется полезной, я проверю это.
Ответ №1:
У вас есть код, который использует исключительно процессор. Подобный код нельзя ускорить с помощью async.
Асинхронность срабатывает, когда у вас есть задачи, которые ожидают чего-то, не связанного с процессором, например, сетевого запроса или чтения с диска. Как правило, это верно для всех языков.
В Python подход, основанный на потоках, также не поможет вам, поскольку это по-прежнему ограничивает вас одним ядром, а не настоящим параллельным выполнением. Это связано с глобальной блокировкой интерпретатора (GIL). Накладные расходы на запуск и переключение между потоками делают его медленнее, чем простая версия. В этом отношении потоки аналогичны асинхронности в python, это помогает только в том случае, если время ожидания не тратится в основном на процессор или если вы вызываете код, который не связан с GIL, например, расширениями C.
Использование multiprocessing
действительно использует несколько ядер процессора, поэтому это быстрее, чем обычное решение.
Комментарии:
1. Итак, если у меня есть подключение к базе данных, которое может быть асинхронной библиотекой и выполнять некоторый SQL, эти вещи могут быть добавлены асинхронным способом, верно? Не могли бы вы указать мне хороший ресурс для функциональности pymssql async?
2. @johnmich вы можете использовать aiomysql, который является асинхронной библиотекой для базы данных.
Ответ №2:
asyncio def run(time):
await asyncio.sleep(time)
Этот код занимает 1 минуту 40 секунд
from datetime import datetime
now = datetime.now()
task=[]
for i in range(10):
await run(10)
now1=datetime.now()
print(now1-now)
ОПТИМИЗИРОВАНО С ИСПОЛЬЗОВАНИЕМ async—>
Это занимает всего 10 секунд
from datetime import datetime
now = datetime.now()
task=[]
for i in range(10):
task.append(asyncio.create_task(run(10)))
await asyncio.gather(*task)
now1=datetime.now()
print(now1-now)