کاهش مصرف حافظه و افزایش سرعت اجرای کد پایتون با Generator — راهنمای کاربردی

۴۲۲ بازدید
آخرین به‌روزرسانی: ۲۵ فروردین ۱۴۰۳
زمان مطالعه: ۳ دقیقه
کاهش مصرف حافظه و افزایش سرعت اجرای کد پایتون با Generator — راهنمای کاربردی

اغلب افرادی که با Generator-های پایتون آشنایی ندارند یا به تازگی آشنا شده‌اند، در مورد اهمیت این مفهوم به دیده اغماض می‌نگرند. Generator-ها به طور خاص در زمینه یادگیری ماشین برای نوشتن تابع‌های سفارشی نقش بسیار مهمی ایفا می‌کنند. در این مقاله با روش کاهش مصرف حافظه و افزایش سرعت اجرای کد پایتون با Generator آشنا خواهیم شد.

تابع‌های Generator این امکان را فراهم می‌سازند که تابعی اعلان کنیم که مانند یک تکرارکننده (iterator) عمل می‌کند. این نوع از تابع‌ها به برنامه‌نویس امکان می‌دهند یک تکرارکننده را به روشی سریع، آسان و تمیز ایجاد کند. منظور از تکرارکننده شیئی است که می‌تواند (در یک حلقه) تکرار شود. از تکرارکننده برای تجرید یک کانتینر داده‌ها استفاده می‌شود تا طوری رفتار کند که گویی یک شیء تکرارپذیر است. برخی از موارد رایج از اشیای تکرارپذیر شامل رشته‌ها، لیست‌ها و دیکشنری‌ها هستند. Generator تا حد زیادی شبیه یک تابع است، اما به جای return از کلیدواژه yield استفاده می‌کند. برای درک بهتر یک مثال را بررسی می‌کنیم:

1def generate_numbers():
2    n = 0
3    while n < 3:
4        yield n
5        n += 1

این یک تابع generator است و زمانی که آن را فراخوانی کنیم، یک شیء generator بازگشت می‌دهد:

1>>> numbers = generate_numbers()
2>>> type(numbers)
3<class 'generator'>

نکته مهمی که باید توجه داشته باشیم این است که حالت (State) درون بدنه تابع generator قرار گرفته است. امکان حرکت به صورت گام به گام با استفاده از تابع داخلی next()‎ نیز وجود دارد:

1>>> next_number = generate_numbers()
2>>> next(next_number)
30
4>>> next(next_number)
51
6>>> next(next_number)
72

فراخوانی ()next پس از رسیدن به انتها

StopIteration یک نوع استثنای داخلی است که به صورت خودکار در مواردی که generator نتواند yield کند صادر می‌شود. این نشانه‌ای از این است که حلقه باید متوقف شود.

گزاره yield

وظیفه اصلی گزاره yield کنترل گردش تابع generator به روشی است که مانند گزاره return به نظر رسد. هنگامی که یک تابع generator را فرا می‌خوانید یا از یک عبارت generator استفاده می‌کنید، یک تکرارکننده خاص به نام generator دریافت می‌شود. می‌توان این generator را به یک متغیر انتساب داد تا از آن استفاده کند. زمانی که متدهای خاص روی generator از قبیل ()next را فراخوانی می‌کنید، کد درون تابع تا yield اجرا می‌شود.

هنگامی که پایتون با گزاره yield مواجه شود، برنامه اجرای تابع را معلق می‌کند و مقدار yield شده را به فراخوانی کننده بازگشت می‌دهد. این وضعیت مخالف گزاره return است که اجرای تابع را به طور کامل متوقف می‌کند. زمانی که یک تابع تعلیق شود، حالت تابع ذخیره می‌شود. اکنون که با generator پایتون آشنا شدید، نوبت آن رسیده است که رویکرد معمولی را با رویکردی که از generator استفاده می‌کند از نظر مصرف حافظه و زمان مورد نیاز برای اجرای کد مقایسه کنیم.

صورت مسئله

فرض کنید مجبور هستیم روی یک لیست بزرگی از اعداد (مثلاً 100000000 عدد) حلقه‌ای تعریف کنیم و مربع همه این اعداد را در یک لیست جداگانه ذخیره کنیم.

رویکرد معمول

1import memory_profiler
2import time
3def check_even(numbers):
4    even = []
5    for num in numbers:
6        if num % 2 == 0: 
7            even.append(num*num)
8            
9    return even
10if __name__ == '__main__':
11    m1 = memory_profiler.memory_usage()
12    t1 = time.clock()
13    cubes = check_even(range(100000000))
14    t2 = time.clock()
15    m2 = memory_profiler.memory_usage()
16    time_diff = t2 - t1
17    mem_diff = m2[0] - m1[0]
18    print(f"It took {time_diff} Secs and {mem_diff} Mb to execute this method")

با اجرای کد فوق خروجی زیر به دست می‌آید:

It took 21.876470000000005 Secs and 1929.703125 Mb to execute this method

رویکرد Generator

1import memory_profiler
2import time
3def check_even(numbers):
4    for num in numbers:
5        if num % 2 == 0:
6            yield num * num 
7    
8if __name__ == '__main__':
9    m1 = memory_profiler.memory_usage()
10    t1 = time.clock()
11    cubes = check_even(range(100000000))
12    t2 = time.clock()
13    m2 = memory_profiler.memory_usage()
14    time_diff = t2 - t1
15    mem_diff = m2[0] - m1[0]
16    print(f"It took {time_diff} Secs and {mem_diff} Mb to execute this method")

با اجرای کد فوق خروجی زیر به دست می‌آید:

It took 2.9999999995311555e-05 Secs and 0.02656277 Mb to execute this method

برای مقایسه بهتر تفاوت دو رویکرد در جدول زیر نشان داده شده است:

چنان که می‌بینید، هم مصرف حافظه و هم زمان مورد نیاز برای اجرای کد در زمان استفاده از Generator به میزان زیادی کاهش یافته است. Generator-ها تنها بنا به تقاضا کار می‌کنند و مشهور است که از طریق ارزیابی تنبل (lazy) عمل می‌کنند. این بدان معنی است که می‌توانند در مصرف CPU، حافظه و دیگر منابع محاسباتی صرفه‌جویی کنند.

سخن پایانی

در این بخش به جمع‌بندی مطالب طرح شده در این مقاله می‌پردازیم. در این راهنما نشان دادیم که generator-های پایتون می‌توانند برای کاهش مصرف حافظه و اجرای سریع‌تر کدها مورد استفاده قرار گیرند. مزیت اصلی آن‌ها در این واقعیت نهفته است که نتایج را در حافظه ذخیره نمی‌کنند، بلکه آن‌ها را به صورت درجا تولید می‌کنند و از این رو حافظه تنها زمانی استفاده می‌شود که نتیجه‌ای را از آن‌ها طلب کنیم. همچنین generator-ها اغلب بخش‌های کد از پیش آماده مورد نیاز برای نوشتن تکرارکننده‌ها را دور می‌اندازند که به کاهش اندازه کد نیز کمک می‌کند.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۷ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
towardsdatascience
۳ دیدگاه برای «کاهش مصرف حافظه و افزایش سرعت اجرای کد پایتون با Generator — راهنمای کاربردی»

بررسی و مقایسه شما به کل اشتباهه
تابع اول لیست رو محاسبه و برمیگردونه
تابع دوم صرفا جنراتور ساخته شده و برگردونده شده چیز معادلی محاسبه نشده که بیایم مقایسه کنیم. الا تازه باید جنراتور رو بندازیم تو فور و مقادیر رو محاسبه کنیم.

سلام و وقت بخیر؛

به‌طور کلی، جنریتورها به‌عنوان یک ویژگی قدرتمند در پایتون، می‌توانند در مصرف حافظه صرفه‌جویی کنند. چون مقادیر را در لحظه تولید می‌کنند به‌جای اینکه همه آن‌ها را در حافظه ذخیره کنند.

از همراهی شما با مجله فرادرس سپاسگزاریم.

سلام.
خیلی عالی با یک مثال ساده مزیت استفاده از generator را توضیح دادید.

در آموزش پایتون همراه با مثال های عملی هم به این موضوع پرداخته شده است.

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *