Запросы Python-потоки / процессы против ввода-вывода
я подключаюсь к локальному серверу (OSRM) через HTTP для отправки маршрутов и возврата времени на диске. Я замечаю, что ввод-вывод медленнее, чем поток, потому что кажется, что период ожидания для вычисления меньше, чем время, необходимое для отправки запроса и обработки вывода JSON (я думаю, что ввод-вывод лучше, когда серверу требуется некоторое время для обработки вашего запроса - > вы не хотите, чтобы он блокировался, потому что вам нужно ждать, это не мой случай). Threading страдает от глобального интерпретатора Блокировка и поэтому кажется (и доказательства ниже), что мой самый быстрый вариант-использовать многопроцессорную обработку.
проблема с многопроцессорной обработкой заключается в том, что она настолько быстра, что исчерпывает мои сокеты, и я получаю ошибку (запросы выдает новое соединение каждый раз). Я могу (последовательно) использовать запросы.Sessions () объект, чтобы сохранить соединение живым, однако я не могу заставить это работать параллельно (каждый процесс имеет свой собственный сеанс).
ближайший код я должен работать на данный момент это многопроцессорный код:
conn_pool = HTTPConnectionPool(host='127.0.0.1', port=5005, maxsize=cpu_count())
def ReqOsrm(url_input):
ul, qid = url_input
try:
response = conn_pool.request('GET', ul)
json_geocode = json.loads(response.data.decode('utf-8'))
status = int(json_geocode['status'])
if status == 200:
tot_time_s = json_geocode['route_summary']['total_time']
tot_dist_m = json_geocode['route_summary']['total_distance']
used_from, used_to = json_geocode['via_points']
out = [qid, status, tot_time_s, tot_dist_m, used_from[0], used_from[1], used_to[0], used_to[1]]
return out
else:
print("Done but no route: %d %s" % (qid, req_url))
return [qid, 999, 0, 0, 0, 0, 0, 0]
except Exception as err:
print("%s: %d %s" % (err, qid, req_url))
return [qid, 999, 0, 0, 0, 0, 0, 0]
# run:
pool = Pool(cpu_count())
calc_routes = pool.map(ReqOsrm, url_routes)
pool.close()
pool.join()
однако я не могу заставить HTTPConnectionPool работать правильно, и он создает новые сокеты каждый раз (я думаю), а затем дает мне ошибку:
HTTPConnectionPool (host= '127.0.0.1', port=5005): превышено максимальное число попыток с url: /viaroute?loc=44.779708, 4.2609877 & loc=44.648439, 4.2811959 & alt=ложь & геометрия=ложь (Вызвано NewConnectionError ( ' : не удалось установить новое соединение: [WinError 10048] только одно использование каждого адрес сокета (протокол / сетевой адрес / порт) обычно разрешен',))
моя цель - получить расчеты расстояния от OSRM-сервер маршрутизации я работаю локально (как можно быстрее).
у меня вопрос в двух частях - в основном я пытаюсь преобразовать некоторый код с помощью многопроцессорной обработки.Пул() для лучшего кода (правильные асинхронные функции-так что выполнение никогда не ломается и работает так же быстро, как вероятный.)
проблема в том, что все, что я пытаюсь, кажется медленнее, чем многопроцессорная обработка (я приведу несколько примеров ниже того, что я пробовал).
некоторые потенциальные методы являются следующими: gevents, grequests, торнадо, запросы-фьючерсы, asyncio, etc.
A-Многопроцессорная Обработка.Pool ()
Я изначально начал с чего-то вроде этого:
def ReqOsrm(url_input):
req_url, query_id = url_input
try_c = 0
#print(req_url)
while try_c < 5:
try:
response = requests.get(req_url)
json_geocode = response.json()
status = int(json_geocode['status'])
# Found route between points
if status == 200:
....
pool = Pool(cpu_count()-1)
calc_routes = pool.map(ReqOsrm, url_routes)
где я подключался к локальному серверу (localhost,порт:5005), который был запущен на 8 нитей и поддерживает параллельное исполнение.
после недолгих поисков я осознал ошибки я получаю потому, что запросы был открытие нового соединения / сокета для каждого-request. Так что это было на самом деле слишком быстро и утомительно через некоторое время. Кажется, способ решить эту проблему-использовать запросы.Session() - однако я не смог заставить это работать с многопроцессорной обработкой (где каждый процесс имеет свой собственный сеанс).
Вопрос 1.
На некоторых компьютерах это работает нормально, например:
для сравнения: 45% использования сервера и 1700 запросов в секунду
однако на некоторых это не так, и я не совсем понимаю, почему:
HTTPConnectionPool (host= '127.0.0.1', port=5000): превышено максимальное число попыток с url-адрес: /viaroute?loc=49.34343, 3.30199 & loc=49.56655, 3.25837 & alt=ложь & геометрия=ложь (Вызвано NewConnectionError (': не удалось установить новое соединение: [WinError 10048] только одно использование каждого адреса сокета (протокол / сетевой адрес / порт) обычно разрешен',))
Я предполагаю, что, поскольку запросы блокируют сокет, когда он используется - иногда сервер слишком медленный, чтобы ответить на старый запрос, и генерируется новый. Сервер поддерживает очередь, однако запросы не так, вместо добавления в очередь я получаю ошибку?
Вопрос 2.
я нашел:
Блокировка Или Неблокирование?
С транспортным адаптером по умолчанию на месте, запросы не предоставляют любой вид неблокирующего ввода-вывода. ответ.свойство content будет блокировать пока не будет загружен весь ответ. Если вы больше требуют детализация, потоковые функции библиотека (см. потокового Запросы) позволяют получить меньшее количество ответа по адресу время. Однако эти вызовы все равно будут блокироваться.
если вы обеспокоены использованием блокировки ввода-вывода, есть много проекты, которые объединяют запросы с одним из Python основы asynchronicity.
два отличных примера-grequests и requests-futures.
B-запросы-фьючерсы
в адрес это мне нужно было переписать мой код для использования асинхронных запросов, поэтому я попробовал ниже, используя:
from requests_futures.sessions import FuturesSession
from concurrent.futures import ThreadPoolExecutor, as_completed
(кстати, я запускаю свой сервер с возможностью использовать все потоки)
и основной код:
calc_routes = []
futures = {}
with FuturesSession(executor=ThreadPoolExecutor(max_workers=1000)) as session:
# Submit requests and process in background
for i in range(len(url_routes)):
url_in, qid = url_routes[i] # url |query-id
future = session.get(url_in, background_callback=lambda sess, resp: ReqOsrm(sess, resp))
futures[future] = qid
# Process the futures as they become complete
for future in as_completed(futures):
r = future.result()
try:
row = [futures[future]] + r.data
except Exception as err:
print('No route')
row = [futures[future], 999, 0, 0, 0, 0, 0, 0]
calc_routes.append(row)
где моя функция (ReqOsrm) теперь переписана как:
def ReqOsrm(sess, resp):
json_geocode = resp.json()
status = int(json_geocode['status'])
# Found route between points
if status == 200:
tot_time_s = json_geocode['route_summary']['total_time']
tot_dist_m = json_geocode['route_summary']['total_distance']
used_from = json_geocode['via_points'][0]
used_to = json_geocode['via_points'][1]
out = [status, tot_time_s, tot_dist_m, used_from[0], used_from[1], used_to[0], used_to[1]]
# Cannot find route between points (code errors as 999)
else:
out = [999, 0, 0, 0, 0, 0, 0]
resp.data = out
однако этот код медленнее чем многопроцессорный! Раньше я получал около 1700 запросов в секунду, теперь я получаю 600 секунд. Я думаю это потому, что у меня нет полного использования процессора, однако я не уверен, как его увеличить?
C-Thread
я попробовал другой способ (создание темы) - однако снова не был уверен, как получить это, чтобы максимизировать использование процессора (в идеале я хочу, чтобы мой сервер использовал 50%, нет?):
def doWork():
while True:
url,qid = q.get()
status, resp = getReq(url)
processReq(status, resp, qid)
q.task_done()
def getReq(url):
try:
resp = requests.get(url)
return resp.status_code, resp
except:
return 999, None
def processReq(status, resp, qid):
try:
json_geocode = resp.json()
# Found route between points
if status == 200:
tot_time_s = json_geocode['route_summary']['total_time']
tot_dist_m = json_geocode['route_summary']['total_distance']
used_from = json_geocode['via_points'][0]
used_to = json_geocode['via_points'][1]
out = [qid, status, tot_time_s, tot_dist_m, used_from[0], used_from[1], used_to[0], used_to[1]]
else:
print("Done but no route")
out = [qid, 999, 0, 0, 0, 0, 0, 0]
except Exception as err:
print("Error: %s" % err)
out = [qid, 999, 0, 0, 0, 0, 0, 0]
qres.put(out)
return
#Run:
concurrent = 1000
qres = Queue()
q = Queue(concurrent)
for i in range(concurrent):
t = Thread(target=doWork)
t.daemon = True
t.start()
try:
for url in url_routes:
q.put(url)
q.join()
except Exception:
pass
# Get results
calc_routes = [qres.get() for _ in range(len(url_routes))]
этот метод быстрее, чем requests_futures я думаю, но я не знаю, сколько потоков установить чтобы максимизировать это -
D-торнадо (не работает)
Я сейчас пытаюсь торнадо-однако не могу заставить его работать он ломается с существующим кодом -1073741819 если я использую curl-если я использую simple_httpclient он работает, но затем я получаю ошибки тайм-аута:
ошибка:торнадо.применение:множественные исключения в Traceback списка выхода (последний звонок): файл "C:Anaconda3libsite-packagestornadogen.py", линия 789, обратный вызов полученный список.добавление (f.result ()) File "C:Anaconda3libsite-packagestornadoconcurrent.py", линия 232, в результат raise_exc_info (self._exc_info) файл"", строка 3, в raise_exc_info торнадо.с помощью HttpClient.HTTPError: HTTP 599: Timeout
def handle_req(r):
try:
json_geocode = json_decode(r)
status = int(json_geocode['status'])
tot_time_s = json_geocode['route_summary']['total_time']
tot_dist_m = json_geocode['route_summary']['total_distance']
used_from = json_geocode['via_points'][0]
used_to = json_geocode['via_points'][1]
out = [status, tot_time_s, tot_dist_m, used_from[0], used_from[1], used_to[0], used_to[1]]
print(out)
except Exception as err:
print(err)
out = [999, 0, 0, 0, 0, 0, 0]
return out
# Configure
# For some reason curl_httpclient crashes my computer
AsyncHTTPClient.configure("tornado.simple_httpclient.SimpleAsyncHTTPClient", max_clients=10)
@gen.coroutine
def run_experiment(urls):
http_client = AsyncHTTPClient()
responses = yield [http_client.fetch(url) for url, qid in urls]
responses_out = [handle_req(r.body) for r in responses]
raise gen.Return(value=responses_out)
# Initialise
_ioloop = ioloop.IOLoop.instance()
run_func = partial(run_experiment, url_routes)
calc_routes = _ioloop.run_sync(run_func)
е - ввода-вывода / aiohttp
решил попробовать другой подход (хотя было бы здорово получить торнадо рабочих), используя ввода-вывода и aiohttp.
import asyncio
import aiohttp
def handle_req(data, qid):
json_geocode = json.loads(data.decode('utf-8'))
status = int(json_geocode['status'])
if status == 200:
tot_time_s = json_geocode['route_summary']['total_time']
tot_dist_m = json_geocode['route_summary']['total_distance']
used_from = json_geocode['via_points'][0]
used_to = json_geocode['via_points'][1]
out = [qid, status, tot_time_s, tot_dist_m, used_from[0], used_from[1], used_to[0], used_to[1]]
else:
print("Done, but not route for {0} - status: {1}".format(qid, status))
out = [qid, 999, 0, 0, 0, 0, 0, 0]
return out
def chunked_http_client(num_chunks):
# Use semaphore to limit number of requests
semaphore = asyncio.Semaphore(num_chunks)
@asyncio.coroutine
# Return co-routine that will download files asynchronously and respect
# locking fo semaphore
def http_get(url, qid):
nonlocal semaphore
with (yield from semaphore):
response = yield from aiohttp.request('GET', url)
body = yield from response.content.read()
yield from response.wait_for_close()
return body, qid
return http_get
def run_experiment(urls):
http_client = chunked_http_client(500)
# http_client returns futures
# save all the futures to a list
tasks = [http_client(url, qid) for url, qid in urls]
response = []
# wait for futures to be ready then iterate over them
for future in asyncio.as_completed(tasks):
data, qid = yield from future
try:
out = handle_req(data, qid)
except Exception as err:
print("Error for {0} - {1}".format(qid,err))
out = [qid, 999, 0, 0, 0, 0, 0, 0]
response.append(out)
return response
# Run:
loop = asyncio.get_event_loop()
calc_routes = loop.run_until_complete(run_experiment(url_routes))
это работает нормально, однако еще медленнее, чем многопроцессорная обработка!
4 ответов
глядя на ваш многопроцессорный код в верхней части вопроса. Кажется, что a HttpConnectionPool()
вызывается каждый раз при вызове ReqOsrm. Таким образом, для каждого url создается новый пул. Вместо этого, используйте initializer
и args
параметр для создания единого пула для каждого процесса.
conn_pool = None
def makePool(host, port):
global conn_pool
pool = conn_pool = HTTPConnectionPool(host=host, port=port, maxsize=1)
def ReqOsrm(url_input):
ul, qid = url_input
try:
response = conn_pool.request('GET', ul)
json_geocode = json.loads(response.data.decode('utf-8'))
status = int(json_geocode['status'])
if status == 200:
tot_time_s = json_geocode['route_summary']['total_time']
tot_dist_m = json_geocode['route_summary']['total_distance']
used_from, used_to = json_geocode['via_points']
out = [qid, status, tot_time_s, tot_dist_m, used_from[0], used_from[1], used_to[0], used_to[1]]
return out
else:
print("Done but no route: %d %s" % (qid, req_url))
return [qid, 999, 0, 0, 0, 0, 0, 0]
except Exception as err:
print("%s: %d %s" % (err, qid, req_url))
return [qid, 999, 0, 0, 0, 0, 0, 0]
if __name__ == "__main__":
# run:
pool = Pool(initializer=makePool, initargs=('127.0.0.1', 5005))
calc_routes = pool.map(ReqOsrm, url_routes)
pool.close()
pool.join()
версия request-futures, похоже, имеет ошибку отступа. Петля
for future in as_completed(futures):
имеет отступ под внешней петлей
for i in range(len(url_routes)):
. Так что запрос сделан в внешний цикл и внутренний цикл ожидает, что это будущее вернется до следующей итерации внешнего цикла. Это заставляет запросы выполняться последовательно, а не параллельно.
Я думаю, что код должен быть следующим:
calc_routes = []
futures = {}
with FuturesSession(executor=ThreadPoolExecutor(max_workers=1000)) as session:
# Submit all the requests and process in background
for i in range(len(url_routes)):
url_in, qid = url_routes[i] # url |query-id
future = session.get(url_in, background_callback=lambda sess, resp: ReqOsrm(sess, resp))
futures[future] = qid
# this was indented under the code in section B of the question
# process the futures as they become copmlete
for future in as_completed(futures):
r = future.result()
try:
row = [futures[future]] + r.data
except Exception as err:
print('No route')
row = [futures[future], 999, 0, 0, 0, 0, 0, 0]
print(row)
calc_routes.append(row)
спасибо всем за помощь. Я хотел опубликовать свои выводы:
поскольку мои HTTP-запросы относятся к локальному серверу, который обрабатывает запрос мгновенно, мне не имеет смысла использовать асинхронные подходы (по сравнению с большинством случаев, когда запросы отправляются через интернет). Дорогостоящим фактором для меня является отправка запроса и обработка обратной связи, что означает, что я получаю гораздо лучшие скорости, используя несколько процессов (потоки страдают от GIL). Я также должен использовать сеансы для увеличения скорости (нет необходимости закрывать и повторно открывать соединение с тем же сервером) и помогают предотвратить истощение портов.
вот все методы, которые пробовали (работают) с примером RPS:
серийный
S1. Последовательный запрос GET (без сеанса) - > 215 RPS
def ReqOsrm(data):
url, qid = data
try:
response = requests.get(url)
json_geocode = json.loads(response.content.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
return [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
return [qid, 999, 0, 0]
# Run:
calc_routes = [ReqOsrm(x) for x in url_routes]
S2. Последовательный запрос GET (запросы.Session ()) - > 335 RPS
session = requests.Session()
def ReqOsrm(data):
url, qid = data
try:
response = session.get(url)
json_geocode = json.loads(response.content.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
return [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
return [qid, 999, 0, 0]
# Run:
calc_routes = [ReqOsrm(x) for x in url_routes]
S3. Последовательный запрос GET (urllib3.HTTPConnectionPool) - > 545 RPS
conn_pool = HTTPConnectionPool(host=ghost, port=gport, maxsize=1)
def ReqOsrm(data):
url, qid = data
try:
response = conn_pool.request('GET', url)
json_geocode = json.loads(response.data.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
return [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
return [qid, 999, 0, 0]
# Run:
calc_routes = [ReqOsrm(x) for x in url_routes]
асинхронный ИО
A4. AsyncIO с aiohttp - > 450 RPS
import asyncio
import aiohttp
concurrent = 100
def handle_req(data, qid):
json_geocode = json.loads(data.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
return [qid, 200, tot_time_s, tot_dist_m]
def chunked_http_client(num_chunks):
# Use semaphore to limit number of requests
semaphore = asyncio.Semaphore(num_chunks)
@asyncio.coroutine
# Return co-routine that will download files asynchronously and respect
# locking fo semaphore
def http_get(url, qid):
nonlocal semaphore
with (yield from semaphore):
with aiohttp.ClientSession() as session:
response = yield from session.get(url)
body = yield from response.content.read()
yield from response.wait_for_close()
return body, qid
return http_get
def run_experiment(urls):
http_client = chunked_http_client(num_chunks=concurrent)
# http_client returns futures, save all the futures to a list
tasks = [http_client(url, qid) for url, qid in urls]
response = []
# wait for futures to be ready then iterate over them
for future in asyncio.as_completed(tasks):
data, qid = yield from future
try:
out = handle_req(data, qid)
except Exception as err:
print("Error for {0} - {1}".format(qid,err))
out = [qid, 999, 0, 0]
response.append(out)
return response
# Run:
loop = asyncio.get_event_loop()
calc_routes = loop.run_until_complete(run_experiment(url_routes))
A5. Резьба без сеансов - > 330 RPS
from threading import Thread
from queue import Queue
concurrent = 100
def doWork():
while True:
url,qid = q.get()
status, resp = getReq(url)
processReq(status, resp, qid)
q.task_done()
def getReq(url):
try:
resp = requests.get(url)
return resp.status_code, resp
except:
return 999, None
def processReq(status, resp, qid):
try:
json_geocode = json.loads(resp.content.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
out = [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
print("Error: ", err, qid, url)
out = [qid, 999, 0, 0]
qres.put(out)
return
#Run:
qres = Queue()
q = Queue(concurrent)
for i in range(concurrent):
t = Thread(target=doWork)
t.daemon = True
t.start()
for url in url_routes:
q.put(url)
q.join()
# Get results
calc_routes = [qres.get() for _ in range(len(url_routes))]
A6. Потоковая передача с HTTPConnectionPool - > 1550 RPS
from threading import Thread
from queue import Queue
from urllib3 import HTTPConnectionPool
concurrent = 100
conn_pool = HTTPConnectionPool(host=ghost, port=gport, maxsize=concurrent)
def doWork():
while True:
url,qid = q.get()
status, resp = getReq(url)
processReq(status, resp, qid)
q.task_done()
def getReq(url):
try:
resp = conn_pool.request('GET', url)
return resp.status, resp
except:
return 999, None
def processReq(status, resp, qid):
try:
json_geocode = json.loads(resp.data.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
out = [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
print("Error: ", err, qid, url)
out = [qid, 999, 0, 0]
qres.put(out)
return
#Run:
qres = Queue()
q = Queue(concurrent)
for i in range(concurrent):
t = Thread(target=doWork)
t.daemon = True
t.start()
for url in url_routes:
q.put(url)
q.join()
# Get results
calc_routes = [qres.get() for _ in range(len(url_routes))]
A7. запросы-фьючерсы - > 520 RPS
from requests_futures.sessions import FuturesSession
from concurrent.futures import ThreadPoolExecutor, as_completed
concurrent = 100
def ReqOsrm(sess, resp):
try:
json_geocode = resp.json()
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
out = [200, tot_time_s, tot_dist_m]
except Exception as err:
print("Error: ", err)
out = [999, 0, 0]
resp.data = out
#Run:
calc_routes = []
futures = {}
with FuturesSession(executor=ThreadPoolExecutor(max_workers=concurrent)) as session:
# Submit requests and process in background
for i in range(len(url_routes)):
url_in, qid = url_routes[i] # url |query-id
future = session.get(url_in, background_callback=lambda sess, resp: ReqOsrm(sess, resp))
futures[future] = qid
# Process the futures as they become complete
for future in as_completed(futures):
r = future.result()
try:
row = [futures[future]] + r.data
except Exception as err:
print('No route')
row = [futures[future], 999, 0, 0]
calc_routes.append(row)
Несколько Процессов
P8. многопроцессорный.рабочий + очередь + запросы.сессии() -> 1058 RPS
from multiprocessing import *
class Worker(Process):
def __init__(self, qin, qout, *args, **kwargs):
super(Worker, self).__init__(*args, **kwargs)
self.qin = qin
self.qout = qout
def run(self):
s = requests.session()
while not self.qin.empty():
url, qid = self.qin.get()
data = s.get(url)
self.qout.put(ReqOsrm(data, qid))
self.qin.task_done()
def ReqOsrm(resp, qid):
try:
json_geocode = json.loads(resp.content.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
return [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
print("Error: ", err, qid)
return [qid, 999, 0, 0]
# Run:
qout = Queue()
qin = JoinableQueue()
[qin.put(url_q) for url_q in url_routes]
[Worker(qin, qout).start() for _ in range(cpu_count())]
qin.join()
calc_routes = []
while not qout.empty():
calc_routes.append(qout.get())
P9. многопроцессорный.рабочий + очередь + HTTPConnectionPool () - > 1230 RPS
P10. multiprocessing v2 (не совсем уверен, как это отличается) - > 1350 RPS
conn_pool = None
def makePool(host, port):
global conn_pool
pool = conn_pool = HTTPConnectionPool(host=host, port=port, maxsize=1)
def ReqOsrm(data):
url, qid = data
try:
response = conn_pool.request('GET', url)
json_geocode = json.loads(response.data.decode('utf-8'))
tot_time_s = json_geocode['paths'][0]['time']
tot_dist_m = json_geocode['paths'][0]['distance']
return [qid, 200, tot_time_s, tot_dist_m]
except Exception as err:
print("Error: ", err, qid, url)
return [qid, 999, 0, 0]
# Run:
pool = Pool(initializer=makePool, initargs=(ghost, gport))
calc_routes = pool.map(ReqOsrm, url_routes)
Итак, в заключение кажется, что лучшие методы для меня #10 (и удивительно #6)
Вопрос 1
вы получаете ошибку, потому что этот подход:
def ReqOsrm(url_input):
req_url, query_id = url_input
try_c = 0
#print(req_url)
while try_c < 5:
try:
response = requests.get(req_url)
json_geocode = response.json()
status = int(json_geocode['status'])
# Found route between points
if status == 200:
....
pool = Pool(cpu_count()-1)
calc_routes = pool.map(ReqOsrm, url_routes)
создает новое TCP-соединение для каждого запрошенного URL-адреса, и в какой-то момент он терпит неудачу только потому, что в системе нет свободных локальных портов. Чтобы подтвердить, что вы можете запустить netstat
во время выполнения кода:
netstat -a -n | find /c "localhost:5005"
Это даст вам несколько подключений к серверу.
кроме того, достижение 1700 RPS выглядит довольно нереалистичным для этого подхода, так как requests.get
довольно дорогая деятельность и маловероятно что вы можете получить даже 50 РПС этот путь. Таким образом, вам, вероятно, нужно дважды проверить свои расчеты RPS.
чтобы избежать ошибки, вам нужно использовать сеансы вместо создания соединений с нуля:
import multiprocessing
import requests
import time
class Worker(multiprocessing.Process):
def __init__(self, qin, qout, *args, **kwargs):
super(Worker, self).__init__(*args, **kwargs)
self.qin = qin
self.qout = qout
def run(self):
s = requests.session()
while not self.qin.empty():
result = s.get(self.qin.get())
self.qout.put(result)
self.qin.task_done()
if __name__ == '__main__':
start = time.time()
qin = multiprocessing.JoinableQueue()
[qin.put('http://localhost:8080/') for _ in range(10000)]
qout = multiprocessing.Queue()
[Worker(qin, qout).start() for _ in range(multiprocessing.cpu_count())]
qin.join()
result = []
while not qout.empty():
result.append(qout.get())
print time.time() - start
print result
Вопрос 2
вы не получите более высокий RPS с потоками или асинхронными подходами, если только ввод-вывод не займет больше времени, чем вычисления (например, высокая сетевая задержка, большие ответы и т. д.), поскольку потоки зависят от GIL, так как запуск в том же процессе Python и асинхронные библиотеки могут быть заблокированы длительными вычислениями.
хотя потоки или асинхронные библиотеки могут повысить производительность, выполнение одного и того же потокового или асинхронного кода в нескольких процессах даст вам еще большую производительность.
вот шаблон я использовал с gevent, который сопрограмма и не страдать от Гила. Это может быть быстрее, чем использование потоков, и, возможно, быстрее всего при использовании в сочетании с многопроцессорной обработкой (в настоящее время используется только 1 ядро):
from gevent import monkey
monkey.patch_all()
import logging
import random
import time
from threading import Thread
from gevent.queue import JoinableQueue
from logger import initialize_logger
initialize_logger()
log = logging.getLogger(__name__)
class Worker(Thread):
def __init__(self, worker_idx, queue):
# initialize the base class
super(Worker, self).__init__()
self.worker_idx = worker_idx
self.queue = queue
def log(self, msg):
log.info("WORKER %s - %s" % (self.worker_idx, msg))
def do_work(self, line):
#self.log(line)
time.sleep(random.random() / 10)
def run(self):
while True:
line = self.queue.get()
self.do_work(line)
self.queue.task_done()
def main(number_of_workers=20):
start_time = time.time()
queue = JoinableQueue()
for idx in range(number_of_workers):
worker = Worker(idx, queue)
# "daemonize" a thread to ensure that the threads will
# close when the main program finishes
worker.daemon = True
worker.start()
for idx in xrange(100):
queue.put("%s" % idx)
queue.join()
time_taken = time.time() - start_time
log.info("Parallel work took %s seconds." % time_taken)
start_time = time.time()
for idx in xrange(100):
#log.info(idx)
time.sleep(random.random() / 10)
time_taken = time.time() - start_time
log.info("Sync work took %s seconds." % time_taken)
if __name__ == "__main__":
main()