مدیریت حافظه هنگام پردازش دادههای چند گیگابایتی با generator ها و streaming
چکیده
افزایش حجم داده های تولید شده و پردازش شده در سامانههای نرم افزاری، مدیریت حافظه را به یکی از چالشهای مهم در پردازش داده تبدیل کرده است. هنگامی که حجم یک مجموعه داده از ظرفیت حافظه اصلی بیشتر باشد، بارگذاری کامل داده در حافظه می تواند موجب افزایش شدید مصرف RAM و در مواردی شکست اجرای برنامه شود. یکی از راهکارهای مقابله با این مسئله، پردازش داده به صورت تدریجی و جلوگیری از نگهداری هم زمان کل مجموعه داده در حافظه است. در زبان Python، Iterator و Generator امکان تولید و مصرف تدریجی داده را فراهم می کنند و Generator Expression نیز می تواند از ایجاد هم زمان کل نتایج جلوگیری کند. در سطح سیستمهای پردازش داده، Streaming نیز داده را به صورت تدریجی و معمولاً با محدودیت منابع پردازش می کند. زمانی که مجموعه داده از ظرفیت حافظه اصلی بزرگتر است، استفاده از حافظه خارجی، تقسیم داده و روشهای دسترسی تدریجی میتواند امکان پردازش دادههای بزرگ را فراهم کند. برای ارزیابی تجربی، سه روش Full Loading، Generator-based Processing و Chunk-based Streaming در نظر گرفته شده و معیارهای Peak Memory، زمان اجرا و Throughput برای مقایسه آنها استفاده میشود. همچنین با تغییر اندازه Chunk، رابطه میان مصرف حافظه و کارایی پردازش بررسی خواهد شد.
مقدمه
رشد سریع حجم دادهها باعث شده است بسیاری از برنامههای پردازشی با مجموعهدادههایی مواجه شوند که نگهداری کامل آنها در حافظه اصلی امکانپذیر یا بهینه نیست. در روش ساده بارگذاری داده، ابتدا تمام Dataset در حافظه قرار میگیرد و سپس عملیات پردازشی روی آن انجام میشود. این روش در Datasetهای کوچک مناسب است، اما با افزایش حجم داده، مقدار حافظه موردنیاز نیز افزایش پیدا میکند و در صورت عبور نیاز حافظه از ظرفیت RAM، اجرای برنامه میتواند با خطای کمبود حافظه مواجه شود.
در Stream Processing مسئله از زاویه دیگری مطرح میشود. یک Data Stream بهصورت تدریجی در طول زمان تولید میشود و الزاماً پیش از شروع پردازش بهطور کامل در دسترس نیست. بنابراین سامانههای Stream Processing باید عناصر داده را بهصورت on-the-fly و با منابع حافظه محدود پردازش کنند.
در سطح زبان Python، Iteratorها امکان دسترسی ترتیبی به دادهها را فراهم میکنند. مستندات رسمی پایتون، Iterator را شیئی معرفی میکند که عناصر یک جریان داده را یک به یک ارائه میدهد. Generatorها نیز نوع خاصی از سازوکار تولید Iterator هستند که با استفاده از «yield» مقادیر را به صورت تدریجی تولید میکنند .
از این رو، استفاده از Generator در کنار منبع دادهای که خود به صورت تدریجی خوانده میشود، میتواند راهکاری برای جلوگیری از materialization کل Dataset در حافظه باشد. استفاده از Generator الزاماً به معنای کاهش مصرف حافظه کل برنامه نیست؛ زیرا اگر داده در مرحله دیگری از Pipeline بهصورت یک List یا ساختار بزرگ دیگر ذخیره شود، بخش عمدهای از مصرف حافظه همچنان باقی خواهد ماند.
بیان مسئله
فرض میشود Datasetی با حجم چند گیگابایت در اختیار برنامه قرار دارد. اگر برنامه از روشی مانند زیر استفاده کند:
data = load_entire_dataset()
for item in data:
process(item)
کل داده پیش از شروع پردازش در حافظه قرار میگیرد. در Datasetهای بزرگ، این روش میتواند بخش قابل توجهی از RAM را اشغال کند.
راهکار دیگر این است که داده به صورت تدریجی خوانده شود:
for item in read_data():
process(item)
در این حالت، بهجای materialize کردن کل Dataset، داده میتواند بهصورت عنصر به عنصر یا در بخشهای کوچکتر وارد Pipeline پردازش شود.
این ایده در مقیاس بزرگتر با مفاهیم Streaming و Out-of-Core Processing ارتباط پیدا میکند. وقتی Dataset از حافظه اصلی بزرگتر است، روشهایی مانند تقسیم Dataset به بخشهای کوچکتر، پردازش ترتیبی بخشها و ادغام نتایج جزئی میتوانند امکان پردازش داده را با حافظه محدود فراهم کنند .
بنابراین مسئله پژوهش فقط این نیست که آیا Generator حافظه کمتری مصرف میکند یا خیر؛ بلکه باید بررسی شود:
۱) مصرف حافظه در روش Full Loading چگونه با افزایش حجم Dataset تغییر میکند؟
۲) استفاده از Generator چه تأثیری بر Peak Memory دارد؟
۳) Streaming یا Chunk-based Processing چه تفاوتی با Generator دارد؟
۴) آیا کاهش مصرف حافظه باعث افزایش زمان اجرا میشود؟
۵) اندازه Chunk چه تأثیری بر تعادل میان Memory و Performance دارد؟
حافظه اصلی و پردازش دادههای بزرگ
حافظه اصلی یکی از منابع محدود در اجرای برنامههای پردازشی است. هرگاه برنامه دادههای بیشتری نسبت به ظرفیت حافظه اصلی نیاز داشته باشد، روشهای پردازش درونحافظهای با محدودیت مواجه میشوند. یکی از اصول اساسی پردازش دادههای بزرگ، محدود کردن مقدار دادهای است که در هر لحظه در حافظه اصلی قرار میگیرد.
Iterator
Iterator یکی از مفاهیم پایه در Python برای پردازش ترتیبی داده است. طبق مستندات رسمی پایتون، Iterator شیئی است که جریان داده را عنصر به عنصر ارائه میکند و با استفاده از __next__() عنصر بعدی را برمیگرداند. هنگامی که جریان داده به پایان برسد، «StopIteration» ایجاد میشود .
این مدل با ساختاری که تمام نتایج را از ابتدا در یک List ذخیره میکند متفاوت است. Iterator امکان میدهد مصرفکننده داده، هر عنصر را در زمان نیاز دریافت کند. برای مثال:
iterator = iter(data)
item = next(iterator)
با این حال، باید میان Iterator و Memory Efficiency تفاوت قائل شد. اگر «data» از قبل یک List چندگیگابایتی باشد، استفاده از Iterator روی آن باعث حذف List از حافظه نمیشود. بنابراین برای کاهش Memory Footprint باید منبع داده نیز به شکل مناسب طراحی شود.
Generator
Generator روشی برای ایجاد Iterator است که نوشتن Iteratorهای قابل استفاده مجدد را ساده میکند. در Python، تابعی که از «yield» استفاده کند بهعنوان Generator Function شناخته میشود. هنگام فراخوانی چنین تابعی، Generator Object ایجاد میشود و اجرای تابع در هر مرحله تا رسیدن به «yield» ادامه پیدا میکند. سپس وضعیت اجرای تابع حفظ شده و در درخواست بعدی از همان نقطه ادامه مییابد.
def generate_numbers(n):
for i in range(n):
yield i
for number in generate_numbers(1000000):
process(number)
در این ساختار، تابع یک List شامل یک میلیون عدد تولید نمیکند، بلکه مقادیر را بهصورت تدریجی ارائه میکند. اسلاتکین در همین راستا توصیه میکند در مواردی که خروجی یک تابع میتواند بهصورت متوالی تولید شود، بهجای بازگرداندن یک List کامل از Generator استفاده شود، زیرا این کار از تخصیص غیرضروری حافظه جلوگیری میکند؛ او همچنین برای ترکیب چند Generator متوالی در یک Pipeline بدون تجمیع نتایج میانی در حافظه، استفاده از «yield from» را پیشنهاد میدهد .
Lazy Evaluation
یکی از ویژگیهای مهم Generatorها، امکان محاسبه و ارائه مقادیر هنگام نیاز است. مستندات Python در مقایسه Generator Expression و List Comprehension توضیح میدهد که Generator Expression یک Iterator تولید میکند که مقادیر را در صورت نیاز محاسبه میکند، در حالی که List Comprehension یک List ایجاد میکند .
result = [process(x) for x in data] # List Comprehension
result = (process(x) for x in data) # Generator Expression
در حالت اول، نتیجه بهصورت یک List materialize میشود؛ در حالت دوم، خروجی بهصورت تدریجی تولید میشود. بنابراین Lazy Evaluation میتواند برای پردازش جریانهای بزرگ مفید باشد، زیرا لازم نیست تمام خروجی قبل از مصرف شدن در حافظه قرار گیرد.
Streaming
در Stream Processing، داده به صورت تدریجی در طول زمان تولید میشود. برخلاف یک Dataset ثابت که میتواند پیش از پردازش بهطور کامل در دسترس باشد، Stream ممکن است پیوسته ادامه داشته باشد و از نظر تعداد عناصر محدود نباشد.
Stream دادهای است که به صورت incremental در طول زمان تولید میشود. سامانههای Streaming نمیتوانند کل Stream را به شکل قابل دسترس ذخیره کنند؛ بنابراین باید عناصر آن را بهصورت on-the-fly و با حافظه محدود پردازش کنند . پردازش داده چندگیگابایتی از یک فایل نیز میتواند به صورت یک جریان داده پیادهسازی شود؛ بهگونهای که دادهها به صورت تدریجی خوانده شده و بلافاصله پردازش شوند.
تفاوت Generator و Streaming
Generator و Streaming دو مفهوم یکسان نیستند. Generator یک سازوکار در سطح زبان برنامهنویسی برای تولید تدریجی داده است؛ Streaming یک مدل پردازش است که در آن داده به صورت پیوسته یا تدریجی دریافت و پردازش میشود. میتوان Generator را یکی از ابزارهای پیادهسازی یک Pipeline پردازش Streaming در یک برنامه Python دانست:
Data Source -> Generator -> Transformation -> Filtering -> Processing -> Output
در این Pipeline، هر مرحله میتواند داده را به مرحله بعد منتقل کند، بدون آنکه الزاماً کل خروجی مرحله قبلی در حافظه ذخیره شود.
Out-of-Core Processing
Out-of-Core Processing به روشهایی اشاره دارد که برای پردازش دادههایی طراحی شدهاند که به طور کامل در حافظه اصلی قرار نمیگیرند. در معماریهای Out-of-Core، میتوان از حافظه خارجی، Storage، Memory Mapping یا Partitioning برای مدیریت Dataset استفاده کرد.
Generator و Streaming را میتوان در سطح سادهتر برنامهنویسی و Out-of-Core را در سطح گستردهتر سیستمهای پردازش داده بررسی کرد.
Stream Processing
Streaming Systems باید دادهها را به صورت مداوم و با منابع محدود پردازش کنند؛ همچنین مدیریت State در سامانههای Streaming میتواند از معماریهای In-Memory تا External Memory و Remote Memory متفاوت باشد . محدودیت حافظه مسئلهای صرفاً مربوط به Python نیست و در معماری سامانههای بزرگ Stream Processing نیز اهمیت دارد.
با طراحی مناسب Pipeline و استفاده از روشهای Streaming میتوان پردازش مجموعهدادههای بزرگ را با محدودیت حافظه مدیریت کرد . ارتباط میان Streaming و مدیریت حافظه در سیستمهای پردازش داده یک مسئله پژوهشی مستقل و مهم است.
Out-of-Core MapReduce
سیستمهای In-Memory ممکن است در مواجهه با Datasetهای بزرگ با Out-of-Memory مواجه شوند و یک رویکرد Out-of-Core MapReduce ارائه می شود.
Stream Processing در مقیاس بزرگ
ELF یک سیستم Stream Processing برای پردازش مقیاسپذیر دادههای استخراجشده از Web Serverها است که از ساختارهای فشرده و Memory-Efficient برای ذخیره موقت دادهها استفاده میکند . همچنین تخصیص و زمانبندی منابع میتواند بر کارایی پردازش Streaming تأثیر بگذارد .
روشهای مورد مقایسه
روش اول Full Loading:
کل Dataset ابتدا در حافظه قرار میگیرد و بهعنوان Baseline استفاده میشود.
data = load_entire_dataset()
for item in data:
process(item)
روش دوم Generator-based Processing:
دادهها به صورت تدریجی توسط Generator خوانده میشوند.
def read_data(filename):
with open(filename, "r") as file:
for line in file:
yield line
for item in read_data("large_file.text"):
process(item)
روش سوم Chunk-based Streaming : Dataset به بخشهای مشخص تقسیم شده و هر بخش جداگانه پردازش میشود.
while data_available:
chunk = read_chunk()
process(chunk)
Dataset
برای آزمایش، یک Dataset ، متن یا CSV چندگیگابایتی ایجاد یا انتخاب میشود. محتوای Dataset باید برای هر سه روش یکسان باشد و عملیات پردازشی نیز در تمام آزمایشها ثابت باقی بماند.
عملیات پردازشی
برای اینکه مقایسه منصفانه باشد، عملیات مشخصی روی هر رکورد انجام میشود: خواندن رکورد، حذف فاصلههای اضافی، بررسی یک شرط، محاسبه یک مقدار عددی و شمارش رکوردهای معتبر. مهم است که عملیات در هر سه روش دقیقاً یکسان باشد.
معیارهای ارزیابی
سه معیار اصلی در نظر گرفته میشود:
(۱) Peak Memory: بیشترین مقدار حافظه ردیابیشده در طول اجرای عملیات
(۲) Execution Time: مدت زمان لازم برای پردازش کل Dataset
(۳) Throughput که به صورت زیر محاسبه می شود:
Throughput = Dataset Size / Execution Time
ابزار اندازهگیری حافظه
برای اندازهگیری حافظه Python از «tracemalloc» استفاده میشود. مستندات رسمی پایتون، «tracemalloc» را ابزاری برای Trace کردن Memory Allocationهای Python معرفی میکند. تابع «get_traced_memory()» مقدار فعلی و Peak Memory را برمیگرداند و «reset_peak()» امکان اندازهگیری Peak مربوط به یک بخش مشخص از اجرای برنامه را فراهم میکند .
import tracemalloc
tracemalloc.start()
# processing
current, peak = tracemalloc.get_traced_memory()
print("Current memory:", current)
print("Peak memory:", peak)
tracemalloc.stop()
برای هر آزمایش، حافظه و زمان از قبل از شروع پردازش تا پایان عملیات اندازهگیری میشوند.
اندازهگیری زمان
برای اندازهگیری زمان اجرا از «time.perf_counter()» استفاده میشود:
import time
start = time.perf_counter()
# processing
elapsed = time.perf_counter() - start
print("Execution time:", elapsed)
طراحی آزمایش
هر سه روش باید در شرایط یکسان اجرا شوند: سیستمعامل یکسان، نسخه Python یکسان، Dataset یکسان، عملیات پردازشی یکسان، سختافزار یکسان و تعداد اجرای یکسان. آزمایش برای چند Dataset Size انجام میشود:
| Dataset | Full Loading | Generator | Streaming |
|---|---|---|---|
| 500 MB | ✓ | ✓ | ✓ |
| 1 GB | ✓ | ✓ | ✓ |
| 2 GB | ✓ | ✓ | ✓ |
| 4 GB | ✓ | ✓ | ✓ |
| 8 GB | در صورت امکان | در صورت امکان | در صورت امکان |
بررسی اندازه Chunk
برای روش Streaming، اندازه Chunk نیز متغیر آزمایش خواهد بود. هدف این بخش بررسی Trade-off میان حافظه و زمان اجرا است. با کاهش اندازه Chunk، مقدار دادهای که همزمان در حافظه قرار میگیرد کاهش مییابد، اما ممکن است تعداد عملیات خواندن و سربار پردازش افزایش پیدا کند. در مقابل، Chunk بزرگ تر می تواند کارایی I/O را بهبود دهد اما حافظه بیشتری مصرف کند.
فرضیههای پژوهش
فرضیه اول: پردازش Full Loading با افزایش اندازه Dataset، Peak Memory بیشتری نسبت به روشهای تدریجی مصرف خواهد کرد.
فرضیه دوم: در صورتی که منبع داده نیز بهصورت تدریجی خوانده شود، Generator-based Processing میتواند Peak Memory را نسبت به Full Loading کاهش دهد.
فرضیه سوم: Chunk Size بر رابطه میان مصرف حافظه و زمان اجرا تأثیر دارد.
فرضیه چهارم: کاهش مصرف حافظه الزاماً به معنی کاهش زمان اجرا نیست و ممکن است بین Memory Efficiency و Execution Time یک Trade-off ایجاد شود.
نتایج مورد اندازهگیری
آزمایش تاکنون روی یک فایل ۱۰۰ مگابایتی (۱,۹۷۸,۵۷۵ رکورد) با محیط اجرای مشخصشده (Python 3.13.0، Windows 11، پردازنده Intel64 با ۱۴ هسته) انجام شده است.
| Dataset | Method | Peak Memory | Execution Time | Throughput |
|---|---|---|---|---|
| 100 MB | Full Loading | 191.84 MB | 13.02 s | ≈ 7.68 MB/s |
| 100 MB | Generator | 0.03 MB | 13.88 s | ≈ 7.21 MB/s |
| 100 MB | Streaming (Chunk=1MB) | 3.94 MB | 14.23 s | ≈ 7.03 MB/s |
| 500 MB | Full Loading | — | — | — |
| 500 MB | Generator | — | — | — |
| 500 MB | Streaming | — | — | — |
| 1 GB | Full Loading | — | — | — |
| 1 GB | Generator | — | — | — |
| 1 GB | Streaming | — | — | — |
| 2 GB | Full Loading | — | — | — |
| 2 GB | Generator | — | — | — |
| 2 GB | Streaming | — | — | — |
| 4 GB | Full Loading | — | — | — |
| 4 GB | Generator | — | — | — |
| 4 GB | Streaming | — | — | — |
برای Chunk Size (روی همان فایل ۱۰۰ مگابایتی):
| Chunk Size | Peak Memory | Execution Time | Throughput |
|---|---|---|---|
| 1 MB | 3.94 MB | 14.23 s | ≈ 7.03 MB/s |
| 10 MB | 38.9 MB | 14.04 s | ≈ 7.12 MB/s |
| 50 MB | 38.9 MB | 12.94 s | ≈ 7.73 MB/s |
| 100 MB | — | — | — |
| 500 MB | — | — | — |
مقایسه روشها باید بر اساس دو محور اصلی انجام شود: مصرف حافظه و کارایی زمانی. در آزمایش انجامشده روی فایل ۱۰۰ مگابایتی، روش Full Loading به Peak Memory برابر با ۱۹۱.۸۴ مگابایت رسید یعنی تقریباً دو برابر حجم خام فایل ورودی. در روش Full Loading، هم متن خام فایل (از طریق «readlines()») و هم ساختارهای پردازشی (اشیاء رشتهای Python) همزمان در حافظه نگه داشته میشوند و overhead اشیاء رشتهای در Python باعث میشود مصرف حافظه از حجم خام فایل نیز فراتر رود.
در Generator-based Processing، Peak Memory به ۰.۰۳ مگابایت کاهش یافت کاهشی در حدود ۶۴۰۰ برابر نسبت به Full Loading در حالی که Records، Count > 500 و Total در هر دو روش دقیقاً یکسان ماندند (۱,۹۷۸,۵۷۵ رکورد و مجموع ۱,۹۷۸,۵۷۵,۰۰۰). این برابری نشان میدهد شرط عملیات پردازشی یکسان رعایت شده و مقایسه معتبر است. زمان اجرا نیز تقریباً ثابت ماند (۱۳.۰۲ در برابر ۱۳.۸۸ ثانیه، حدود ۶٪ کندتر)، بهگونهای که کاهش شدید حافظه با هزینهی زمانی محسوسی همراه نبود. این یافته مستقیماً از فرضیه دوم پژوهش (کاهش Peak Memory توسط Generator) و بخشی از فرضیه چهارم (نبود Trade-off لزوماً شدید میان Memory و Time) پشتیبانی میکند.
در روش Chunk-based Streaming، با Chunk=1MB، Peak Memory برابر ۳.۹۴ مگابایت بود که بهمراتب کمتر از Full Loading اما هنوز بیشتر از Generator است زیرا هر Chunk یک بلاک کامل از داده را یکجا در حافظه نگه میدارد، برخلاف Generator که تنها یک رکورد را در هر لحظه پردازش میکند. با افزایش Chunk به ۱۰ و ۵۰ مگابایت، Peak Memory روی مقدار ۳۸.۹ مگابایت ثابت ماند؛ این رفتار کاملاً خطی نیست و نشان میدهد در این محدوده احتمالاً یک ساختار میانی (مانند بافر خواندن فایل) بر Peak Memory غالب بوده است، نه صرفاً اندازه اسمی Chunk .
در سامانههای بزرگ Stream Processing نیز چنین Trade-offهایی وجود دارند.
محدودیتهای پژوهش
یکی از محدودیتهای این پژوهش این است که «tracemalloc» تمام مصرف حافظه فرایند را بهعنوان RAM سیستم اندازهگیری نمیکند، بلکه Memory Allocationهای قابل ردیابی توسط Python را اندازهگیری میکند . بنابراین برای پژوهشهای دقیقتر میتوان علاوه بر «tracemalloc»، مصرف حافظه Process را نیز با ابزارهای سیستمعامل اندازهگیری کرد.
محدودیت دوم وابستگی نتایج به سختافزار است؛ سرعت Storage، مقدار RAM، CPU و سیستمعامل میتوانند بر زمان اجرای روشهای مختلف تأثیر بگذارند.
محدودیت سوم نوع Dataset است: رفتار یک فایل CSV متنی ممکن است با Dataset باینری، تصویر، صوت یا داده ساختاریافته متفاوت باشد.
محدودیت چهارم این است که Generator در صورتی مفید است که Pipeline بهصورت کامل یا تا حد زیادی lazy طراحی شده باشد؛ اگر یکی از مراحل پردازش تمام داده را materialize کند، مزیت حافظهای روش کاهش پیدا میکند.
نتیجهگیری
در این پژوهش، مسئله مدیریت حافظه هنگام پردازش دادههای چندگیگابایتی با تمرکز بر Generator و Streaming بررسی شد. بررسی مبانی نظری نشان میدهد که پردازش دادههای بزرگ یکی از زمینههایی است که در آن محدودیت حافظه میتواند به یک عامل تعیینکننده تبدیل شود.
در سطح پایتون، Iteratorها امکان پردازش ترتیبی دادهها را فراهم میکنند و Generatorها با استفاده از «yield» راهی ساده برای ایجاد Iteratorهای lazy هستند؛ Generator Expression نیز میتواند از materialization همزمان کل خروجی جلوگیری کند. Streaming در مقیاس سامانه، مفهوم گستردهتر است که داده را بهصورت تدریجی پردازش میکند. پژوهشهای Stream Processing نشان میدهند که سیستمهای Streaming باید بتوانند دادههای ورودی را بهصورت مداوم و با محدودیت منابع پردازش کنند و برای مدیریت State، بار و فشار ورودی به سازوکارهای مختلف نیاز دارند.
از نظر عملی، مهمترین نکته این است که Generator بهتنهایی یک راهحل کامل برای مدیریت حافظه نیست. مزیت آن زمانی بیشترین اهمیت را دارد که منبع داده نیز بهصورت تدریجی خوانده شود و مراحل مختلف Pipeline از materialization غیرضروری جلوگیری کنند. در بخش تجربی پژوهش، مقایسه Full Loading، Generator-based Processing و Chunk-based Streaming میتواند نشان دهد که هر روش چه رفتاری از نظر Peak Memory، Execution Time و Throughput دارد؛ بررسی اندازههای مختلف Chunk نیز امکان مطالعه Trade-off میان مصرف حافظه و کارایی را فراهم میکند.
در نهایت، انتخاب روش مناسب باید بر اساس شرایط مسئله انجام شود. Full Loading در Datasetهای کوچک میتواند ساده و سریع باشد، اما با افزایش حجم داده با محدودیت حافظه مواجه میشود. Generator و Streaming امکان پردازش تدریجی را فراهم میکنند و برای Datasetهای بزرگ مناسبتر هستند، در حالی که Out-of-Core روشهای گستردهتری برای پردازش دادههایی ارائه میدهد که از ظرفیت حافظه اصلی بزرگترند.
منابع
[1] M. Fragkoulis, P. Carbone, V. Kalavri, and A. Katsifodimos, “A Survey on the Evolution of Stream Processing Systems,” The VLDB Journal, vol. 33, pp. 507-541, 2024. DOI: 10.1007/s00778-023-00819-8.
[2] B. Van Essen, H. Hsieh, S. Ames, R. Pearce, and M. Gokhale, “DI-MMAP-A Scalable Memory-Map Runtime for Out-of-Core Data-Intensive Applications,” Cluster Computing, vol. 18, pp. 15-28, 2015. DOI: 10.1007/s10586-013-0309-0.
[3] T. Schradi and S. Juhasz, “Out-of-Core Processing in Preparation Phase of Data Mining Tasks,” 2009.
[4] R. Ivancsy and S. Juhasz, “Approaches for Efficient Handling of Large Datasets,” IADIS Digital Library, 2009.
[5] S. Juhasz and R. Ivancsy, “Out-of-Core Data Handling with Periodic Partial Result Merging,” IADIS Digital Library, 2009.
[6] L. K. Ha, J. Kruger, J. L. D. Comba, C. T. Silva, and S. Joshi, “ISP: An Optimal Out-of-Core Image-Set Processing Streaming Architecture for Parallel Heterogeneous Systems,” IEEE Transactions on Visualization and Computer Graphics, vol. 18, no. 6, pp. 838-851, 2012. DOI: 10.1109/TVCG.2012.32.
[7] G. Kaur, K. Vora, S. C. Koduru, and R. Gupta, “OMR: Out-of-Core MapReduce for Large Data Sets,” ACM SIGPLAN Notices, vol. 53, no. 5, pp. 71-83, 2018. DOI: 10.1145/3299706.3210568.
[8] L. Hu, K. Schwan, H. Amur, and X. Chen, “ELF: Efficient Lightweight Fast Stream Processing at Scale,” USENIX Annual Technical Conference, 2014.
[9] Y. Morisawa, M. Suzuki, and T. Kitahara, “Resource Efficient Stream Processing Platform with Latency-Aware Scheduling Algorithms,” USENIX HotCloud, 2020.
[10] L. H. Sifei et al., “The Streaming Batch Model for Efficient and Fault-Tolerant Heterogeneous Execution,” 2025.
[11] L. Reichmann, D. Hagele, and D. Weiskopf, “Out-of-Core Dimensionality Reduction for Large Data via Out-of-Sample Extensions,” 2024.
[12] Python Software Foundation, “Functional Programming HOWTO – Iterators, Generator Expressions and Generators,” Python Documentation.
[13] Python Software Foundation, “Generators,” The Python Tutorial.
[14] Python Software Foundation, “tracemalloc – Trace Memory Allocations,” Python Documentation.
[15] L. Ramalho, Fluent Python, 2nd ed., O’Reilly Media, Chapter 17, “Iterators, Generators, and Classic Coroutines.”
[16] B. Slatkin, Effective Python: 125 Specific Ways to Write Better Python, 3rd ed., Addison-Wesley Professional, Chapter 6, “Comprehensions and Generators.”
[17] PEP 289 – Generator Expressions, Python Enhancement Proposals, https://peps.python.org/pep-0289/
