استبدال مسار سريع في رمز JavaScript لتطبيقك باستخدام WebAssembly

إنه سريع دائمًا، أليس كذلك

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

WebAssembly هو جهاز افتراضي منخفض المستوى تشغّل رمز البايت الذي يتم تخزينه في .wasm ملف. تمت كتابة رمز البايت هذا وتنظيمه بقوة بطريقة بحيث يمكن تجميعها وتحسينها للنظام المضيف بشكل أسرع بكثير من يمكن لـ JavaScript تنفيذ ذلك. يوفر WebAssembly بيئة لتشغيل التعليمات البرمجية التي وضع الحماية والتضمين في الاعتبار منذ البداية.

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

أغنية The Hot Path

كتبنا في squoosh دالة JavaScript يعمل على تدوير المخزن المؤقت للصور بمقدار مضاعفات بمقدار 90 درجة. بينما OffscreenCanvas ستكون مثالية فهو غير متوافق مع جميع المتصفحات التي استهدفناها، فضلاً عن الأخطاء في Chrome.

تتكرر هذه الدالة على كل بكسل من صورة المدخلة وتنسخها إلى موضع مختلف في الصورة الناتجة لتحقيق الدوران. لـ 4094 بكسل × يجب أن يكون حجم الصورة 4096 بكسل (16 ميغابكسل) أكثر من 16 مليون تكرار كتلة رموز داخلية، وهو ما نسميه "المسار السريع". رغم ذلك، عدد التكرارات، يُنهي اثنان من بين ثلاثة متصفحات اختبرناها المهمة في ثوانٍ أو أقل. مدة مقبولة لهذا النوع من التفاعل.

for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    const in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}

في المقابل، يستغرق متصفّح واحد أكثر من 8 ثوانٍ. الطريقة التي تحسّن بها المتصفّحات لغة JavaScript معقدًا حقًا، وتُجري محركات مختلفة تحسينات لتحقيق أشياء مختلفة. وبعضها يُحسن التنفيذ الأوّلي، بينما يُحسِّن البعض الآخر للتفاعل مع نموذج العناصر في المستند. ضِمن في هذه الحالة، وصلنا إلى مسار غير محسّن في متصفح واحد.

من ناحية أخرى، تستند WebAssembly بشكل كامل إلى سرعة التنفيذ الأولية. إذًا، إذا أردنا الحصول على أداء سريع يمكن التنبؤ به عبر المتصفحات لرموز برمجية مثل هذه يمكن أن تساعدك WebAssembly في ذلك.

WebAssembly لتحقيق أداء يمكن توقُّعه

بشكل عام، يمكن لكل من JavaScript وWebAssembly تحقيق أعلى مستوى من الأداء. مع ذلك، بالنسبة إلى JavaScript، لا يمكن الوصول إلى هذا الأداء إلا من خلال "المسار السريع"، وغالبًا ما يكون من الصعب البقاء على هذا "المسار السريع". تتمثل إحدى الميزات الرئيسية التي توفّر عروض WebAssembly أداءً يمكن توقّعه، حتى على مختلَف المتصفحات. الصارم فإن الكتابة والبنية منخفضة المستوى تتيح للمحول البرمجي جعل بحيث لا يتم تحسين رمز WebAssembly إلا مرة واحدة دائمًا "المسار السريع".

الكتابة من أجل WebAssembly

أخذنا سابقًا مكتبات C/C++ وتجميعها في WebAssembly لاستخدام على الويب. فنحن لم نتطرق إلى رمز المكتبات حقًا، قمتَ بكتابة كميات صغيرة من رموز C/C++ لتشكيل الجسر بين المتصفح والمكتبة. هذه المرة دافعنا مختلف: نريد أن نكتب أي شيء من البداية مع وضع WebAssembly في الاعتبار حتى نتمكن من الاستفادة من المزايا التي تتمتع بها WebAssembly.

بنية WebAssembly

عند الكتابة من أجل WebAssembly، من المفيد معرفة المزيد عن ماهية WebAssembly في الواقع.

اقتباس من WebAssembly.org:

عندما تجمع جزءًا من رمز C أو Rust إلى WebAssembly، ستحصل على .wasm. يحتوي على إعلان وحدة. يتكون هذا البيان من قائمة "عمليات الاستيراد" تتوقعه الوحدة من بيئتها، وهي عبارة عن قائمة بعمليات التصدير التي هذه الوحدة متاحة للمضيف (الدوال والثوابت وأجزاء الذاكرة) وبالطبع التعليمات الثنائية الفعلية للدوال الواردة فيها.

شيء لم أدركه حتى نظرت في هذا: المكدس الذي يجعل WebAssembly "جهاز افتراضي مستند إلى تسلسل استدعاء الدوال البرمجية" في مجموعة التي تستخدمها وحدات WebAssembly. تعد المكدسة VM-داخلية بالكامل لا يمكن لمطوّري البرامج على الويب الوصول إليه (إلا من خلال "أدوات مطوّري البرامج"). وبناءً على ذلك، من الممكن لكتابة وحدات WebAssembly التي لا تحتاج على الإطلاق إلى ذاكرة إضافية عدم استخدام سوى حزم VM-داخلية.

في هذه الحالة، سنحتاج إلى استخدام ذاكرة إضافية للسماح بالوصول العشوائي إلى وحدات البكسل في الصورة وننشئ نسخة مستديرة من تلك الصورة. هذا هو الغرض من استخدام WebAssembly.Memory.

إدارة الذاكرة

وبشكل عام، بمجرد استخدام ذاكرة إضافية، ستجد الحاجة إلى في إدارة تلك الذكرى. ما هي أجزاء الذاكرة المستخدَمة؟ أي منها مجانية؟ على سبيل المثال، في لغة C، تكون لديك الدالة malloc(n) التي تبحث عن مساحة في الذاكرة من إجمالي n بايت متتالي. تُعرف الدوال من هذا النوع أيضًا باسم "أدوات تخصيص الإعلانات". بالطبع يجب تضمين عملية تنفيذ التخصيص المستخدم في WebAssembly التي ستزيد من حجم الملف. لا شك في أن هذا الحجم والأداء من وظائف إدارة الذاكرة هذه يمكن أن تختلف اختلافًا كبيرًا اعتمادًا على الخوارزمية المستخدمة، ولهذا السبب توفر العديد من اللغات عمليات تنفيذ متعددة للاختيار من بينها ("dmalloc" و"emmalloc" و"wee_alloc" وما إلى ذلك).

وفي حالتنا، نعرف أبعاد الصورة المدخلة (وبالتالي وأبعاد صورة الإخراج) قبل تشغيل وحدة WebAssembly. هنا نحن فرصة: عادةً ما نمرر المخزن المؤقت RGBA الخاص بالصورة المُدخلة كمخزن إلى دالة WebAssembly وإرجاع الصورة التي تم تدويرها كإرجاع لإنشاء تلك القيمة المعروضة، يتعين علينا الاستفادة من مخصص. وبما أننا نعرف المساحة الإجمالية المطلوبة للذاكرة (ضعف حجم الإدخال صورة، مرة للإدخال ومرة للمخرجات)، يمكننا وضع صورة المدخلات في ذاكرة WebAssembly باستخدام JavaScript، شغِّل وحدة WebAssembly لإنشاء الثانية، تم تدوير الصورة ثم استخدام JavaScript لقراءة النتيجة. يمكننا الحصول على بدون استخدام أي إدارة للذاكرة على الإطلاق!

استكشِفها للاختيار

إذا نظرت إلى دالة JavaScript الأصلية التي نريدها على WebAssembly، يمكنك أن ترى أنها عملية حسابية بحت مع عدم وجود واجهات برمجة تطبيقات خاصة بـ JavaScript. لذلك يجب أن يكون مستقيمًا إلى حد ما إعادة التوجيه لنقل هذا الرمز إلى أي لغة. قيّمنا 3 لغات مختلفة يتم تجميعها مع WebAssembly: C/C++ وRust وAssemblyScript. السؤال الوحيد لكل لغة من هذه اللغات: كيف نصل إلى الذاكرة الأولية بدون استخدام وظائف إدارة الذاكرة؟

C وEmscripten

Emscripten هو برنامج تجميع بلغة C لهدف WebAssembly. إن هدف Emscripten هو تعمل كبديل منخفض لبرامج التحويل البرمجي لـ C المعروفة مثل GCC أو clang ويتوافق في الغالب مع العلامات هذا جزء أساسي من مهمة Emscripten حيث تريد أن تجعل عملية تجميع رموز C وC++ الحالية إلى WebAssembly أمرًا سهلاً ممكن.

يعتمد الوصول إلى الذاكرة الأولية على طبيعة C وتوجد مؤشرات لهذا الغرض السبب:

uint8_t* ptr = (uint8_t*)0x124;
ptr[0] = 0xFF;

نعمل هنا على تحويل الرقم 0x124 إلى مؤشر إلى الإصدار 8 بت غير موقَّع. الأعداد الصحيحة (أو البايت). يؤدي ذلك إلى تحويل المتغيّر ptr بفعالية إلى مصفوفة. بدءًا من عنوان الذاكرة 0x124، والذي يمكننا استخدامه مثل أي صفيفة أخرى، وهو ما يتيح لنا الوصول إلى وحدات البايت الفردية للقراءة والكتابة. في حالتنا، نحن نبحث في المورد الاحتياطي RGBA للصورة التي نريد إعادة ترتيبها تدوير. لنقل بكسل، نحتاج في الواقع إلى نقل 4 بايت متتالية في وقت واحد (بايت واحد لكل قناة: R وG وB وA). لتسهيل ذلك، يمكننا إنشاء مصفوفة من الأعداد الصحيحة 32 بت غير الموقَّعة. حسب الاصطلاح، ستبدأ الصورة المدخلة في العنوان 4 وستبدأ صورة الإخراج مباشرة بعد صورة الإدخال ينتهي:

int bpp = 4;
int imageSize = inputWidth * inputHeight * bpp;
uint32_t* inBuffer = (uint32_t*) 4;
uint32_t* outBuffer = (uint32_t*) (inBuffer + imageSize);

for (int d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
    for (int d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
    int in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
    outBuffer[i] = inBuffer[in_idx];
    i += 1;
    }
}
.

بعد نقل دالة JavaScript بأكملها إلى C، يمكننا تجميع الملف C. مع emcc:

$ emcc -O3 -s ALLOW_MEMORY_GROWTH=1 -o c.js rotate.c

كالعادة، ينشئ emscripten ملف رمز ملتصق باسم c.js ووحدة Wasm. يسمى c.wasm. لاحظ أن وحدة Wasm يتم ضغطها بتنسيق gzip فقط إلى حوالي 260 بايت، في حين أن يكون حجم الرمز الملتصق حوالي 3.5 كيلوبايت بعد gzip. بعد بعض العبث، تمكنا من التخلص من التعليمة البرمجية الملتصقة وإنشاء مثيل لوحدات WebAssembly باستخدام واجهات برمجة تطبيقات vanilla. غالبًا ما يكون هذا ممكنًا مع Emscripten طالما أنك لا تستخدم أي شيء من مكتبة C القياسية.

Rust

Rust هي لغة برمجة جديدة وحديثة مع نظام كتابة غني بدون وقت تشغيل. ونموذج ملكية يضمن أمان الذاكرة وسلامة سلسلة المحادثات. صدأ WebAssembly كميزة أساسية، وقد ساعد فريق Rust ساهمت كثيرًا في استخدام أدوات ممتازة في منظومة WebAssembly المتكاملة.

إحدى هذه الأدوات هي wasm-pack، بواسطة مجموعة عمل Rustwasm. wasm-pack تأخذ التعليمات البرمجية الخاصة بك وتحولها إلى وحدة متوافقة مع الويب تعمل مبتكرة مع برامج الحزم مثل webpack. تطبيق wasm-pack رائع للغاية تجربة ملائمة، ولكنه يعمل حاليًا مع Rust فقط. المجموعة هي تفكر في إضافة دعم للغات الأخرى التي تستهدف WebAssembly.

في Rust، الشرائح هي الصفائف في C. وكما هو الحال في C، نحتاج إلى إنشاء الشرائح التي تستخدم عناوين البداية. يتعارض هذا الإجراء مع نموذج أمان الذاكرة. الذي يفرضه Rust، لذا لنتمكن من استخدام الكلمة الرئيسية unsafe، مما يتيح لنا كتابة التعليمات البرمجية التي لا تتوافق مع هذا النموذج.

let imageSize = (inputWidth * inputHeight) as usize;
let inBuffer: &mut [u32];
let outBuffer: &mut [u32];
unsafe {
    inBuffer = slice::from_raw_parts_mut::<u32>(4 as *mut u32, imageSize);
    outBuffer = slice::from_raw_parts_mut::<u32>((imageSize * 4 + 4) as *mut u32, imageSize);
}

for d2 in 0..d2Limit {
    for d1 in 0..d1Limit {
    let in_idx = (d1Start + d1 * d1Advance) * d1Multiplier + (d2Start + d2 * d2Advance) * d2Multiplier;
    outBuffer[i as usize] = inBuffer[in_idx as usize];
    i += 1;
    }
}

يعد تجميع ملفات Rust باستخدام

$ wasm-pack build

ينتج عنها وحدة Wasm بحجم 7.6 كيلوبايت وبها حوالي 100 بايت من الرمز الملتصق (كلاهما بعد gzip).

AssemblyScript

تُعد AssemblyScript من الأصغر سنًا الذي يهدف إلى أن يكون برنامج تجميع TypeScript-to-WebAssembly. من المهم ومع ذلك، تجدر الإشارة إلى أنها لن تستنفد أي نوع من النصوص البرمجية من النوع TypeScript. يستخدم AssemblyScript نفس بناء الجملة مثل TypeScript ولكنه يستبدل المعيار مكتبتهم الخاصة. نماذج مكتبتهم القياسية إمكانيات WebAssembly. وهذا يعني أنه لا يمكنك تجميع أي نص من النوع TypeScript لديك كذب. إلى WebAssembly، لكن هذا يعني أنك لن تحتاج إلى تعلم لغة لغة برمجة لكتابة WebAssembly!

    for (let d2 = d2Start; d2 >= 0 && d2 < d2Limit; d2 += d2Advance) {
      for (let d1 = d1Start; d1 >= 0 && d1 < d1Limit; d1 += d1Advance) {
        let in_idx = ((d1 * d1Multiplier) + (d2 * d2Multiplier));
        store<u32>(offset + i * 4 + 4, load<u32>(in_idx * 4 + 4));
        i += 1;
      }
    }

بالنظر إلى سطح النوع الصغير في دالة rotate()، كان من السهل إلى حد ما نقل هذه التعليمة البرمجية إلى AssemblyScript. توفر AssemblyScript الدالتين load<T>(ptr: usize) وstore<T>(ptr: usize, value: T) الوصول إلى الذاكرة الأولية. لتجميع ملف AssemblyScript، نحتاج فقط إلى تثبيت حزمة AssemblyScript/assemblyscript npm وتشغيل

$ asc rotate.ts -b assemblyscript.wasm --validate -O3

ستزوّدنا شركة AssemblyScript بوحدة Wasm بحجم 300 بايت تقريبًا وبدون رموز غراء. تعمل الوحدة فقط مع واجهات برمجة تطبيقات vanilla WebAssembly.

تحليل WebAssembly

إنّ حجم 7.6 كيلوبايت في Rust كبير بدرجة مدهشة مقارنةً باللغتَين الأخريَين. هناك هما أداتان في النظام الشامل WebAssembly يمكن أن تساعدك في تحليل ملفات WebAssembly (بغض النظر عن اللغة التي تم إنشاؤها بها) بما يحدث وتساعدك أيضًا على تحسين موقفك

تويغي

Twiggy هي أداة أخرى من تطوير Rust يقوم فريق WebAssembly باستخراج مجموعة من البيانات المفيدة من WebAssembly واحدة. ولا تختلف الأداة عن Rust، كما تسمح لك بفحص أشياء مثل الرسم البياني لاستدعاء الوحدة، وتحديد الأقسام غير المستخدمة أو الزائدة واكتشاف الأقسام التي تساهم في إجمالي حجم الملف للوحدة. تشير رسالة الأشكال البيانية يمكن تنفيذ الأمر الأخير باستخدام أمر top في Twiggy:

$ twiggy top rotate_bg.wasm
لقطة شاشة لتثبيت Twiggy

في هذه الحالة، يمكننا أن نرى أن معظم حجم الملفات ينبع من مخصص. كان ذلك مفاجئًا لنا لأنّ الرمز البرمجي لا يستخدم تخصيصات ديناميكية. هناك عامل مساهم كبير آخر وهو "أسماء الدوال" القسم الفرعي.

شريط Wasm

wasm-strip هي أداة من WebAssembly Binary Toolkit، أو wabt باختصار. ويشتمل على أداتين تتيحان لك فحص وحدات WebAssembly ومعالجتها. إنّ wasm2wat هي أداة تفكيك يحوِّل وحدة Wasm الثنائية إلى تنسيق يمكن لشخص عادي قراءته. يحتوي تطبيق Wabt أيضًا على العنصر wat2wasm الذي يتيح لك تحويل هذا التنسيق الذي يمكن للإنسان قراءته إلى وحدة Wasm ثنائية. بينما استخدمنا هاتان الأداتان المتكاملتان لفحص ملفات WebAssembly، وجدنا wasm-strip ليكون الأكثر فائدة. يزيل wasm-strip الأقسام غير الضرورية والبيانات الوصفية من وحدة WebAssembly:

$ wasm-strip rotate_bg.wasm

ويؤدي ذلك إلى تقليل حجم ملف وحدة Rust من 7.5 كيلوبايت إلى 6.6 كيلوبايت (بعد gzip).

wasm-opt

wasm-opt هي أداة من Binaryen. فهي تأخذ وحدة WebAssembly وتحاول تحسينها من حيث الحجم الأداء على أساس رمز البايت فقط. بعض الأدوات مثل Emscripten قيد التشغيل بالفعل هذه الأداة، والبعض الآخر لا يستخدمها. من الجيد عادةً محاولة حفظ بعض وحدات بايت إضافية باستخدام هذه الأدوات.

wasm-opt -O3 -o rotate_bg_opt.wasm rotate_bg.wasm

يمكننا باستخدام wasm-opt إزالة بعض وحدات البايت الأخرى لترك إجمالي 6.2 كيلوبايت بعد gzip.

#![no_std]

بعد إجراء بعض الاستشارات والأبحاث، أعدنا كتابة Rust code بدون استخدام بمكتبة Rust القياسية، باستخدام #![no_std] الجديدة. يؤدي هذا أيضًا إلى تعطيل تخصيصات الذاكرة الديناميكية تمامًا، وإزالة من وحدتنا. تجميع ملف Rust هذا مع

$ rustc --target=wasm32-unknown-unknown -C opt-level=3 -o rust.wasm rotate.rs

مجموعة Wasm بحجم 1.6 كيلوبايت بعد wasm-opt وwasm-strip وgzip. إنّه لا تزال أكبر من الوحدات التي تم إنشاؤها بواسطة C وAssemblyScript، فإنها بما يكفي ليكون خفيفًا.

الأداء

قبل أن ننتقل إلى الاستنتاجات بناءً على حجم الملف فقط - ذهبنا في رحلة لتحسين الأداء، وليس حجم الملف. إذن كيف قمنا بقياس الأداء ماذا كانت النتائج؟

كيفية قياس الأداء

على الرغم من أنّ WebAssembly تنسيق رمز بايت منخفض المستوى، لا يزال يجب إرساله. من خلال برنامج تحويل لإنشاء رمز جهاز خاص بالمضيف. وتمامًا مثل JavaScript، يعمل برنامج التجميع في مراحل متعددة. وأقول ببساطة: المرحلة الأولى هي أسرع في التجميع ولكنها تميل إلى إنشاء رموز أبطأ. بمجرد بدء الوحدة قيد التشغيل، يلاحظ المتصفح الأجزاء التي يتم استخدامها بشكل متكرر ويرسلها من خلال برنامج تجميع أكثر بطئًا ولكن في الوقت نفسه

من المثير للاهتمام في حالة الاستخدام لدينا أنه سيتم استخدام رمز تدوير الصورة مرة، ربما مرتين. لذا، في الغالبية العظمى من الحالات، لن نحصل أبدًا على مزايا برنامج التحويل البرمجي للتحسين. من المهم وضع ذلك في الاعتبار عند لقياس الأداء. تشغيل وحدات WebAssembly 10000 مرة بشكل متكرر سيمنحك نتائج غير واقعية. للحصول على أعداد واقعية، ينبغي لنا تشغيل الوحدة مرة واحدة واتخاذ القرارات بناءً على الأرقام من تلك الجولة الواحدة.

مقارنة الأداء

مقارنة السرعة لكل لغة
مقارنة السرعة لكل متصفح

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

وبدون تحليل هذه الرسومات البيانية أكثر من اللازم، يتضح أننا حللنا المحتوى مشكلة في الأداء: تعمل جميع وحدات WebAssembly خلال 500 ملي ثانية تقريبًا أو أقل. هذا النمط ما طرحناه في البداية: تقدّم لك WebAssembly إمكانية توقُّع أدائه. وبغض النظر عن اللغة التي نختارها، فإنّ التباين بين المتصفحات واللغات هو الحد الأدنى. على وجه التحديد: الانحراف المعياري لـ JavaScript عبر جميع المتصفحات هو حوالي 400 ملّي ثانية، في حين أن الانحراف المعياري لجميع تصل مدة وحدات WebAssembly في جميع المتصفحات إلى 80 ملي ثانية تقريبًا.

الجهد

وهناك مقياس آخر وهو مقدار الجهد الذي اضطررنا إلى بذله لإنشاء ودمج وحدة WebAssembly الخاصة بنا إلى squoosh. من الصعب تعيين قيمة رقمية والجهد، لذلك لن أقوم بإنشاء أي رسوم بيانية ولكن هناك بعض الأشياء التي أود ملاحظة:

كانت AssemblyScript سلسة للغاية. فهي لا تسمح لك باستخدام TypeScript فحسب بكتابة WebAssembly، أجعل مراجعة التعليمات البرمجية أمرًا سهلاً للغاية لزملائي، لكنها كانت أيضًا إنتاج وحدات WebAssembly خالية من الصمغ صغيرة جدًا ومتوافقة أدائه. الأدوات في النظام البيئي لـ TypeScript، مثل pretier وtslint، أن يعمل فقط.

إنّ استخدام الصدأ مع wasm-pack هو مناسب جدًا أيضًا ولكنه ممتاز. وأكثر في مشروعات WebAssembly الأكبر كانت عمليات الربط وإدارة الذاكرة احتاجت. كان علينا الابتعاد قليلاً عن المسار الصحيح لتحقيق هدف تنافسي حجم الملف.

أنشأ كل من C وEmscripten وحدة WebAssembly صغيرة جدًا وذات أداء عالٍ ولكن بدون الشجاعة لاستخدام قواعد الغراء وتقليلها من الضروري أن يكون الحجم الإجمالي (وحدة WebAssembly + الرمز الملتصق) ينتهي كبير جدًا.

الخاتمة

إذن، ما اللغة التي يجب استخدامها إذا كان لديك مسار JavaScript سريع وتريد أسرع أو أكثر اتساقًا مع WebAssembly. وكما هو الحال دائمًا مع الأداء على الأسئلة، فإن الإجابة هي: على حسب. إذًا ماذا شحننا؟

الرسم البياني للمقارنة

المقارنة بين حجم الوحدة / الأداء للغات المختلفة التي استخدمناها، فإن الخيار الأفضل هو إما C أو AssemblyScript. قرّرنا شحن Rust. هناك أسباب متعددة لهذا القرار: جميع برامج الترميز التي تم شحنها إلى Squoosh حتى الآن باستخدام Emscripten. أردنا توسيع نطاق معرفتنا حول منظومة WebAssembly المتكاملة وتستخدم لغة مختلفة في عملية الإنتاج تُعد AssemblyScript بديلاً قويًا، ولكن المشروع صغير نسبيًا فإن المحول ليس ناضجًا مثل محول Rust.

في حين أن الفرق في حجم الملف بين Rust واللغات الأخرى في الرسم البياني للنقاط المبعثرة، لا يمثل هذا الأمر مشكلة كبيرة في الواقع: يستغرق تحميل 500 مليار أو 1.6 كيلوبايت حتى عبر شبكة الجيل الثاني أقل من 1/10 من الثانية. و ونأمل أن يسد Rust الفجوة في ما يتعلق بحجم الوحدة قريبًا.

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

ويُرجى العِلم أنّ AssemblyScript كانت اكتشافًا عظيمًا. وهو يسمح للويب على المطوّرين إنتاج وحدات WebAssembly بدون الحاجة إلى تعلُّم . لقد كان فريق AssemblyScript سريع الاستجابة بشكل كبير وكان فعالاً يعملون على تحسين سلسلة الأدوات الخاصة بهم. سنراقب بالتأكيد AssemblyScript في المستقبل.

تحديث: Rust

بعد نشر هذه المقالة، قال زين فيتزجيرالد من فريق Rust إلى كتاب Rust Wasm الممتاز، الذي يحتوي على قسم حول تحسين حجم الملف. يساعد اتباع (ويشمل ذلك على وجه الخصوص تفعيل تحسينات وقت الربط والدليل التعامل مع الذعر) سمح لنا بكتابة رمز صدئ "عادي" والعودة إلى استخدام Cargo (npm الذي يبلغ حجم الملف Rust) بدون تكبير حجم الملف. تنتهي وحدة Rust مع 370B بعد gzip. للاطّلاع على التفاصيل، يُرجى إلقاء نظرة على PR الذي فتحته على Squoosh.

نشكر آشلي ويليامز وستيف كلابنيك ونك فيتزجيرالد وماكس غراي على كل مساعدتهم في هذه الرحلة.