برنامه نویسی تابعی خالص چقدر می تواند به C نزدیک شود؟
آدرس مقاله: https://rtfeldman.com/fp-vs-c آدرس نظرات: https://news.ycombinator.com/item?id=50218195 امتیاز: 208 # نظرات: 59
در کتاب چرا Lean سریعتر از Rust است، نویسنده نتیجه چشمگیری را به اشتراک می گذارد: یک کتابخانه فشرده سازی DEFLATE که در Lean 4 نوشته شده است، یک زبان برنامه نویسی کاربردی و دستیار اثبات است که از بسته Rust مشابهی که crates.io می گوید بیش از یک میلیارد بار دانلود شده است، بهتر عمل می کند. از مقاله:
lean-zip هر سطح فشردهسازی را که miniz_oxide ارائه میکند شکست میدهد، یعنی ما میتوانیم همان فشردهسازی را در توان عملیاتی بالاتر ارائه دهیم. در سریعترین تنظیم آن، 15٪ سریعتر و خروجی 8.8٪ کوچکتر است.
با این حال، مقاله همچنین خاطرنشان میکند که در حالی که آنها موفق شدند از miniz_oxide Rust پیشی بگیرند، اما هیچ رقبای با رئیس نهایی عملکرد DEFLATE ندارند: libdeflate، نوشته شده در C.
lean-zip مطمئنا در اینجا بهترین نیست: libdeflate بهطور شگفتانگیزی آن را از آب بیرون میکشد (تعجبی ندارد زیرا این یک پیادهسازی بسیار دقیق تنظیمشده با استفاده از SIMD خاص معماری است که ما نمیتوانیم آن را در Lean لمس کنیم).
Roc را وارد کنید، یک زبان برنامه نویسی کاربردی (اما نه یک اثبات کننده قضیه) با تمرکز صریح بر عملکرد زمان اجرا.
شعار Roc "سریع، دوستانه، کاربردی" است، و Fast در اولویت قرار دارد زیرا هم دویدن سریع و هم کامپایل سریع همیشه اهداف اصلی بوده اند. Roc یک زبان بالغ نیست (هنوز هفته ها تا اولین نسخه شماره گذاری شده آن، 0.1.0 فاصله دارد)، و اکوسیستم بسته نوپای آن تقریبا هیچ کار بهینه سازی روی آن انجام نداده است. با این حال، بین طراحی زبان، بهینهسازیهای طولانی مدت و پیادهسازی کامپایلر کاملا جدید، متوجه شدم که Roc از قبل به اندازه کافی پایه و اساس برای تصاحب قهرمانی دارد: libdeflate.
بنابراین من مدت زیادی را صرف بهینهسازی عملکرد روی roc-deflate کردم، بستهای که Niclas Ahden برای فشردهسازی DEFLATE نوشت (و در کارهای تولیدی پولی استفاده میکند). من انتظار نداشتم که عملکرد libdeflate را در سراسر صفحه شکست دهم، اما میخواستم ببینم چقدر میتواند به APIهای غیر استریم نزدیک شود (زیرا roc-deflate هنوز استریم را اجرا نکرده است)، با استفاده از پیکره کلاسیک Silesia برای محک زدن و تأیید اینکه خروجیهای یکسانی تولید میکند.
در اینجا بنچمارکها امروز قرار دارند، در مکبوک پرو M1 2021 که در آن من تمام کارهای بهینهسازی را انجام دادم، و همچنین روی دستگاه AMD Ryzen 9 3900X Ubuntu برای مقایسه x64.
roc-deflate این 144 معیار را به طور متوسط کندتر از libdeflate اجرا کرد، در حالت متوسط 8.9٪ در Ryzen 3900X و 2.6٪ در M1 MacBook Pro.
بنابراین، در حالی که ما به طور متوسط از قهرمان بهتر عمل نکردیم، یک درصد کاهش سرعت متوسط در یک رقم در مقایسه با چنین کتابخانه C با SIMD که با دقت تنظیم شده است، وحشی است. صراحتا این نتیجهای نیست که من میدانستم که در آن قابل دستیابی است، زیرا Roc یک زبان کاربردی با مدیریت خودکار حافظه است که با کد رقابت میکند و نیازی به بررسی کرانهها ندارد. (بررسی مرزها هزینه دارد، اما باز هم، آخرین باری که بررسی کرانه های نادیده گرفته شده در کد فشرده سازی C منجر به آسیب پذیری اصلی Remote Code Execution دیروز بود.)
نمودار جزییات بیشتری را نشان می دهد تا فقط شکاف میانه. در ورودی های بدترین حالت، roc-deflate 18٪ کندتر از libdeflate بود، اما در ورودی های بهترین حالت، roc-deflate کمی بیش از 18٪ سریعتر بود! دلیلش این نیست که Roc بهینه سازی جادویی دارد که C نمی تواند به آن دسترسی داشته باشد. فقط این دو پیاده سازی متفاوت هستند، و اگرچه به طور متوسط roc-deflate کندتر است، برای ورودی های خاص سریعتر است. بنابراین، در حالی که امکان افزایش سرعت با تغییر از libdeflate به roc-deflate وجود دارد، نباید به طور متوسط انتظار آن را داشته باشید.
برای روشن بودن، سقف عملکرد C بالاتر از Roc است. برای هر ورودی که در آن roc-deflate در حال حاضر بهتر از libdeflate است، کد C می تواند آن شکاف را ببندد. عکس آن درست نیست. راک کد از نظر حافظه ایمن است، شمارش خودکار مراجع را انجام میدهد، محدودهها را بررسی میکند، و برای انصراف از هیچ یک از موارد ناامن Rust-style، پشتیبانی نمیکند. این ویژگیها به کد Roc تضمینهای ایمنی قویتری نسبت به کدهای نوشته شده به زبانهای C، Zig، Rust و سایر زبانها میدهند که امکان دسترسی کامل به سختافزار را فراهم میکنند - و قیمتی که برای این تضمینها میپردازد سقف عملکرد پایینتر است.
بدیهی است که ما نمیتوانیم از این یک نتیجه در مورد اینکه Roc در حالت کلی چقدر میتواند به C نزدیک شود، تعمیم دهیم، اما اکنون چیزی نشان داده شده است که هیچکس قبلا نمیدانست که قابل دستیابی است. تا آنجا که من می دانم، این بیشترین تلاش برای بهینه سازی است که تا به امروز برای هر بسته Roc انجام شده است، بنابراین اینکه دیگران چقدر می توانند به این نتیجه نزدیک شوند، در حال حاضر ناشناخته است. بنابراین، در حالی که من از این نتیجه هیجان زده هستم، کاملا آگاهم که تا زمانی که نمونه وسیع تری از پایه های کد Roc در دنیای واقعی را بهینه سازی نکنیم، نمی دانیم که چقدر به خوبی تعمیم می یابد!
یک سوال واضح این است که "چرا به هر مقدار آهسته تر بسنده کنیم؟" اگر میتوانید سطح عملکرد C را دریافت کنید، چرا از خود libdeflate استفاده نکنید و آن شکاف باقیمانده را ببندید؟
پیاده سازی Pure-Roc چندین مزیت نسبت به Pure-C دارد:
اگر هر یک از این ضمانتها در عمل اجرا نمیشوند، تنها میتواند به دلیل نقص در کامپایلر Roc، پلتفرمی که برنامه روی آن اجرا میشود، یا سختافزار زیربنایی باشد. بنابراین هنگامی که در اسناد roc-deflate مشاهده می کنید که کل بسته دارای عملکردهای 100٪ خالص است، می توانید مطمئن باشید که تمام ویژگی های فوق را از همه چیزهایی که بسته ارائه می دهد دریافت می کنید. سیستم نوع آن را اجرا می کند!
دلیل اینکه ما اینقدر روی همه اینها کار کردهایم ممکن است واضحتر شود اگر به صورت معکوس به این قضیه نگاه کنید: آیا ایمنی تضمین شده حافظه، فقدان اثرات جانبی تضمین شده، رفتار قطعی تضمین شده در همه اهداف و مزایای امنیتی نداشتن اسکریپتهای ساخت قابل بهرهبرداری در بستهها را کنار میگذارید... برای میانگین درصد افزایش سرعت تک رقمی؟
این پاسخ می تواند به طور قانونی بر اساس موارد استفاده متفاوت باشد - برای مثال کامپایلر خود راک حداکثر عملکردی را می خواهد که سخت افزار می تواند ارائه دهد - اما شرط می بندم که بسیاری از افراد با خواندن این مطلب می توانند ببینند که چرا ما از دستیابی به این نتیجه بسیار هیجان زده هستیم.
بیشتر بهینهسازیهایی که اعداد roc-deflate را بهبود بخشیدند، از تکنیکهای بهینهسازی عمومی که به طور گسترده در بین زبانها مفید هستند، به دست آمدند. (من همیشه سری برنامهنویسی آگاه از عملکرد کیسی موراتوری را توصیه میکنم، که برای آن هزینه کردم و چیزهای زیادی از آن یاد گرفتم.) اگرچه برخی از تکنیکهای بهینهسازی مختص Roc بودند و میخواستم آنها را در اینجا به عنوان مرجعی برای دیگران ذکر کنم. حتی اگر کد Roc نمینویسید، این میتواند به شما ایده دهد که Roc چگونه به عملکرد خود دست مییابد.
یکی از موثرترین بهینهسازیهای Roc، جهش فرصتطلب است (که کوکا آن را "عملکردی اما در محل" مینامد. میتوانید نحوه عملکرد آن را در اسناد Roc در مورد جهش فرصتطلب بیاموزید، اما از منظر بهینهسازی عملکرد، اجتناب از اشتراکگذاری غیرضروری بسیار مهم است تا مقادیر منحصربهفرد باقی بمانند و در نتیجه واجد شرایط جهش در محل باشند.
مانند C، Zig و Rust نمیتوانید از بررسی کرانهها (بهعنوان مثال دریافت مقدار از مجموعهای با اندازه پویا) در Roc انصراف دهید، اما کامپایلر Roc میتواند آنها را در صورتی که بداند تضمین میشوند که پاس میشوند، حذف کند. بررسی کرانهها در داخل حلقهها پرهزینهترین هستند، بنابراین میتوانید با افزودن چیزی مانند if collection.len() <= بالاترین_index_used { return 0 } قبل از شروع حلقه، مسافت پیمودهای زیادی بدست آورید.
این یک بررسی اضافی اضافه میکند که به نظر اضافی میرسد، اما میتواند به بهینهساز اجازه دهد تا با خیال راحت چکهای پرهزینهتر را در داخل حلقه حذف کند، پس از اینکه متوجه شد که خرابیها پس از بازگشت اولیه قابل دسترسی نیستند.
ارزیابی زمان کامپایل Roc می تواند برای جمع آوری جداول جستجو در زمان کامپایل استفاده شود تا زمانی که هیچ چیز به ورودی های زمان اجرا بستگی نداشته باشد. حتی بهتر از آن، از آنجایی که طول آنها در زمان کامپایل مشخص است، می توانید کارهایی مانند Ok(entry) = lookup_table.get(5) را برای انجام یک تطابق الگوی جامع که کامپایلر Roc بداند به لطف ارزیابی زمان کامپایل معتبر است، انجام دهید. این کار از بررسی محدودیتهای زمان اجرا که جداول غیر ثابت ممکن است مجبور به پرداخت هزینه باشند، جلوگیری میکند.
در Roc، a + b desugars به a.plus(b) می شود. برای یک عدد صحیح مانند U64، U64.plus شامل بررسی سرریز است، در حالی که plus_wrap اینطور نیست. اگرچه plus پیشفرض امنتر است، بررسیهای سرریز هزینه زمان اجرا دارند، زیرا CPUها میتوانند محاسبات عدد صحیح را در یک دستورالعمل انجام دهند. هنگامی که کامپایلر Roc می تواند ثابت کند که سرریز اتفاق نمی افتد، هر دو plus و plus_wrap به دستورالعمل های یکسانی بهینه می شوند، اما زمانی که نمی تواند، ممکن است با انتخاب plus_wrap سرعت بیشتری دریافت کنید.
من مطمئن هستم که هنوز فرصت هایی برای بهبود عملکرد roc-deflate وجود دارد - به خصوص در CPU های x64 - و همچنین خط لوله بهینه سازی در کامپایلر Roc.
در این مرحله من کاملا مطمئن هستم که همه میوه های کم آویزان انتخاب شده اند، اما اگر کسی می خواهد آن را برای خودش امتحان کند، در اینجا اسکریپتی است که من برای تنظیم و اجرای مجموعه معیار در لینوکس اجرا کردم. (توجه داشته باشید که Claude Opus 5.5 این اسکریپت را تولید کرده است، و من آن را از نظر امنیتی بررسی نکرده ام؛ آن را با مسئولیت خود در خارج از یک جعبه شنی اجرا کنید!) این نسخه هم نسخه های کامپایلر Roc و هم نسخه های roc-deflate را پین می کند، بنابراین اگر در حال تلاش برای بهبودهای بالقوه هستید، بهتر است این تعهدات را تنظیم کنید.
به نوبه خود، از هم اکنون تا زمانی که منتشر شود، روی نسخه بعدی Roc 0.1.0 تمرکز خواهم کرد. هشت سال از ساخت آن می گذرد، بنابراین من بسیار خوشحال خواهم شد که بالاخره یک نسخه شماره دار داشته باشیم! اگر میخواهید درباره Roc بیشتر بدانید، roc-lang.org را بررسی کنید یا به Roc Zulip سلام کنید!
متن اصلی (انگلیسی)
How close can pure functional programming get to C?
Article URL: https://rtfeldman.com/fp-vs-c Comments URL: https://news.ycombinator.com/item?id=50218195 Points: 208 # Comments: 59