مرحبا يا هبر!
من خلال العمل مع 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 }
كود غير متزامن:
النتائج
نرى أن الحساب في جميع اللغات الثلاث جيد بنفس الدرجة ، لكن النفقات العامة في انتظار تختلف حسب ترتيب الحجم. ومن المثير للاهتمام ، عندما يكون استخدام المزامنة / الانتظار هو الأكثر شيوعًا (وحتى يتم الترويج له) ، فإن تكلفة المكالمة غير المتزامنة تعتبر باهظة. روست فاز بالسباق كما هو الحال دائمًا ، وربما هذا هو السبب الرئيسي وراء فوز إطار WEB المكتوب عليه باستمرار بمعايير لأكثر من عام.
ملخص
ليس من دون سبب ، مطورو Java ليسوا في عجلة من أمرهم لإضافة جملة غير متزامنة مباشرةً إلى اللغة ، وعلى الرغم من أنني أعتقد أن المزامنة / الانتظار هي عبارة عن تجريد كبير ، يجب أن نفهم حجم الحمل عند استخدامه.
PS
شكرًا للجميع الذين أشاروا إلى إمكانية تسريع الكود عن طريق تخزين المهمة / الوعود في ذاكرة التخزين المؤقت (بدلاً من تخزين النتائج مؤقتًا) ، بالإضافة إلى وجود أداة رائعة في حل المشكلة (C #).