وبلاگ رسانگار
با ما حرفه ای باشید

سرور مجازی NVMe

تجزیه و تحلیل عملکرد پایتون ناهمزمان در مقابل سنکرون

0 2
زمان لازم برای مطالعه: 6 دقیقه


معرفی

این مقاله قسمت دوم یک مجموعه است روی استفاده از پایتون برای توسعه برنامه های وب ناهمزمان بخش اول پوشش عمیق تری از همزمانی در پایتون و 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

امتیاز شما به این مطلب
دیدگاه شما در خصوص مطلب چیست ؟

آدرس ایمیل شما منتشر نخواهد شد.

لطفا دیدگاه خود را با احترام به دیدگاه های دیگران و با توجه به محتوای مطلب درج کنید