از طریق منوی جستجو مطلب مورد نظر خود در وبلاگ را به سرعت پیدا کنید
تجزیه و تحلیل عملکرد پایتون ناهمزمان در مقابل سنکرون
سرفصلهای مطلب
معرفی
این مقاله قسمت دوم یک مجموعه است روی استفاده از پایتون برای توسعه برنامه های وب ناهمزمان بخش اول پوشش عمیق تری از همزمانی در پایتون و asyncio
، همچنین aiohttp
.
اگر مایلید در مورد Python Asynchronous for Web Development بیشتر بخوانید، ما آن را پوشش داده ایم.
با توجه به ماهیت غیر مسدود کننده کتابخانه های ناهمزمان مانند aiohttp
امیدواریم بتوانیم درخواست های بیشتری را در مدت زمان معین نسبت به کدهای همگام آنالوگ ایجاد و رسیدگی کنیم. این به دلیل این واقعیت است که کد ناهمزمان می تواند به سرعت بین زمینه ها جابجا شود تا زمان صرف شده برای انتظار I/O را به حداقل برساند.
عملکرد سمت مشتری در مقابل عملکرد سمت سرور
آزمایش عملکرد سمت سرویس گیرنده یک کتابخانه ناهمزمان مانند aiohttp
نسبتاً ساده است. ما برخی از وبسایتها را به عنوان مرجع انتخاب میکنیم، و سپس تعداد معینی از درخواستها را انجام میدهیم، زمانبندی زمانی که کد ما برای تکمیل آنها طول میکشد. ما عملکرد نسبی را بررسی خواهیم کرد aiohttp
و requests
هنگام درخواست به https://example.com
.
تست عملکرد سمت سرور کمی دشوارتر است. کتابخانه ها مانند aiohttp
دارای سرورهای توسعه داخلی است که برای آزمایش مسیرها مناسب هستند روی یک شبکه محلی با این حال، این سرورهای توسعه برای استقرار برنامه ها مناسب نیستند روی وب عمومی، زیرا نمی توانند بار مورد انتظار از یک وب سایت در دسترس عموم را تحمل کنند، و در ارائه دارایی های ثابت مانند جاوا اسکریپت، CSS و فایل های تصویری خوب نیستند.
به منظور درک بهتر عملکرد نسبی aiohttp
و یک چارچوب وب همزمان مشابه، میخواهیم برنامه وب خود را با استفاده از آن دوباره پیادهسازی کنیم فلاسک و سپس سرورهای توسعه و تولید را برای هر دو پیاده سازی مقایسه خواهیم کرد.
برای سرور تولید، ما از آن استفاده خواهیم کرد اسب شاخدار.
Client-Side: aiohttp در مقابل درخواست ها
برای یک رویکرد سنتی و همزمان، ما فقط از یک روش ساده استفاده می کنیم for
حلقه اگرچه، قبل از اجرای کد، مطمئن شوید که ماژول درخواست ها را نصب کرده اید:
$ pip install --user requests
با وجود این موضوع، بیایید جلو برویم و آن را به شیوهای سنتیتر اجرا کنیم:
import requests
def main():
n_requests = 100
url = "https://example.com"
session = requests.Session()
for i in range(n_requests):
print(f"making request {i} to {url}")
resp = session.get(url)
if resp.status_code == 200:
pass
main()
هر چند کد ناهمزمان آنالوگ کمی پیچیده تر است. ایجاد چندین درخواست با aiohttp
اهرمی است asyncio.gather
روش درخواست همزمان:
import asyncio
import aiohttp
async def make_request(session, req_n):
url = "https://example.com"
print(f"making request {req_n} to {url}")
async with session.get(url) as resp:
if resp.status == 200:
await resp.text()
async def main():
n_requests = 100
async with aiohttp.ClientSession() as session:
await asyncio.gather(
*(make_request(session, i) for i in range(n_requests))
)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
اجرای کدهای همزمان و ناهمزمان با bash زمان سودمندی:
me@local:~$ time python multiple_sync_requests.py
real 0m13.112s
user 0m1.212s
sys 0m0.053s
me@local:~$ time python multiple_async_requests.py
real 0m1.277s
user 0m0.695s
sys 0m0.054s
کد همزمان/ناهمزمان بسیار سریعتر است. اما اگر کد سنکرون را چند رشته ای کنیم چه اتفاقی می افتد؟ آیا می تواند با سرعت کد همزمان مطابقت داشته باشد؟
import threading
import argparse
import requests
def create_parser():
parser = argparse.ArgumentParser(
description="Specify the number of threads to use"
)
parser.add_argument("-nt", "--n_threads", default=1, type=int)
return parser
def make_requests(session, n, url, name=""):
for i in range(n):
print(f"{name}: making request {i} to {url}")
resp = session.get(url)
if resp.status_code == 200:
pass
def main():
parsed = create_parser().parse_args()
n_requests = 100
n_requests_per_thread = n_requests // parsed.n_threads
url = "https://example.com"
session = requests.Session()
threads = (
threading.Thread(
target=make_requests,
args=(session, n_requests_per_thread, url, f"thread_{i}")
) for i in range(parsed.n_threads)
)
for t in threads:
t.start()
for t in threads:
t.join()
main()
با اجرای این قطعه کد نسبتاً پرمخاطب به دست می آید:
me@local:~$ time python multiple_sync_request_threaded.py -nt 10
real 0m2.170s
user 0m0.942s
sys 0m0.104s
و ما می توانیم با استفاده از رشته های بیشتر عملکرد را افزایش دهیم، اما بازده به سرعت کاهش می یابد:
me@local:~$ time python multiple_sync_request_threaded.py -nt 20
real 0m1.714s
user 0m1.126s
sys 0m0.119s
با معرفی رشته، میتوانیم به تطابق عملکرد کد ناهمزمان، به قیمت افزایش پیچیدگی کد، نزدیک شویم.
در حالی که زمان پاسخگویی مشابهی را ارائه می دهد، اما برای قیمت پیچیده کد که می تواند ساده باشد ارزش آن را ندارد – کیفیت کد با پیچیدگی یا تعداد خطوطی که استفاده می کنیم افزایش نمی یابد.
سمت سرور: aiohttp در مقابل Flask
ما استفاده خواهیم کرد بنچمارک آپاچی (ab) ابزاری برای آزمایش عملکرد سرورهای مختلف.
با ab
ما میتوانیم تعداد کل درخواستهایی را که باید انجام شود، علاوه بر تعداد آنها مشخص کنیم هم زمان درخواست هایی برای ایجاد
قبل از شروع آزمایش، باید برنامه ردیاب سیاره خود را (از مقاله قبلی) با استفاده از یک چارچوب همزمان مجدداً پیاده سازی کنیم. استفاده خواهیم کرد Flask
، همانطور که API مشابه است aiohttp
(در واقع aiohttp
مسیریابی API بر اساس Flask
):
from flask import Flask, jsonify, render_template, request
from planet_tracker import PlanetTracker
__all__ = ("app")
app = Flask(__name__, static_url_path="",
static_folder="./client",
template_folder="./client")
@app.route("/planets/<planet_name>", methods=("GET"))
def get_planet_ephmeris(planet_name):
data = request.args
try:
geo_location_data = {
"lon": str(data("lon")),
"lat": str(data("lat")),
"elevation": float(data("elevation"))
}
except KeyError as err:
geo_location_data = {
"lon": "-0.0005",
"lat": "51.4769",
"elevation": 0.0,
}
print(f"get_planet_ephmeris: {planet_name}, {geo_location_data}")
tracker = PlanetTracker()
tracker.lon = geo_location_data("lon")
tracker.lat = geo_location_data("lat")
tracker.elevation = geo_location_data("elevation")
planet_data = tracker.calc_planet(planet_name)
return jsonify(planet_data)
@app.route('/')
def hello():
return render_template("index.html")
if __name__ == "__main__":
app.run(
host="localhost",
port=8000,
threaded=True
)
اگر بدون خواندن مقاله قبلی وارد می شوید، باید پروژه خود را کمی قبل از آزمایش تنظیم کنیم. من تمام کدهای سرور پایتون را در یک دایرکتوری قرار داده ام planettracker
، خود یک زیر شاخه از پوشه خانه من است.
me@local:~/planettracker$ ls
planet_tracker.py
flask_app.py
aiohttp_app.py
اکیداً پیشنهاد می کنم که از مقاله قبلی دیدن کنید و قبل از ادامه با برنامه ای که قبلاً ساخته ایم آشنا شوید.
سرورهای توسعه aiohttp و Flask
بیایید ببینیم چقدر طول میکشد تا سرورهای ما به 1000 درخواست رسیدگی کنند که هر بار 20 درخواست انجام شده است.
اول، من دو را باز می کنم terminal پنجره ها. در اول، سرور را اجرا می کنم:
me@local:~/planettracker$ pipenv run python aiohttp_app.py
در دومی، بیایید اجرا کنیم ab
:
me@local:~/planettracker$ ab -k -c 20 -n 1000 "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
...
Concurrency Level: 20
Time taken for tests: 0.494 seconds
Complete requests: 1000
Failed requests: 0
Keep-Alive requests: 1000
Total transferred: 322000 bytes
HTML transferred: 140000 bytes
Requests per second: 1402.08 (\
Time per request: 9.886 (ms) (mean)
Time per request: 0.494 (ms) (mean, across all concurrent requests)
Transfer rate: 636.16 (Kbytes/sec) received
...
ab
اطلاعات زیادی را خروجی می دهد و من فقط مرتبط ترین بیت را نمایش داده ام. از این تعداد، قسمتی که باید به آن بیشتر توجه کنیم، فیلد «درخواستها در ثانیه» است.
اکنون، با خروج از سرور در پنجره اول، اجازه می دهد تا ما را روشن کند Flask
برنامه:
me@local:~/planettracker$ pipenv run python flask_app.py
اجرای مجدد اسکریپت تست:
me@local:~/planettracker$ ab -k -c 20 -n 1000 "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
...
Concurrency Level: 20
Time taken for tests: 1.385 seconds
Complete requests: 1000
Failed requests: 0
Keep-Alive requests: 0
Total transferred: 210000 bytes
HTML transferred: 64000 bytes
Requests per second: 721.92 (\
Time per request: 27.704 (ms) (mean)
Time per request: 1.385 (ms) (mean, across all concurrent requests)
Transfer rate: 148.05 (Kbytes/sec) received
...
به نظر می رسد aiohttp
برنامه 2.5 برابر تا 3 برابر سریعتر از برنامه است Flask
هنگام استفاده از سرور توسعه مربوطه هر کتابخانه.
اگر استفاده کنیم چه اتفاقی می افتد gunicorn
برای ارائه برنامه های ما؟
aiohttp و Flask همانطور که توسط گانیکورن سرو می شود
قبل از اینکه بتوانیم برنامه های خود را در حالت تولید آزمایش کنیم، ابتدا باید آن را نصب کنیم gunicorn
و روش اجرای برنامه های خود را با استفاده از برنامه مناسب بیابید gunicorn
طبقه کارگر به منظور آزمایش Flask
برنامه ما می توانیم از استاندارد استفاده کنیم gunicorn
کارگر، اما برای aiohttp
ما باید از gunicorn
کارگر همراه با aiohttp
. میتونیم نصب کنیم gunicorn
با pipenv:
me@local~/planettracker$ pipenv install gunicorn
ما می توانیم اجرا کنیم aiohttp
برنامه با مناسب gunicorn
کارگر:
me@local:~/planettracker$ pipenv run gunicorn aiohttp_app:app --worker-class aiohttp.GunicornWebWorker
حرکت به جلو، هنگام نمایش ab
نتایج آزمون من فقط برای اختصار فیلد «درخواستها در ثانیه» را نشان میدهم:
me@local:~/planettracker$ ab -k -c 20 -n 1000 "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
...
Requests per second: 2396.24 (\
...
حالا بیایید ببینیم چگونه Flask
کرایه برنامه:
me@local:~/planettracker$ pipenv run gunicorn flask_app:app
تست کردن با ab
:
me@local:~/planettracker$ ab -k -c 20 -n 1000 "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
...
Requests per second: 1041.30 (\
...
استفاده کردن gunicorn
قطعا منجر به افزایش عملکرد برای هر دو می شود aiohttp
و Flask
برنامه ها این aiohttp
برنامه همچنان عملکرد بهتری دارد، اگرچه نه به اندازه سرور توسعه.
gunicorn
به ما امکان می دهد از چندین کارگر برای ارائه برنامه های خود استفاده کنیم. ما می توانیم استفاده کنیم -w
آرگومان خط فرمان برای گفتن gunicorn
برای ایجاد فرآیندهای کارگری بیشتر استفاده از 4 کارگر منجر به افزایش عملکرد قابل توجهی برای برنامههای ما میشود:
me@local:~/planettracker$ pipenv run gunicorn aiohttp_app:app -w 4
تست کردن با ab
:
me@local:~/planettracker$ ab -k -c 20 -n 1000 "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
...
Requests per second: 2541.97 (\
...
در حال حرکت روی را Flask
نسخه:
me@local:~/planettracker$ pipenv run gunicorn flask_app:app -w 4
تست کردن با ab
:
me@local:~/planettracker$ ab -k -c 20 -n 1000 "localhost:8000/planets/mars؟lon=145.051&lat=-39.754&elevation=0"
...
Requests per second: 1729.17 (\
...
این Flask
برنامه هنگام استفاده از چندین کارگر افزایش قابل توجهی در عملکرد داشت!
جمع بندی نتایج
بیایید یک قدم به عقب برگردیم و به نتایج آزمایش سرورهای توسعه و تولید برای هر دو نگاه کنیم aiohttp
و Flask
پیاده سازی برنامه ردیاب سیاره ما در یک جدول:
aiohttp | فلاسک | ٪ تفاوت | |
---|---|---|---|
سرور توسعه (درخواست/ثانیه) | 1402.08 | 721.92 | 180.24 |
گانیکورن (درخواستها/ثانیه) | 2396.24 | 1041.30 | 130.12 |
درصد افزایش نسبت به سرور توسعه | 18.45 | 44.24 | |
gunicorn -w 4 (درخواستها/ثانیه) | 2541.97 | 1729.17 | 47.01 |
درصد افزایش نسبت به سرور توسعه | 25.65 | 139.52 |
نتیجه
در این مقاله، عملکرد یک برنامه وب ناهمزمان را در مقایسه با همتای همگام آن مقایسه کرده ایم و از چندین ابزار برای این کار استفاده کرده ایم.
استفاده از کتابخانه های ناهمزمان پایتون و تکنیک های برنامه نویسی این پتانسیل را دارد که یک برنامه کاربردی را سرعت بخشد، چه درخواست های آن به سرور راه دور یا
رسیدگی به درخواست های دریافتی
(برچسبها به ترجمه)# python
منتشر شده در 1403-01-26 04:30:03