في الفصل الأخير من الجامعة ، اخترت
الدورة التدريبية لبرنامج التحويل البرمجي CS444 . هناك ، كان على كل مجموعة من 1-3 أشخاص كتابة مترجم من مجموعة فرعية كبيرة من Java في x86. اللغة لاختيار مجموعة. كانت هذه فرصة نادرة لمقارنة تطبيقات البرامج الكبيرة التي لها نفس الوظيفة ، والتي كتبها مبرمجون أكفاء للغاية بلغات مختلفة ، ولمقارنة الفرق في التصميم واختيار اللغة. أعطت مثل هذه المقارنة الكثير من الأفكار المثيرة للاهتمام. نادراً ما تتم مشاهدة مثل هذه المقارنة الخاضعة للرقابة من اللغات. إنها ليست مثالية ، لكنها أفضل بكثير من معظم القصص الذاتية التي تستند إليها آراء الناس حول لغات البرمجة.
لقد صنعنا مترجم Rust ، وقمت أولاً بمقارنته بمشروع فريق Haskell. كنت أتوقع أن يكون برنامجهم أقصر بكثير ، لكن اتضح أنه بنفس الحجم أو أكبر. الشيء نفسه ينطبق على OCaml. ثم قارنته مع برنامج التحويل البرمجي C ++ ، وكان من المتوقع تمامًا أن يكون برنامج التحويل البرمجي أكبر بنسبة 30٪ تقريبًا ، ويرجع ذلك أساسًا إلى الرؤوس ونقص أنواع المبلغ ومطابقة النمط. المقارنة التالية كانت مع صديقي ، الذي جعل المترجم بمفردها في بيثون واستخدم أقل من نصف الشفرة مقارنة بنا ، نظرًا لقوة metaprogramming والأنواع الديناميكية. وكان صديق آخر برنامج سكالا أصغر. أكثر ما أثار دهشتي هو المقارنة مع فريق آخر استخدم Rust أيضًا ، لكن تبين أن لديهم ثلاثة أضعاف الكود بسبب قرارات التصميم المختلفة. في النهاية ، كان الفرق الأكبر في مقدار الشفرة في نفس اللغة!
سأشرح لماذا اعتبر هذا مقارنة جيدة ، وسأقدم بعض المعلومات حول كل مشروع وأشرح بعض أسباب الاختلافات في حجم المترجم. سأستخلص أيضًا استنتاجات من كل مقارنة. لا تتردد في استخدام هذه الروابط للانتقال إلى قسم الاهتمام:
محتوى
لماذا أجدها ذات مغزى
قبل أن تقول أن مقدار الشفرة (قارنت كل من السلاسل والبايتات) هو مقياس فظيع ، أريد أن أشير إلى أنه في هذه الحالة يمكن أن يوفر فهمًا جيدًا. على الأقل هذا هو المثال الأكثر رقابة حيث تكتب الفرق المختلفة نفس البرنامج الكبير الذي سمعت عنه أو قرأت عنه.
- لم يعلم أحد (بمن فيهم أنا) أنني سأقيس هذه المعلمة ، لذلك لم يحاول أحد تشغيل المقاييس ، حاول الجميع فقط إنهاء المشروع بسرعة وبشكل صحيح.
- الكل (باستثناء مشروع بيثون ، الذي سأناقشه لاحقًا) نفذ البرنامج لغرض وحيد هو اجتياز مجموعة الاختبار الآلية نفسها في نفس الوقت ، لذلك لا يمكن تشويه النتائج إلى حد كبير بواسطة مجموعات تعمل على حل المشكلات المختلفة.
- تم الانتهاء من المشروع في غضون بضعة أشهر ، مع الفريق ، وكان من المفترض أن تتوسع تدريجيا وتمرير كل من الاختبارات المعروفة وغير المعروفة. هذا يعني أنه كان من المفيد كتابة رمز نظيف وواضح.
- بصرف النظر عن اجتياز اختبارات الدورة التدريبية ، لن يتم استخدام الشفرة لأي شيء آخر ، ولن يقرأها أحد ، ولأنه مترجم لمجموعة فرعية محدودة من Java في أداة تجميع النصوص ، فلن يكون مفيدًا.
- لا يُسمح بأي مكتبات أخرى غير المكتبة القياسية ، ولا يُساعد في تحليلها ، حتى لو كانت موجودة في المكتبة القياسية. هذا يعني أنه لا يمكن تشويه المقارنة بواسطة مكتبات المحول البرمجي القوية التي لديها بعض الأوامر فقط.
- لم تكن هناك اختبارات عامة فحسب ، ولكن أيضًا اختبارات سرية. بدأوا مرة واحدة بعد الولادة النهائية. هذا يعني أنه كان هناك حافز لكتابة رمز الاختبار الخاص بك والتأكد من أن المترجم موثوق به وصحيح ويتعامل مع مواقف الحدود المعقدة.
- على الرغم من أن جميع المشاركين طلاب ، إلا أنني أعتبرهم مبرمجين أكفاء تمامًا. استغرق كل منهم التدريب لمدة لا تقل عن سنتين ، وخاصة في شركات التكنولوجيا الفائقة ، وأحيانا تعمل على المجمعين. كلهم تقريبًا يقومون بالبرمجة منذ 7 إلى 13 عامًا وهم متحمسون يقرؤون الكثير على الإنترنت خارج دوراتهم التدريبية.
- لم يتم أخذ الشفرة التي تم إنشاؤها في الاعتبار ، لكن ملفات القواعد والرمز الذي أنشأ الشفرة الأخرى تم أخذها في الاعتبار.
وبالتالي ، أعتقد أن مقدار الكود يوفر فهماً جيداً لمقدار الجهد المطلوب لدعم كل مشروع ، إذا كان طويلاً. أعتقد أنه ليس هناك فرق كبير بين المشروعات يسمح لك أيضًا بتفنيد بعض العبارات الاستثنائية التي قرأتها ، على سبيل المثال ، أن برنامج التحويل البرمجي Haskell سيكون أكثر من نصف حجم C ++ بسبب اللغة.
الصدأ (أساس للمقارنة)
كتب أنا وأحد رفاقي أكثر من 10 آلاف سطر في Rust قبل ذلك ، وقد كتب الزميل الثالث ، ربما ، 500 سطر في بعض الهاكاثون. تم إنتاج برنامج التحويل البرمجي الخاص بنا في 6806 سطرًا من
wc -l
، و 5900 سطرًا من المصدر (بدون مسافات وتعليقات) ، و 220 كيلو بايت
wc -c
.
لقد وجدت أن هذه النسب تحظى باحترام تقريبي في المشروعات الأخرى ، مع بعض الاستثناءات القليلة ، والتي سألاحظها. بالنسبة لبقية المقالة ، عندما أشير إلى سلاسل أو مبالغ ، أعني
wc -l
، لكن هذا لا يهم (إلا إذا لاحظت الفرق) ، ويمكنك التحويل باستخدام معامل.
كتبت مقالا
آخر يصف تصميمنا ، والذي اجتاز جميع الاختبارات العامة والسرية. يحتوي أيضًا على بعض الميزات الإضافية التي صنعناها من أجل المتعة ، وليس لاجتياز الاختبارات ، والتي ربما أضافت حوالي 400 سطر. كما أن لديها حوالي 500 خط من اختبارات وحدتنا.
هاسكل
ضم فريق هاسكل اثنين من أصدقائي الذين ربما كتبوا بضعة آلاف سطر من هاسكل ، بالإضافة إلى قراءة الكثير من المحتوى عبر الإنترنت حول هاسكل ولغات وظيفية أخرى مماثلة مثل OCaml و Lean. كان لديهم زميل آخر في الفريق لم أكن أعرفه جيدًا ، لكن يبدو أن مبرمجًا قويًا استخدم هاسكل من قبل.
بلغ مجموع المترجم الخاص بهم 9،750 سطرًا من
wc -l
و 357 كيلو بايت و 7777 سطرًا من التعليمات البرمجية (SLOC). لدى هذا الفريق أيضًا الاختلافات المهمة الوحيدة بين هذه النسب: إن برنامج التحويل البرمجي الخاص به أكبر بمقدار 1.4 مرة من مجموعتنا في الصفوف ، و 1.3 مرة في SLOC و 1.6 مرة في البايت. أنها لم تنفذ أي وظائف إضافية ، مرت 100 ٪ من الاختبارات العامة والسرية.
من المهم أن نلاحظ أن إدراج الاختبارات أثر على هذا الفريق أكثر من أي شيء آخر. نظرًا لأنهم اقتربوا بعناية من صحة الكود ، فقد تضمنوا 1600 سطر من الاختبارات. لقد اكتشفوا العديد من المواقف الحدودية التي لم يصادفها فريقنا ، ولكن لم يتم التحقق من هذه الحالات ببساطة عن طريق اختبارات الدورة التدريبية. لذلك من دون اختبارات على كلا الجانبين (6.3 ألف سطر مقابل 8.1 ألف خط) فإن المترجم الخاص بهم يزيد بنسبة 30٪ فقط عن المترجم.
أميل هنا إلى البايت كمقياس أكثر معقولية لمقارنة الحجم ، لأنه في مشروع Haskell ، في المتوسط ، هناك خطوط أطول ، لأنه لا يحتوي على عدد كبير من الأسطر من قوس إغلاق واحد ، ولا يقوم
rustfmt
بتقسيم سلاسل الوظائف ذات الخط المفرد إلى عدة خطوط.
بعد البحث مع أحد زملائي في الفريق ، توصلنا إلى التفسير التالي لهذا الاختلاف:
- استخدمنا محلل معجم مكتوب بخط اليد وطريقة نزول عودي ، واستخدموا مولد NFA و DFA ومحلل LR ، ثم تمريرة لتحويل شجرة التحليل إلى AST ( شجرة بناء جملة مجردة ، تمثيل أكثر ملاءمة للرمز). هذا أعطاهم كودًا أكبر بكثير: 2677 سطرًا مقارنة بـ 1705 ، أي حوالي 1000 سطر.
- استخدموا AST الخيالية العامة ، والتي انتقلت إلى معلمات أنواع مختلفة حيث تم إضافة المزيد من المعلومات في كل تمريرة. من المحتمل أن تشرح هذه الوظيفة والمزيد من الوظائف المساعدة لإعادة الكتابة السبب وراء طول رمز AST بحوالي 500 سطر من تطبيقنا ، حيث نجمع القيم الحرفية الهيكلية ونغير مجالات
Option<_>
لإضافة معلومات أثناء تصفحنا.
- لا يزال لديهم حوالي 400 سطر من الشفرة أثناء التوليد ، والتي ترتبط بشكل أساسي مع التجريد الأكبر اللازم لإنشاء الشفرة ودمجها بطريقة وظيفية بحتة ، حيث نستخدم ببساطة خطوط التحويل والكتابة.
تشرح هذه الاختلافات بالإضافة إلى الاختبارات جميع الاختلافات في الحجم. في الواقع ، ملفاتنا لثوابت قابلة للطي ودقة السياق قريبة جدا في الحجم. لكن مع ذلك ، يوجد بعض الاختلاف في البايت بسبب الخطوط الأطول: ربما لأنه يلزم المزيد من التعليمات البرمجية لإعادة كتابة الشجرة بأكملها في كل تمريرة.
كنتيجة لذلك ، وبصرف النظر عن القرارات المتعلقة بالتصميم ، في رأيي ، فإن Rust و Haskell معبران على قدم المساواة ، ربما مع ميزة بسيطة Rust بسبب القدرة على استخدام الطفرة بسهولة عندما تكون مريحة. كان من المثير للاهتمام أيضًا أن أعرف أن خياري لطريقة النسب العودية والمحلل المعجم المكتوب بخط اليد يؤتي ثماره: لقد كان ذلك مخاطرة تتعارض مع توصيات وتعليمات الأستاذ ، لكنني قررت أنه كان أسهل وكان ذلك صحيحًا.
سوف يجادل عشاق Haskell أن ذلك الفريق ربما لم يستفيد بالكامل من ميزات Haskell ، وإذا كانوا يعرفون اللغة بشكل أفضل ، فيمكنهم إنشاء مشروع برمز أقل. أوافق ، يمكن لشخص مثل
إدوارد كميت أن يكتب نفس المترجم بكمية أقل. في الواقع ، لم يستخدم فريق صديقي العديد من التجريدات الفائقة المتطورة والمكتبات المدمجة المختلطة مثل
العدسات . ومع ذلك ، كل هذا يؤثر على إمكانية قراءة التعليمات البرمجية. جميع الأشخاص في الفريق هم مبرمجون ذوو خبرة ، وكانوا يعرفون أن هاسكل كان قادرًا على القيام بأشياء غريبة للغاية ، لكنهم قرروا عدم استخدامها لأنهم قرروا أن فهمهم سيستغرق وقتًا أكثر مما سيوفرون ، ويجعل الكود أكثر صعوبة في فهمه. يبدو هذا كحل وسط حقيقي ، والادعاء بأن Haskell مناسب بطريقة سحرية للمترجمين يتحول إلى شيء مثل "Haskell يتطلب مهارة عالية للغاية في كتابة مترجمين إذا كنت لا تهتم بدعم الكود للأشخاص الذين ليسوا ماهرين للغاية في Haskell."
من المثير للاهتمام أيضًا ملاحظة أنه في بداية كل مشروع ، يقول الأستاذ أنه يمكن للطلاب استخدام أي لغة يتم تشغيلها على خادم الجامعة ، لكنه يحذر من أن الفرق الموجودة في Haskell مختلفة عن البقية: لديهم أكبر مبعثر في الدرجات. كثير من الناس يبالغون في تقدير قدراتهم وفرق Haskell لديها درجات سيئة للغاية ، على الرغم من أن البعض الآخر لا بأس به مثل أصدقائي.
C ++
ثم تحدثت مع صديقي من فريق C ++. لقد عرفت شخصًا واحدًا فقط في هذا الفريق ، ولكن تم استخدام C ++ في العديد من الدورات في جامعتنا ، لذلك ربما كان لدى كل فرد في الفريق خبرة C ++.
يتألف مشروعهم من 8733 سطرًا و 280 كيلوبايت ، وليس بما في ذلك رمز الاختبار ، ولكن يشمل حوالي 500 سطر من الوظائف الإضافية. مما يجعله أكبر بـ 1.4 مرة من الكود الخاص بنا دون اختبارات ، والذي يحتوي أيضًا على حوالي 500 سطر من الوظائف الإضافية. اجتازوا 100 ٪ من الاختبارات العامة ، ولكن 90 ٪ فقط من الاختبارات السرية. من المفترض أنهم لم يطبقوا صفيفات vtables الفاخرة التي تتطلبها المواصفات ، والتي ربما تحتوي على 50-100 سطر من التعليمات البرمجية.
لم أتعمق في هذه الاختلافات في الحجم. أعتقد أن هذا يرجع بشكل رئيسي إلى:
- يستخدمون المحلل اللغوي و rewriter شجرة بدلاً من أسلوب النسب العودية.
- عدم وجود أنواع المبلغ ومقارنات الأنماط في C ++ ، والتي استخدمناها على نطاق واسع والتي كانت مفيدة للغاية.
- الحاجة إلى تكرار جميع التواقيع في ملفات الرأس ، وهذا ليس هو الحال في Rust.
نحن أيضا مقارنة وقت التجميع. على جهاز الكمبيوتر المحمول الخاص بي ، يستغرق إنشاء برنامج تصحيح الأخطاء النظيف الخاص بالمترجم الخاص بنا 9.7 ثوانٍ ، والإصدار النظيف 12.5 ثانية ، وتصحيح الأخطاء التدريجي الإصدار 3.5 ثانية. لم يكن لدى صديقي توقيت في متناول اليد لبناء C ++ الخاص بهم (باستخدام تكوين متوازي) ، لكنه قال إن الأرقام متشابهة ، مع التحذير من أنهم وضعوا تطبيقات للعديد من الوظائف الصغيرة في ملفات الرأس لتقليل ازدواج التواقيع بتكلفة وقت أطول (أي لذلك ، لا يمكنني قياس مقدار الحمل net line في ملفات الرأس).
الثعبان
صديقي ، مبرمج جيد للغاية ، قرر أن يجعل المشروع وحده في بيثون. قامت أيضًا بتنفيذ ميزات أكثر تقدماً (للمتعة) من أي فريق آخر ، بما في ذلك طريقة عرض SSA وسيطة مع تخصيص التسجيل والتحسينات الأخرى. من ناحية أخرى ، نظرًا لأنها عملت بمفردها ونفذت العديد من الوظائف الإضافية ، فقد أولت الاهتمام الأقل لجودة الشفرة ، على سبيل المثال ، رمي استثناءات غير متمايزة لجميع الأخطاء (بالاعتماد على backtraces لتصحيح الأخطاء) بدلاً من تطبيق أنواع الأخطاء والرسائل المقابلة ، مثل لنا.
وتألفت مترجمها من 4581 خطًا واجتازت جميع الاختبارات العامة والسرية. قامت أيضًا بتنفيذ وظائف أكثر تقدماً من أي أمر آخر ، لكن من الصعب تحديد مقدار الكود الإضافي الذي استلزمه الأمر ، لأن العديد من الوظائف الإضافية كانت نسخًا أقوى من الأشياء البسيطة التي يحتاجها الجميع لتنفيذها ، مثل الثوابت القابلة للطي وتوليد الكود. من المحتمل أن تكون الوظائف الإضافية من 1000 إلى 2000 سطر ، على الأقل ، لذلك أنا متأكد من أن الكود الخاص بها هو على الأقل ضعف معبر مثلنا.
جزء كبير من هذا الاختلاف هو الكتابة الديناميكية على الأرجح. يوجد فقط 500 سطر من تعريفات الأنواع في
ast.rs
لدينا ، والعديد من الأنواع الأخرى محددة في أي مكان آخر في المجمع. نحن أيضًا مقيدون دائمًا بنظام الكتابة نفسه. على سبيل المثال ، نحتاج إلى بنية تحتية لإضافة معلومات جديدة هندسيًا إلى AST أثناء مرورنا عليها والوصول إليها لاحقًا. أثناء وجودك في Python ، يمكنك فقط تعيين حقول جديدة على عقد AST.
metaprogramming قوية كما يفسر جزء من الفرق. على سبيل المثال ، على الرغم من أنها استخدمت محلل LR بدلاً من طريقة نزول عودي ، في حالتي أعتقد أنه استغرق رمزًا أقل لأنه بدلاً من المرور عبر إعادة كتابة شجرة ، تضمنت قواعد LR الخاصة بها أجزاء من شفرة Python لإنشاء AST ، والتي يمكن للمولد أن يتحول إلى وظائف Python باستخدام
eval
. جزء من السبب في أننا لم نستخدم المحلل اللغوي LR لأن إنشاء AST دون إعادة كتابة الشجرة سيتطلب الكثير من الاحتفالات (إنشاء ملفات Rust أو وحدات ماكرو إجرائية) لربط القواعد النحوية بشظايا كود Rust.
مثال آخر على قوة metaprogramming والكتابة الديناميكية هو ملف
visit.rs
الذي
visit.rs
400 سطر ، والذي يعد في الأساس رمزًا متكررًا ينفذ الزائر على مجموعة من هياكل AST. في Python ، يمكن أن تكون هذه وظيفة قصيرة من حوالي 10 سطور
__dict__
بشكل متكرر حقول عقدة AST
__dict__
(باستخدام السمة
__dict__
).
بصفتي من عشاق Rust واللغات المكتوبة بشكل ثابت بشكل عام ، أميل إلى ملاحظة أن نظام الكتابة مفيد جدًا لمنع الأخطاء والأداء. metaprogramming غير عادية يمكن أيضاً أن يجعل من الصعب فهم كيفية عمل التعليمات البرمجية. ومع ذلك ، فاجأتني هذه المقارنة بحقيقة أنني لم أتوقع أن يكون الفرق في مقدار الكود كبيرًا جدًا. إذا كان الاختلاف ككل قريبًا جدًا من الاضطرار إلى كتابة ضعف الكود ، فما زلت أعتقد أن Rust هو حل وسط مناسب ، لكن ما زال نصف الكود عبارة عن حجة ، وفي المستقبل أميل إلى فعل شيء ما في Ruby / Python إذا كنت بحاجة فقط إلى بناء شيء بمفرده بسرعة ، ثم رميه بعيدًا.
الصدأ (مجموعة أخرى)
المقارنة الأكثر إثارة للاهتمام بالنسبة لي كانت مع صديقي ، الذي كان يقوم أيضًا بمشروع في Rust مع زميله في الفريق (الذي لم أكن أعرفه). كان صديقي تجربة الصدأ جيدة. ساهم في تطوير المترجم وقراءة الكثير. لا أعرف أي شيء عن رفيقه.
يتألف مشروعهم من 17،211 خطًا خامًا وخطوط مصدر 15 كيلو و 637 كيلوبايت ، وليس بما في ذلك رمز الاختبار والرمز الذي تم إنشاؤه. لم يكن لديها وظائف إضافية ، ومرت فقط 4 من 10 اختبارات سرية و 90 ٪ من الاختبارات العامة لتوليد الشفرة ، لأنها لم يكن لديها ما يكفي من الوقت قبل الموعد النهائي لتنفيذ المزيد من أجزاء غريبة من المواصفات. برنامجهم أكبر بثلاث مرات من برنامجنا ، مكتوب بنفس اللغة وبأداء أقل!
كانت هذه النتيجة مذهلة حقًا بالنسبة لي وألقت بظلالها على جميع الاختلافات بين اللغات التي درستها حتى الآن. لذلك ، قمنا بمقارنة قوائم أحجام ملفات
wc -l
، كما قمنا بفحص كيفية تنفيذ كل منا لبعض الأشياء المحددة التي أدت إلى أحجام مختلفة من التعليمات البرمجية.
يبدو أن كل ذلك يعود إلى الاعتماد المتسق لقرارات التصميم المختلفة. على سبيل المثال ، الواجهة الأمامية (التحليل المعجمي ، التحليل ، بناء AST) تأخذ 7597 سطرًا مقابل 2164. استخدموا المحلل المعجمي DFA ومحلل LALR (1) ، لكن المجموعات الأخرى فعلت أشياء مماثلة دون الكثير من الكود. بالنظر إلى ملف الأعشاب الضارة ، لاحظت عددًا من قرارات التصميم التي كانت مختلفة عن قراراتنا:
- قرروا استخدام شجرة تحليل مكتوبة بالكامل بدلاً من شجرة تحليل موحدة وموحدة وقائمة على السلسلة. ربما تطلب هذا تعريفات أكثر بكثير للنوع ورمز تحويل إضافي في مرحلة التحليل أو محلل أكثر تعقيدًا.
- لقد استخدموا تطبيقات
tryfrom
tryfrom للتحويل بين أنواع تحليل الأشجار وأنواع AST للتحقق منها. هذا يؤدي إلى العديد من الكتل 10-20 خط. للقيام بذلك ، استخدمنا وظائف تُرجع أنواع Result
، والتي تولد عددًا أقل من الخطوط ، كما تحررنا قليلاً من بنية الكتابة ، مما يعمل على تبسيط المعلمات وإعادة الاستخدام. بعض الأشياء التي ، بالنسبة لنا ، كانت فروع match
لخط واحد ، كانت تحتوي على كتل impl
مكونة من 10 أسطر.
- يتم هيكلة أنواعنا لتقليل لصق النسخ. على سبيل المثال ، استخدموا الحقول
is_abstract
is_native
و is_native
و is_static
، حيث يجب نسخ كود التحقق من القيد مرتين: مرة واحدة للطرق المكتوبة باطلة ومرة للطرق مع نوع الإرجاع ، مع تعديلات طفيفة. في حين أن void
كان مجرد نوع خاص ، فقد توصلنا إلى تصنيف للمعدلات مع mode
visibility
التي طبقت قيود مستوى الكتابة ، وأُنشئت أخطاء القيد افتراضيًا لمشغل المطابقة ، الأمر الذي ترجم مجموعة التعديل إلى mode
visibility
.
لم ألق نظرة على كود مرور تحليل المترجم الخاص بهم ، لكنها أيضًا رائعة. تحدثت مع صديقي ، ويبدو أنهم لم يطبقوا أي شيء مماثل للبنية التحتية للزوار ، مثلنا. أعتقد أنه ، إلى جانب بعض اختلافات التصميم الأصغر الأخرى ، يفسر الفرق في حجم هذا الجزء. يسمح الزائر بتمرير تحليلنا للتركيز فقط على أجزاء AST التي يحتاجون إليها ، بدلاً من مطابقة النمط عبر هيكل AST بأكمله. هذا يوفر الكثير من التعليمات البرمجية.
يتكون الجزء الخاص بهم لإنشاء التعليمات البرمجية من 3594 سطرًا ، والجزء الخاص بنا - 1560. لقد نظرت إلى الكود الخاص بهم ويبدو أن الفارق كله تقريبًا هو أنهم اختاروا بنية بيانات وسيطة لتعليمات المجمّع ، حيث استخدمنا فقط تنسيق السلسلة لإخراج المجمع المباشر . كان عليهم تحديد أنواع ووظائف الإخراج لجميع التعليمات المستخدمة وأنواع المعامل. وهذا يعني أيضًا أن تعليمات تجميع المباني استغرقت المزيد من التعليمات البرمجية. حيث كان لدينا مشغل تنسيق بتعليمات قصيرة ، مثل
mov ecx, [edx]
، فقد احتاج إلى مشغل
rustfmt
عملاق ،
rustfmt
إلى 6 خطوط ، والتي بنيت تعليمات مع مجموعة من الأنواع المتداخلة المتوسطة للمعاملات التي تشمل ما يصل إلى 6 مستويات من الأقواس المتداخلة. يمكننا أيضًا إخراج كتل من التعليمات ذات الصلة ، مثل ديباجة الوظيفة ، في بيان تنسيق واحد ، حيث يتعين عليهم تحديد الإنشاء الكامل لكل تعليمة.
كان فريقنا يفكر في استخدام مثل هذا التجريد مثلهم. كان من الأسهل أن تكون قادرًا على إخراج مجموعة نصية أو إصدار رمز الجهاز مباشرةً ، ولكن هذا لم يكن متطلبًا في الدورة التدريبية. يمكن القيام بنفس الشيء باستخدام رمز أقل وأداء أفضل باستخدام
X86Writer
X86Writer مع طرق مثل
push(reg: Register)
. لقد أخذنا أيضًا في الاعتبار أن هذا قد يبسط عملية التصحيح والاختبار ، لكننا أدركنا أن مشاهدة أداة تجميع النصوص التي تم إنشاؤها أسهل في الواقع في القراءة والاختبار باستخدام اختبار
اللقطة ، إذا قمت بإدراج التعليقات بشكل حر. لكننا (على ما يبدو صحيح) توقعنا أن الأمر سيستغرق الكثير من التعليمات البرمجية الإضافية ، ولم يكن هناك فائدة حقيقية ، نظرًا لاحتياجاتنا الحقيقية ، لذلك لم نكن قلقين.
من الجيد مقارنة هذا بالتمثيل الوسيط الذي استخدمه فريق C ++ كدالة إضافية ، حيث استغرق منهم 500 سطر إضافي فقط. لقد استخدموا بنية بسيطة للغاية (لتعريفات النوع البسيط ورمز البناء) التي استخدمت عمليات قريبة مما تطلبه Java. هذا يعني أن تمثيلهم الوسيط كان أصغر بكثير (وبالتالي يتطلب كود بناء أقل) من المجمّع الناتج ، حيث تم توسيع العديد من عمليات اللغة ، مثل المكالمات والقوالب ، في العديد من تعليمات المجمّع. يقولون أيضًا أن هذا ساعد في تصحيح الأخطاء ، حيث إنه يقلل من الكثير من القمامة وتحسين إمكانية القراءة. يسمح العرض التقديمي العالي المستوى أيضًا بإجراء بعض التحسينات البسيطة على تمثيلها الوسيط. لقد توصل فريق C ++ إلى تصميم جيد حقًا حقق لهم مزيدًا من الامتياز برمز أقل.
بشكل عام ، يبدو أن السبب الشائع للفرق ثلاثي الأبعاد في الحجم يرجع إلى الاعتماد المتسق لقرارات التصميم المختلفة ، الكبيرة والصغيرة ، في اتجاه المزيد من التعليمات البرمجية. لقد طبقوا عددًا من التجريدات التي لم نقم بها - لقد أضفوا المزيد من الشفرات ، وتخطوا بعض التجريدات لدينا ، مما يقلل من مقدار الشفرة.
هذه النتيجة فاجأتني حقًا. كنت أعرف أن قرارات التصميم مهمة ، لكنني لم أكن لأخمن مسبقًا أنها ستؤدي إلى أي اختلافات من هذا الحجم ، نظرًا لأنني درست فقط الأشخاص الذين أعتبرهم مبرمجين أكفاء أقوياء. من بين كل نتائج المقارنة ، هذا هو الأهم بالنسبة لي. , , , , , , , AST , .
— . , , , . : , , . , , , , , . , , (, C++), .
: , , , , , , , .
Scala
, Scala, . 4141 ~160 , . 8 10 100% . , 5906 , 30%.
. LR. , . LR. LR 150- Python, - Java, , . - Scala, 1073 1443, .
, - , . , Scala Rust. Scala Rust , , , Scala , borrow checker Rust. , Scala , Rust.
OCaml
Jane Street ( — . .), Jane Street, OCaml.
10914 377 , . 9/10 .
, , LR- , regex->NFA->DFA . ( , , AST) 5548 , — 2164, .
, snapshot-, , ~600 , — 200.
5366 (461 — ) 4642 , 15%, , , . , , , Rust OCaml , , OCaml , Rust — .
, , . , , , , , .