ما السعر الذي ندفعه لاستخدام المزامنة / الانتظار في JS / C # / Rust

مرحبا يا هبر!


من خلال العمل مع Javascript / Typescript ، لاحظت لفترة طويلة أن واجهة برمجة التطبيقات غير المتزامنة أبطأ من واجهة برمجة التطبيقات المتزامنة المماثلة ، وكنت أعرف أنها يجب أن تكون كذلك. لكن في المشروع الأخير ، أصبح العمل غير المتزامن مع نظام الملفات عنق الزجاجة ، وقد اعتنت بالقياسات.


من المعروف أنه لا يمكن استخدام الانتظار إلا داخل وظائف أو كتل غير متزامنة ، مما يعني أنه إذا كان لدينا أدنى مستوى من واجهة برمجة التطبيقات غير متزامن ، فسيتعين عليك استخدام المزامنة / الانتظار في كل مكان تقريبًا ، حتى في حالة عدم وجود حاجة لذلك بوضوح.


على سبيل المثال ، نكتب وظيفة خدمة تأخذ كائنًا من التخزين حسب المفتاح. كتخزين ، يمكننا استخدام ملف ، قاعدة بيانات ، خدمات ميكروية ، أي مصدر بطيء بواجهة غير متزامنة. لتحسين الأداء ، داخل وظيفتنا ، نقوم بتخزين الكائنات التي تم استخراجها مسبقًا (إضافتها إلى الخريطة ). عندما يصبح برنامج الوصول الحقيقي إلى المتجر أصغر ، يتم إرجاع الكائنات من ذاكرة التخزين المؤقت السريعة ، ولكن تظل واجهة الوظيفة غير متزامنة!


ما السعر الذي يجب علي دفعه لكل مكالمة غير متزامنة؟
نتائج الاختبار محبطة ...


خذ وظيفة بسيطة ، وقم بتمييزها على أنها غير متزامنة ، وسوف نسميها في حلقة ، وقياس الوقت الكلي ، ومقارنتها برمز متزامن مماثل. لمقارنة بناء الجملة ، أقتبس النصوص الكاملة بثلاث لغات.


مخطوطة ( دينو )


كود متزامن
const b = Date.now() let j = 0.0 for (let i = 0; i < 1_000_000_000; i++) { j += f(i) } console.log(j + ', ' + (Date.now() - b)/1000 + 's') function f(i: number): number { return i / 3.1415926 } 

كود غير متزامن:


 (async () => { const b = Date.now() let j = 0.0 for (let i = 0; i < 1_000_000_000; i++) { j += await f(i) } console.log(j + ', ' + (Date.now() - b)/1000 + 's') })() async function f(i: number): Promise<number> { return i / 3.1415926 } 

C # (.NET Core)


كود متزامن
 using System; class App { static void Main(string[] args) { var b = DateTime.Now; var j = 0.0; for (var i = 0L; i < 1_000_000_000L; i++) { j += f(i); } Console.WriteLine(j + ", " + (DateTime.Now - b).TotalMilliseconds / 1000 + "s"); } static double f(long i) { return i / 3.1415926; } } 

كود غير متزامن:


 using System; using System.Threading.Tasks; class App { static async Task Main(string[] args) { var b = DateTime.Now; var j = 0.0; for (var i = 0L; i < 1_000_000_000L; i++) { j += await f(i); } Console.WriteLine(j + ", " + (DateTime.Now - b).TotalMilliseconds / 1000 + "s"); } static async Task<double> f(long i) { return i / 3.1415926; } } 

صدأ


كود متزامن
 fn main() { let tbegin = std::time::SystemTime::now(); let mut j = 0.0; for i in 0..1_000_000_000i64 { j += f(i); } println!("{:?}, {:?}", j, tbegin.elapsed().unwrap()); } fn f(i: i64) -> f64 { return i as f64 / 3.1415926 } 

كود غير متزامن:


 //[dependencies] //futures = "0.3" use futures::executor::block_on; fn main() { block_on(async { let tbegin = std::time::SystemTime::now(); let mut j = 0.0; for i in 0..1_000_000_000i64 { j += f(i).await; } println!("{:?}, {:?}", j, tbegin.elapsed().unwrap()); }); } async fn f(i: i64) -> f64 { return i as f64 / 3.1415926 } 

النتائج


لغةكود متزامن (ثانية)كود غير متزامن (ثانية)خسارة ٪٪
نسخة مطبوعة على الآلة الكاتبة7.4817323 مرة
C #7.4676.210 مرات
صدأ7.4519.22.6 مرة

نرى أن الحساب في جميع اللغات الثلاث جيد بنفس الدرجة ، لكن النفقات العامة في انتظار تختلف حسب ترتيب الحجم. ومن المثير للاهتمام ، عندما يكون استخدام المزامنة / الانتظار هو الأكثر شيوعًا (وحتى يتم الترويج له) ، فإن تكلفة المكالمة غير المتزامنة تعتبر باهظة. روست فاز بالسباق كما هو الحال دائمًا ، وربما هذا هو السبب الرئيسي وراء فوز إطار WEB المكتوب عليه باستمرار بمعايير لأكثر من عام.


ملخص


ليس من دون سبب ، مطورو Java ليسوا في عجلة من أمرهم لإضافة جملة غير متزامنة مباشرةً إلى اللغة ، وعلى الرغم من أنني أعتقد أن المزامنة / الانتظار هي عبارة عن تجريد كبير ، يجب أن نفهم حجم الحمل عند استخدامه.


PS
شكرًا للجميع الذين أشاروا إلى إمكانية تسريع الكود عن طريق تخزين المهمة / الوعود في ذاكرة التخزين المؤقت (بدلاً من تخزين النتائج مؤقتًا) ، بالإضافة إلى وجود أداة رائعة في حل المشكلة (C #).

Source: https://habr.com/ru/post/ar483734/


All Articles