fast_float؛ راهنمایی parser بر اساس شکل عددها

چرا مهم است

اگر با تبدیل تعداد زیادی عدد سروکار داری، شناخت شکل داده‌ها می‌تواند در سرعت اثر بگذارد. این آزمایش‌ها نشان می‌دهند مناسب به داده و processor بستگی دارد و انتخاب نامناسب حتی ممکن است پردازش را کندتر کند.

قبل از خواندن، این‌ها را بدان۵ مفهوم
standard library

مجموعهٔ امکانات تعریف‌شده در استاندارد یک زبان که پیاده‌سازی‌های آن زبان در اختیار برنامه‌نویس می‌گذارند.

بیشتر: standard library

pull request

پیشنهاد ثبت‌شده برای بررسی و واردکردن مجموعه‌ای از تغییرات به یک پروژه.

بیشتر: pull request

performance hint

اطلاعاتی دربارهٔ الگوی مورد انتظار داده یا اجرای برنامه که برای انتخاب مسیر پردازش در اختیار کد قرار می‌گیرد.

در حال نگارش: performance hint

branch prediction

روشی که processor با آن نتیجهٔ احتمالی یک branch را حدس می‌زند و پیش از مشخص‌شدن نتیجه، اجرای دستورها را ادامه می‌دهد.

در حال نگارش: branch prediction

rounding mode

قاعده‌ای برای انتخاب مقدار قابل نمایش، وقتی نتیجهٔ دقیق یک محاسبهٔ floating-point مستقیماً قابل نمایش نیست.

در حال نگارش: rounding mode

کتابخانهٔ fast_float عددهایی مانند ‎-731.51 را به مقدارهای binary floating-point تبدیل می‌کند و از جمله در standard library مربوط به C++ در GCC به کار می‌رود. نویسنده اکنون در یک pull request،؜ سه performance hint به آن اضافه کرده است: اطلاعاتی که برنامه‌نویس دربارهٔ داده می‌داند، اما parser به‌تنهایی نمی‌داند.

مسئله به branch prediction برمی‌گردد. parser باید دربارهٔ علامت، تعداد رقم‌ها، محل ممیز و rounding mode تصمیم بگیرد. processor نتیجهٔ branch را پیش‌بینی می‌کند؛ پیش‌بینی درست تقریباً بدون هزینه است، اما پیش‌بینی اشتباه، طبق توضیح مقاله، ۱۵ تا ۲۰ cycle هزینه دارد. وقتی علامت عددها تصادفی باشد، پیش‌بینی آن تقریباً برای نیمی از عددها اشتباه می‌شود.

گزینهٔ unpredictable_sign علامت را بدون branch پردازش می‌کند. گزینهٔ short_numbers برای حالتی است که محدودهٔ ورودی فقط یک عدد دارد و بیشتر عددها حداکثر هشت character هستند؛ parser هشت character را یک‌جا با چند bitwise operation می‌خواند، بدون branch برای تعداد رقم‌ها یا محل ممیز. گزینهٔ assume_round_to_nearest نیز بررسی rounding mode را حذف می‌کند، با این فرض که برنامه آن را تغییر نمی‌دهد.

این گزینه‌ها به‌صورت template argument ارسال می‌شوند و با | قابل ترکیب‌اند. هر سه با fast_float::from_chars،؜ fast_float::from_chars_advanced و fast_float::integer_times_pow10 کار می‌کنند. بدون آن‌ها رفتار قبلی حفظ می‌شود. نتیجهٔ محاسبه هم تغییر نمی‌کند، مگر اینکه assume_round_to_nearest فعال باشد و بخشی از برنامه rounding mode را تغییر دهد؛ در این حالت بعضی نتیجه‌ها نادرست خواهند بود.

آزمایش‌ها برای چهار گروهِ صد هزار عددی، روی Intel Xeon Gold 6338 با GCC 15 و Apple M4 Max با Apple clang 17 انجام شدند. برای تغییرات قیمت با علامت تصادفی و عددهای کوتاه، ترکیب unpredictable_sign و assume_round_to_nearest نرخ پردازش Intel را از ۷۳ به ۱۰۵ میلیون عدد در ثانیه رساند. روی Apple،؜ هر سه گزینه بهترین بودند: افزایش از ۱۵۳ به ۳۴۵ میلیون.

اما برای موجودی حساب با عددهای بلندتر، افزودن short_numbers روی Intel نتیجه را بدتر کرد. برای داده‌های مثبت حسگر، short_numbers مفید بود ولی unpredictable_sign روی Intel سرعت را کاهش داد. برای قیمت‌های مثبت و هم‌شکل نیز بهترین انتخاب Intel استفاده‌نکردن از این گزینه‌ها بود، درحالی‌که short_numbers روی Apple همچنان کمک کرد.

جمع‌بندی نویسنده این است که این راهنمایی‌ها رایگان نیستند: branch را با دستورهای بیشتر عوض می‌کنند و زمانی سود دارند که پیش‌بینی processor اشتباه باشد.

نکته کلیدی

همهٔ گزینه‌ها را صرفاً به امید سرعت بیشتر فعال نکن؛ ابتدا طول عددها و الگوی علامتشان را مشخص کن. برای مربوط به گردکردن هم مطمئن شو برنامه فرض اعلام‌شده را نقض نمی‌کند، چون ممکن است نتیجه نادرست شود.

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