تجميع السلاسل في MicroPython (string interning)¶
يستخدم MicroPython string interning لتوفير كل من ذاكرة RAM و ROM. وهذا يتجنّب الحاجة إلى تخزين نسخ مكررة من السلسلة نفسها. ينطبق هذا في المقام الأول على المعرّفات في شيفرتك، إذ من المرجّح جداً أن يظهر شيء مثل اسم دالة أو متغير في أماكن متعددة من الشيفرة. في MicroPython تُسمّى السلسلة المُجمَّعة QSTR (uniQue STRing).
قيمة QSTR (من النوع qstr) هي فهرس ضمن قائمة مترابطة من مجمّعات (pools) QSTR. تخزّن سلاسل QSTR طولها وقيمة تجزئة (hash) لمحتواها لإجراء مقارنة سريعة أثناء عملية إزالة التكرار. وجميع عمليات الشيفرة الثنائية (bytecode) التي تتعامل مع السلاسل تستخدم وسيطة QSTR.
توليد QSTR وقت الترجمة (compile-time)¶
في شيفرة C الخاصة بـ MicroPython، تُكتب أي سلاسل ينبغي تجميعها في البرنامج الثابت النهائي على هيئة MP_QSTR_Foo. وفي وقت الترجمة سيُقيَّم هذا إلى قيمة qstr تشير إلى فهرس "Foo" في مجمّع QSTR.
تتولّى عملية متعددة الخطوات في Makefile تحقيق هذا. وباختصار، تتألف هذه العملية من ثلاثة أجزاء:
العثور على جميع رموز
MP_QSTR_Fooفي الشيفرة.توليد مجمّع QSTR ثابت يحتوي على جميع بيانات السلاسل (بما في ذلك الأطوال وقيم التجزئة).
استبدال جميع
MP_QSTR_Foo(عبر المعالج المسبق) بالفهرس المقابل لها.
يُبحث عن رموز MP_QSTR_Foo في مصدرين:
جميع الملفات المُشار إليها في
$(SRC_QSTR). وهذه هي كل شيفرة C (أيpyوextmodوports/stm32) ولكن دون تضمين شيفرة الطرف الثالث مثلlib.إضافات
$(QSTR_GLOBAL_DEPENDENCIES)(التي تشملmpconfig*.h).
ملاحظة: الملف frozen_mpy.c (المُولَّد بواسطة mpy-tool.py) له توليد ومجمّع QSTR خاصان به.
بعض السلاسل الإضافية التي لا يمكن التعبير عنها باستخدام صياغة MP_QSTR_Foo (مثل تلك التي تحتوي على محارف غير أبجدية رقمية) تُوفَّر بشكل صريح في qstrdefs.h و qstrdefsport.h عبر المتغير $(QSTR_DEFS).
تجري المعالجة في المراحل التالية:
الملف
qstr.i.lastهو ناتج تمرير كل ملف إدخال على حدة عبر المعالج المسبق لـ C. وهذا يعني أن أي شيفرة معطَّلة شرطياً ستُزال، وأن الماكروهات ستُوسَّع. ويعني هذا أيضاً أننا لا نضيف إلى المجمّع سلاسل لن تُستخدم في البرنامج الثابت النهائي. لأنه في هذه المرحلة (بفضل الماكروNO_QSTRالذي يضيفهQSTR_GEN_CFLAGS) لا يوجد تعريف لـMP_QSTR_Foo، فإنه يمرّ عبر هذه المرحلة دون تأثّر. كما يتضمّن هذا الملف تعليقات من المعالج المسبق تحوي معلومات أرقام الأسطر. لاحظ أن هذه الخطوة تستخدم فقط الملفات التي تغيّرت، مما يعني أنqstr.i.lastسيحتوي فقط على بيانات من الملفات التي تغيّرت منذ آخر عملية ترجمة.الملف
qstr.splitهو ملف فارغ يُنشأ بعد تشغيلmakeqstrdefs.py splitعلى qstr.i.last. ويُستخدم فقط كاعتمادية للإشارة إلى أن الخطوة قد جرت. يُخرج هذا البرنامج النصي ملفاً واحداً لكل ملف C مُدخل،genhdr/qstr/...file.c.qstr، يحتوي فقط على سلاسل QSTR المطابقة. وتُطبع كل QSTR على هيئةQ(Foo). هذه الخطوة ضرورية لدمج الملفات الموجودة مع البيانات الجديدة المُولَّدة من التحديث التزايدي فيqstr.i.last.الملف
qstrdefs.collected.hهو ناتج دمجgenhdr/qstr/*باستخدامmakeqstrdefs.py cat. وهذا الآن هو المجموعة الكاملة منMP_QSTR_Fooالموجودة في الشيفرة، مُنسَّقة الآن على هيئةQ(Foo)، واحدة في كل سطر، مع التكرارات. ولا يُحدَّث هذا الملف إلا إذا تغيّرت مجموعة سلاسل qstr. وتُكتب قيمة تجزئة لبيانات QSTR إلى ملف آخر (qstrdefs.collected.h.hash) مما يتيح تتبّع التغييرات عبر عمليات البناء.توليد تعداد (enumeration)، يربط كل مدخل فيه
MP_QSTR_Fooبفهرسه المقابل. يدمجqstrdefs.collected.hمعqstrdefs*.h، ثم يحوّل كل سطر منQ(Foo)إلى"Q(Foo)"كي يمرّ عبر المعالج المسبق دون تغيير. ثم يُستخدم المعالج المسبق للتعامل مع أي ترجمة شرطية فيqstrdefs*.h. ثم يُعكَس التحويل عائداً إلىQ(Foo)، ويُحفظ باسمqstrdefs.preprocessed.h.الملف
qstrdefs.generated.hهو ناتجmakeqstrdata.py. ولكلQ(Foo)في qstrdefs.preprocessed.h (بالإضافة إلى بعض القيم المُضمَّنة بشكل ثابت)، يُخرجQDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo").
ثم في عملية الترجمة الرئيسية، يحدث أمران مع qstrdefs.generated.h:
في qstr.h، يصبح كل QDEF مدخلاً في تعداد (enum)، مما يجعل
MP_QSTR_Fooمتاحاً للشيفرة ومساوياً لفهرس تلك السلسلة في جدول QSTR.في qstr.c، يُولَّد جدول بيانات QSTR الفعلي على هيئة عناصر في
mp_qstr_const_pool->qstrs.
توليد QSTR وقت التشغيل (run-time)¶
يمكن إنشاء مجمّعات QSTR إضافية وقت التشغيل بحيث يمكن إضافة سلاسل إليها. على سبيل المثال، الشيفرة:
foo[x] = 3
ستحتاج إلى إنشاء QSTR لقيمة x كي يمكن استخدامها بواسطة الشيفرة الثنائية "load attr".
كذلك، عند ترجمة شيفرة Python، تحتاج المعرّفات والقيم الحرفية (literals) إلى إنشاء سلاسل QSTR. ملاحظة: فقط القيم الحرفية الأقصر من 10 محارف تصبح سلاسل QSTR. وهذا لأن السلسلة العادية على الكومة (heap) تشغل دائماً 16 بايت كحد أدنى (كتلة GC واحدة)، بينما تتيح سلاسل QSTR حزمها بكفاءة أكبر داخل المجمّع.
تُخصَّص مجمّعات QSTR (و"القطع" (chunks) الأساسية التي تخزّن بيانات السلاسل) عند الطلب على الكومة بحدٍّ أدنى من الحجم.