hotpath-rs؛ چرا زمان اجرای benchmark در CI به‌تنهایی کافی نیست؟

چرا مهم است

اگر برای سنجش کارایی به در تکیه می‌کنی، تغییر زمان اجرا لزوماً نشانهٔ تغییر کد نیست. این آزمایش نشان می‌دهد حتی با کد و مدل پردازندهٔ یکسان هم زمان‌ها می‌توانند تفاوت زیادی داشته باشند.

قبل از خواندن، این‌ها را بدان۴ مفهوم
benchmark

آزمایشی با کار و شرایط مشخص برای اندازه‌گیری و مقایسهٔ کارایی برنامه.

بیشتر: benchmark

allocation

اختصاص بخشی از حافظه به داده‌های برنامه؛ تعداد دفعات یا حجم آن را می‌توان برای بررسی رفتار برنامه اندازه گرفت.

بیشتر: allocation

CI

فرایندی برای ساخت و آزمایش خودکار تغییرات کد هنگام اضافه شدن آن‌ها به پروژه.

در حال نگارش: CI

thread

مسیر اجرای دستورها در یک برنامه که می‌تواند در کنار مسیرهای اجرای دیگر کار کند.

در حال نگارش: thread

نویسندهٔ hotpath-rs برای بررسی نوسان اندازه‌گیری کارایی، یک benchmark یکسان را بدون تغییر کد، ۱۰۰ بار روی GitHub Actions اجرا کرده است. زمان اجرا روی x86 از ۳۵ تا ۹۸ میلی‌ثانیه تغییر کرد؛ اختلافی نزدیک به سه برابر. در مقابل، مقدار حافظهٔ اختصاص‌یافته در همهٔ اجراها ثابت بود.

این آزمایش با benchmark موسوم به drain انجام شد که سربار جمع‌آوری یک میلیون سیگنال اندازه‌گیری را بررسی می‌کند. برای جدا کردن اثر محیط CI از تغییرات برنامه، نویسنده یک pull request بدون تغییر کد ساخت و با commit‌های خالی، اجرای دوبارهٔ همان کد و تنظیمات را روی x86 و ARM راه انداخت.

بخشی از اختلاف زمان به سخت‌افزار مربوط بود: ۱۰۰ اجرای x86 میان شش مدل CPU توزیع شدند. بااین‌حال، حتی ۴۱ اجرا روی مدل یکسان AMD EPYC 7763 نیز بین ۶۹٫۷۳ تا ۸۹٫۶۰ میلی‌ثانیه طول کشیدند. بنابراین، یکسان بودن مدل CPU هم نوسان را حذف نکرد.

روی ARM نیز نتیجه مشابه بود. از ۱۰۰ اجرا، ۹۹ اجرا روی Neoverse-N2 انجام شدند، اما زمان همین اجراها از ۲۸٫۹۷ تا ۵۷٫۳۷ میلی‌ثانیه تغییر کرد؛ نزدیک به دو برابر. نویسنده تأکید می‌کند که مقایسهٔ مستقیم زمان x86 و ARM معنایی ندارد، چون خروجی compiler آن‌ها متفاوت است.

ماهیت drain هم مهم است: این benchmark یک محاسبهٔ سادهٔ single-threaded نیست. کد اندازه‌گیری‌شده، رویدادها را در یک ring buffer قرار می‌دهد و یک thread پس‌زمینه آن‌ها را مصرف می‌کند. چنین ارتباطی به زمان‌بندی thread حساس است، اما همان کاری است که hotpath-rs در استفادهٔ واقعی انجام می‌دهد.

در مقابل نوسان زمان، اندازه‌گیری allocation نتیجه‌ای ثابت داشت: مقدار کل حافظهٔ اختصاص‌یافته در هر ۱۰۰ اجرای هر دو معماری دقیقاً ۲٬۴۲۵٬۲۱۳ بایت بود. برای تابع EventProducer::push نیز مقدار ۱٬۲۱۶٬۷۶۸ بایت در همهٔ اجراها ثبت شد. نتیجهٔ این بخش از بررسی این است که benchmark با طراحی مناسب می‌تواند از نظر allocation کاملاً ثابت باشد، حتی وقتی زمان اجرای آن در محیط مشترک CI نوسان زیادی دارد.

نکته کلیدی

برای قضاوت دربارهٔ افت کارایی، فقط زمان اجرا را ملاک نگذار. در این پروژه، مقدار حافظهٔ اختصاص‌یافته برخلاف زمان اجرا ثابت ماند؛ پس ثابت بودن هر اندازه‌گیری را با اجرای تکراری بررسی کن.

خواندن مقاله اصلی (در زبانه‌ی جدید باز می‌شود)
برچسب‌ها (۴)