الكود متعدد الخيوط

في علوم الحاسوب ، يُعدّ الكود المترابط أسلوبًا برمجيًا يتخذ فيه الكود شكلًا يتألف أساسًا من استدعاءات لبرامج فرعية . ويُستخدم هذا الأسلوب غالبًا في المترجمات ، التي قد تُولّد الكود بهذا الشكل أو تُنفّذه بنفسها. ويمكن معالجة الكود بواسطة مُفسّر ، أو قد يكون ببساطة سلسلة من تعليمات استدعاء لغة الآلة .

يتميز الكود متعدد الخيوط بكثافة أفضل من الكود المُولّد بتقنيات توليد بديلة أو باتفاقيات استدعاء بديلة . في البنى المُخزّنة مؤقتًا ، قد يكون تنفيذه أبطأ قليلًا. مع ذلك، قد يعمل برنامج صغير بما يكفي ليناسب ذاكرة التخزين المؤقت لمعالج الحاسوب بشكل أسرع من برنامج أكبر حجمًا يُعاني من أخطاء متكررة في ذاكرة التخزين المؤقت . [ 1 ] كما قد تكون البرامج الصغيرة أسرع في تبديل الخيوط ، عندما تمتلئ ذاكرة التخزين المؤقت ببرامج أخرى.

تشتهر التعليمات البرمجية متعددة الخيوط باستخدامها في العديد من مترجمات لغات البرمجة ، مثل Forth ، والعديد من تطبيقات BASIC ، وبعض تطبيقات COBOL ، والإصدارات المبكرة من B ، [ 2 ] ولغات أخرى لأجهزة الكمبيوتر الصغيرة ولأقمار الراديو للهواة .

تاريخ

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

كانت الحواسيب الأولى ذات ذاكرة محدودة نسبيًا. فعلى سبيل المثال، لم تتجاوز سعة ذاكرة الوصول العشوائي (RAM) في معظم أجهزة Data General Nova و IBM 1130 والعديد من الحواسيب الصغيرة  الأولى 4 كيلوبايت. ونتيجة لذلك، استُهلك الكثير من الوقت في محاولة إيجاد طرق لتقليل حجم البرنامج ليتناسب مع الذاكرة المتاحة.

أحد الحلول هو استخدام مترجم فوري يقرأ اللغة الرمزية بتًا بتًا، ويستدعي الدوال لتنفيذ العمليات. ولأن شفرة المصدر عادةً ما تكون أكثر كثافة من شفرة الآلة الناتجة، فإن هذا يقلل من استخدام الذاكرة الإجمالي. لهذا السبب، تُعدّ لغة مايكروسوفت بيسك مترجمًا فوريًا: [ أ ] كان على شفرتها مشاركة  ذاكرة 4 كيلوبايت لأجهزة مثل ألتاير 8800 مع شفرة المصدر الخاصة بالمستخدم. يُترجم المُصرّف من لغة المصدر إلى شفرة الآلة، لذا يجب أن يكون المُصرّف وشفرة المصدر والمخرجات جميعها في الذاكرة في الوقت نفسه. أما في المترجم الفوري، فلا يوجد مخرج.

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

كانت الحواسيب المركزية وبعض المعالجات الدقيقة المبكرة، مثل RCA 1802، تتطلب عدة تعليمات لاستدعاء روتين فرعي. في التطبيق الرئيسي وفي العديد من الروتينات الفرعية، يتكرر هذا التسلسل باستمرار، مع تغيير عنوان الروتين الفرعي فقط من استدعاء لآخر. هذا يعني أن البرنامج الذي يتكون من العديد من استدعاءات الدوال قد يحتوي على كميات كبيرة من التعليمات البرمجية المتكررة.

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

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

توفر أنظمة التعليمات البرمجية متعددة الخيوط مساحةً عن طريق استبدال قائمة استدعاءات الدوال، حيث يتغير عنوان الروتين الفرعي فقط من استدعاء لآخر، بقائمة من رموز التنفيذ، وهي في الأساس استدعاءات دوال تم حذف رمز (أو رموز) العملية الخاصة بها، ليتبقى فقط قائمة من العناوين. [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ]

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

تطوير

لتوفير المساحة، قام المبرمجون بضغط قوائم استدعاءات الإجراءات الفرعية في قوائم بسيطة لعناوين هذه الإجراءات، واستخدموا حلقة صغيرة لاستدعاء كل إجراء فرعي بدوره. على سبيل المثال، يستخدم الكود الزائف التالي هذه التقنية لجمع عددين A وB. في هذا المثال، تُسمى القائمة thread ، ويتتبع المتغير ip (مؤشر التعليمات) موقعنا داخل القائمة. يحتوي متغير آخر sp (مؤشر المكدس) على عنوان في مكان آخر من الذاكرة متاح لتخزين قيمة مؤقتًا.

start : ip = & thread // يشير إلى العنوان '&pushA'، وليس إلى التسمية النصية 'thread' top : jump * ip ++ // اتبع ip إلى العنوان في thread، اتبع هذا العنوان إلى الروتين الفرعي، تقدم ip thread : & pushA & pushB & add ... pushA : * sp ++ = A // اتبع sp إلى الذاكرة المتاحة، خزّن A هناك، تقدم sp إلى أعلى القفزة pushB : * sp ++ = B jump top add : addend1 = *-- sp // اسحب القيمة العلوية من المكدس addend2 = *-- sp // اسحب القيمة الثانية من المكدس * sp ++ = addend1 + addend2 // اجمع القيمتين معًا وخزّن النتيجة في أعلى المكدس jump top

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

start : ip = & thread // يشير ip إلى &pushA (الذي يشير إلى أول تعليمة في pushA) jump * ip ++ // إرسال التحكم إلى أول تعليمة في pushA وتقديم ip إلى &pushB thread : & pushA & pushB & add ... pushA : * sp ++ = A // تتبع sp إلى الذاكرة المتاحة، وتخزين A هناك، وتقديم sp إلى الخطوة التالية jump * ip ++ // إرسال التحكم إلى حيث يشير ip (أي إلى pushB) وتقديم ip pushB : * sp ++ = B jump * ip ++ add : addend1 = *-- sp // إزالة القيمة العلوية من المكدس addend2 = *-- sp // إزالة القيمة الثانية من المكدس * sp ++ = addend1 + addend2 // جمع القيمتين معًا وتخزين النتيجة في أعلى المكدس jump * ip ++

يُطلق على هذا اسم الكود المترابط المباشر (DTC). على الرغم من أن هذه التقنية أقدم، إلا أن أول استخدام واسع الانتشار لمصطلح "الكود المترابط" ربما كان في مقالة جيمس ر. بيل عام 1973 بعنوان "الكود المترابط". [ 8 ]

في عام 1970، ابتكر تشارلز إتش. مور ترتيبًا أكثر إحكامًا، وهو رمز الخيوط غير المباشر (ITC)، لآلته الافتراضية فورث. وقد توصل مور إلى هذا الترتيب لأن حواسيب نوفا الصغيرة كانت تحتوي على بت توجيه غير مباشر في كل عنوان، مما جعل استخدام ITC سهلًا وسريعًا. وفي وقت لاحق، ذكر أنه وجد هذا الترتيب ملائمًا للغاية لدرجة أنه عممه على جميع تصميمات فورث اللاحقة. [ 9 ]

اليوم، تقوم بعض مُجمّعات لغة فورث بتوليد شيفرة ذات خيوط مباشرة، بينما تقوم أخرى بتوليد شيفرة ذات خيوط غير مباشرة. وتعمل الملفات التنفيذية بنفس الطريقة في كلتا الحالتين.

نماذج الخيوط

تستخدم جميع التعليمات البرمجية القابلة للتنفيذ والمتعددة الخيوط عمليًا إحدى هذه الطرق لاستدعاء الإجراءات الفرعية (تسمى كل طريقة "نموذج الخيوط").

الخيط المباشر

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

قد يقوم مثال على آلة المكدس بتنفيذ التسلسل "دفع A، دفع B، إضافة". يمكن ترجمة ذلك إلى الخيط والروتينات التالية، حيث ipيتم تهيئة المتغير إلى العنوان المسمى thread(أي العنوان الذي &pushAيتم تخزين المتغير فيه).

#define PUSH(x) (*sp++ = (x)) #define POP() (*--sp) start : ip = & thread // يشير ip إلى &pushA (الذي يشير إلى أول تعليمة في pushA) jump * ip ++ // إرسال التحكم إلى أول تعليمة في pushA وتقديم ip إلى &pushB thread : & pushA & pushB & add ... pushA : PUSH ( A ) jump * ip ++ // إرسال التحكم إلى حيث يشير ip (أي إلى pushB) وتقديم ip pushB : PUSH ( B ) jump * ip ++ add : result = POP () + POP () PUSH ( result ) jump * ip ++

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

#define PUSH(x) (*sp++ = (x)) #define POP() (*--sp) start : ip = & thread jump * ip ++ thread : & push & A // عنوان تخزين A، وليس القيمة الحرفية A & push & B & add ... push : variable_address = * ip ++ // يجب نقل ip إلى ما بعد عنوان المعامل، لأنه ليس عنوان روتين فرعي PUSH ( * variable_address ) // قراءة القيمة من المتغير ودفعها إلى المكدس jump * ip ++ add : result = POP () + POP () PUSH ( result ) jump * ip ++

الخيط غير المباشر

يستخدم الترابط غير المباشر مؤشرات إلى مواقع تشير بدورها إلى شفرة الآلة. قد يتبع المؤشر غير المباشر معاملات تُخزَّن في "كتلة" غير مباشرة بدلاً من تخزينها بشكل متكرر في الخيط. لذا، غالبًا ما تكون الشفرة غير المباشرة أكثر إيجازًا من الشفرة ذات الترابط المباشر. عادةً ما يؤدي الترابط غير المباشر إلى إبطاء الشفرة، ولكنها تظل أسرع من مترجمات الشفرة البايتية. عندما تتضمن معاملات المعالج قيمًا وأنواعًا، قد يكون توفير المساحة مقارنةً بالشفرة ذات الترابط المباشر كبيرًا. عادةً ما تُنتج أنظمة FORTH القديمة شفرة ذات ترابط غير مباشر.

على سبيل المثال، إذا كان الهدف هو تنفيذ "دفع A، دفع B، إضافة"، فيمكن استخدام ما يلي. هنا، ipيتم تهيئة المتغير إلى العنوان &thread، ويتم العثور على كل جزء من أجزاء التعليمات البرمجية ( push, add) من خلال التوجيه غير المباشر المزدوج عبر ipكتلة غير مباشرة؛ ويتم العثور على أي معاملات للجزء في الكتلة غير المباشرة التي تلي عنوان الجزء. يتطلب هذا الاحتفاظ بالروتين الفرعي الحالي في ip، على عكس جميع الأمثلة السابقة حيث كان يحتوي على الروتين الفرعي التالي المراد استدعاؤه.

start : ip = & thread // يشير إلى '&i_pushA' jump ** ip // اتبع المؤشرات إلى أول تعليمة من 'push'، لا تُحرك ip بعد thread : & i_pushA & i_pushB & i_add ... i_pushA : & push & A i_pushB : & push & B i_add : & add push : * sp ++ = ** ( * ip + 1 ) // انظر 1 بعد بداية الكتلة غير المباشرة لعنوان المعامل jump * ( *++ ip ) // حرك ip في thread، اقفز عبر الكتلة غير المباشرة التالية إلى الروتين الفرعي التالي add : addend1 = *-- sp addend2 = *-- sp * sp ++ = addend1 + addend2 jump * ( *++ ip )

ترابط الروتين الفرعي

يتألف ما يُسمى بـ"البرمجة الفرعية المُترابطة" (أو "البرمجة الاستدعائية المُترابطة") من سلسلة من تعليمات "الاستدعاء" في لغة الآلة (أو عناوين الدوال المراد "استدعاؤها"، على عكس استخدام "القفز" في الترابط المباشر). غالبًا ما كانت المترجمات المبكرة للغات ALGOL وFortran وCobol وبعض أنظمة Forth تُنتج برمجة فرعية مُترابطة. كانت البرمجة في العديد من هذه الأنظمة تعمل على مكدس معاملات يُرتب حسب آخر المدخلات أول المخرجات (LIFO)، والذي كانت نظرية المترجمات مُطورة بشكل جيد له. تحتوي معظم المعالجات الحديثة على دعم خاص للأجهزة لتعليمات "الاستدعاء" و"الإرجاع" الفرعية، مما يُقلل من عبء تعليمة آلة إضافية لكل عملية إرسال.

صرح أنطون إرتل، أحد مؤسسي مُترجم Gforth ، بأنه "على عكس الاعتقاد الشائع، فإنّ معالجة البرامج الفرعية عادةً ما تكون أبطأ من معالجة البرامج المباشرة". [ 10 ] ومع ذلك، تُظهر أحدث اختبارات إرتل [ 1 ] أن معالجة البرامج الفرعية أسرع من معالجة البرامج المباشرة في 15 حالة من أصل 25 حالة اختبار. وبشكل أكثر تحديدًا، وجد أن معالجة البرامج المباشرة هي أسرع نموذج معالجة على معالجات Xeon وOpteron وAthlon، وأن معالجة البرامج غير المباشرة هي الأسرع على معالجات Pentium M، وأن معالجة البرامج الفرعية هي الأسرع على معالجات Pentium 4 وPentium III وPPC.

كمثال على ترابط المكالمات لـ "دفع أ، دفع ب، إضافة":

thread : call pushA call pushB call add ret pushA : * sp ++ = A ret pushB : * sp ++ = B ret add : addend1 = *-- sp addend2 = *-- sp * sp ++ = addend1 + addend2 ret

ترابط الرموز المميزة

يُنفّذ الكود المُعتمد على الرموز الخيطَ كقائمة من المؤشرات ضمن جدول عمليات؛ ويُختار عرض المؤشر عادةً ليكون أصغر ما يُمكن لتحقيق الكثافة والكفاءة. يُعدّ بايت واحد/8 بتات الخيار الأمثل لسهولة البرمجة، ولكن يُمكن استخدام أحجام أصغر مثل 4 بتات، أو أكبر مثل 12 أو 16 بتًا، وذلك حسب عدد العمليات المدعومة. طالما أن عرض المؤشر مُختار ليكون أضيق من مؤشر الآلة، فسيكون أكثر إحكامًا من أنواع الخيوط الأخرى دون بذل جهد كبير من المبرمج. عادةً ما يكون حجمه من نصف إلى ثلاثة أرباع حجم الخيوط الأخرى، والتي بدورها تتراوح بين ربع إلى ثمن حجم الكود غير المُعتمد على الخيوط. يُمكن أن تكون مؤشرات الجدول غير مباشرة أو مباشرة. تُنتج بعض مُترجمات لغة فورث كودًا مُعتمدًا على الرموز. يعتبر بعض المبرمجين " p-code " الذي يتم إنشاؤه بواسطة بعض مترجمات Pascal ، بالإضافة إلى رموز البايت المستخدمة بواسطة .NET و Java و BASIC وبعض مترجمات C ، بمثابة ترابط الرموز المميزة.

يُعدّ استخدام البايت كود نهجًا شائعًا تاريخيًا، حيث يعتمد عادةً على رموز عملياتية (opcodes) ذات 8 بتات مع آلة افتراضية قائمة على المكدس. يُعرف مُفسّر البايت كود النموذجي باسم "مُفسّر فك التشفير والإرسال"، ويتبع الشكل التالي:

بدء : vpc = & thread إرسال : addr = فك تشفير ( & vpc ) // تحويل عملية البايت كود التالية إلى مؤشر إلى كود الآلة الذي ينفذها // يتم تنفيذ أي عمليات بين التعليمات هنا (مثل تحديث الحالة العامة، ومعالجة الأحداث، وما إلى ذلك) قفزة addr CODE_PTR فك تشفير ( BYTE_CODE ** p ) { // في ترميز أكثر تعقيدًا، قد يكون هناك جداول متعددة للاختيار من بينها أو علامات تحكم/وضع إرجاع جدول [ * ( * p ) ++ ]; } سلسلة العمليات : /* يحتوي على بايت كود، وليس عناوين الآلة. وبالتالي فهو أكثر إيجازًا. */ 1 /*pushA*/ 2 /*pushB*/ 0 /*add*/ table : & add /* table[0] = عنوان رمز الآلة الذي يُنفذ رمز البايت 0 */ & pushA /* table[1] ... */ & pushB /* table[2] ... */ pushA : * sp ++ = A jump dispatch pushB : * sp ++ = B jump dispatch add : addend1 = *-- sp addend2 = *-- sp * sp ++ = addend1 + addend2 jump dispatch

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

بالنسبة للتعليمات التي تتضمن عمليات فردية بسيطة، مثل "الدفع" و"الجمع"، يكون العبء الإضافي المصاحب لتحديد ما يجب تنفيذه أكبر من تكلفة التنفيذ الفعلي، لذا غالبًا ما تكون هذه المترجمات أبطأ بكثير من لغة الآلة. مع ذلك، بالنسبة للتعليمات الأكثر تعقيدًا ("المركبة")، تكون نسبة العبء الإضافي أقل أهمية نسبيًا.

في بعض الأحيان، قد يعمل الكود المُوَسَّط بالرموز بشكل أسرع من الكود الآلي المكافئ له، وذلك عندما يكون حجم الكود الآلي كبيرًا جدًا بحيث لا يتسع في ذاكرة التخزين المؤقت للتعليمات من المستوى الأول (L1) لوحدة المعالجة المركزية. تسمح الكثافة العالية للكود المُوَسَّط، وخاصةً الكود المُوَسَّط بالرموز، بتخزينه بالكامل في ذاكرة التخزين المؤقت L1، مما يُجنِّب حدوث تضارب في الوصول إلى البيانات. مع ذلك، يستهلك الكود المُوَسَّط ذاكرة التخزين المؤقت للتعليمات (لتنفيذ كل عملية) وذاكرة التخزين المؤقت للبيانات (للكود الوسيط والجداول)، على عكس الكود الآلي الذي يستهلك ذاكرة التخزين المؤقت للتعليمات فقط؛ وهذا يعني أن الكود المُوَسَّط سيؤثر على كمية البيانات التي يمكن لوحدة المعالجة المركزية الاحتفاظ بها للمعالجة في أي وقت. على أي حال، إذا كانت المسألة قيد الحساب تتضمن تطبيق عدد كبير من العمليات على كمية صغيرة من البيانات، فقد يكون استخدام الكود المُوَسَّط هو الحل الأمثل. [ 4 ]

خيط هوفمان

يتكون رمز هوفمان المترابط من قوائم من الرموز المخزنة كرموز هوفمان . رمز هوفمان عبارة عن سلسلة بتات متغيرة الطول تُعرّف رمزًا فريدًا. يحدد مُفسّر هوفمان المترابط مواقع الإجراءات الفرعية باستخدام جدول فهرسة أو شجرة مؤشرات يمكن لرمز هوفمان التنقل فيها. يُعد رمز هوفمان المترابط أحد أكثر تمثيلات برامج الحاسوب إيجازًا. يتم اختيار الفهرس والرموز بقياس تكرار استدعاء كل إجراء فرعي في الكود. تُعطى الاستدعاءات المتكررة أقصر الرموز، بينما تُعطى العمليات ذات التكرارات المتقاربة رموزًا بأطوال بتات متقاربة. تم تنفيذ معظم أنظمة هوفمان المترابطة كأنظمة فورث ذات ترابط مباشر، واستُخدمت لتعبئة كميات كبيرة من التعليمات البرمجية البطيئة في وحدات تحكم دقيقة صغيرة ورخيصة . معظم الاستخدامات المنشورة [ 11 ] كانت في البطاقات الذكية والألعاب والآلات الحاسبة والساعات. يمكن اعتبار الكود الموجه نحو البتات والمستخدم في PBASIC بمثابة نوع من الكود المترابط لهوفمان.

خيوط أقل استخدامًا

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

RPL

لغة RPL من HP ، التي طُرحت لأول مرة في آلة حاسبة HP-18C عام 1986، هي نوع من لغات التفسير المترابطة الهجينة (ذات الخيوط المباشرة وغير المباشرة ) [ 12 ] ، والتي، على عكس لغات التفسير المترابطة الأخرى، تسمح بتضمين "كائنات" RPL [ b ] في "تدفق التشغيل"، أي سلسلة العناوين التي يتقدم من خلالها مؤشر المفسر. يمكن اعتبار "كائن" RPL نوعًا خاصًا من البيانات، يحتوي هيكله في الذاكرة على عنوان "مقدمة الكائن" في بداية الكائن، ثم تتبعه البيانات أو التعليمات البرمجية القابلة للتنفيذ. تحدد مقدمة الكائن كيفية تنفيذ أو معالجة جسم الكائن. باستخدام "الحلقة الداخلية لـ RPL" [ 13 ] ، التي اخترعها وحصل على براءة اختراعها [ 14 ] ويليام سي. ويكس عام 1986 ونُشرت عام 1988، يتم التنفيذ على النحو التالي: [ 15 ]

  1. قم بإلغاء مرجعية IP (مؤشر التعليمات) وتخزينه في O (مؤشر الكائن الحالي).
  2. قم بزيادة عنوان IP بمقدار طول مؤشر عنوان واحد
  3. قم بفك مرجع O وتخزين عنوانه في O_1 (هذا هو المستوى الثاني من التوجيه غير المباشر)
  4. انقل التحكم إلى المؤشر التالي أو الكائن المضمن عن طريق ضبط عداد البرنامج (PC) على O_1 زائد واحد مؤشر العنوان.
  5. ارجع إلى الخطوة 1

ويمكن تمثيل ذلك بدقة أكبر من خلال:

 O = [I] I = I + Δ PC = [O] + Δ 

في المثال أعلاه، O هو مؤشر الكائن الحالي، و I هو مؤشر المفسر، و Δ هو طول كلمة عنوان واحدة، و "[]" تعني "فك المرجع".

عند نقل التحكم إلى مؤشر كائن أو كائن مضمن، يستمر التنفيذ على النحو التالي:

مقدمة -> مقدمة (يشير عنوان المقدمة في بداية كود المقدمة إلى نفسه) إذا كان O + Δ ≠ PC ثم انتقل إلى التنفيذ غير المباشر (اختبار التنفيذ المباشر) O = I - Δ (صحح O لتشير إلى بداية الكائن المضمن) I = I + α (صحح I لتشير إلى ما بعد الكائن المضمن حيث α هو طول الكائن) غير مباشر (بقية المقدمة) 

في معالجات HP Saturn الدقيقة التي تستخدم RPL، يوجد مستوى ثالث من التوجيه غير المباشر أصبح ممكناً بفضل خدعة معمارية/برمجية تسمح بتنفيذ أسرع. [ 13 ]

الفروع

في جميع المفسرات، يُغيّر التفرع ببساطة مؤشر الخيط ( ip) إلى عنوان مختلف في الخيط. يمكن تنفيذ تفرع قفزة شرطية إذا كانت القيمة صفرًا، حيث يقفز فقط إذا كانت قيمة أعلى المكدس صفرًا، كما هو موضح أدناه. يستخدم هذا المثال إصدار المعاملات المضمنة من الترابط المباشر، لذا فإن السطر &thread[123]هو وجهة القفز إذا كان الشرط صحيحًا، وبالتالي يجب تخطيه ip++إذا لم يتم تنفيذ التفرع.

thread : ... & brz & thread [ 123 ] ... brz : when_true_ip = * ip ++ // الحصول على عنوان الوجهة للفرع if ( *-- sp == 0 ) // إزالة/استهلاك أعلى المكدس والتحقق مما إذا كان صفرًا ip = when_true_ip jump * ip ++

المرافق العامة

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

توجد عادةً ثلاثة سجلات في الآلة الافتراضية متعددة الخيوط. ويوجد سجل آخر لتمرير البيانات بين الإجراءات الفرعية (الكلمات). وهذه هي:

  • ip أو i ( مؤشر التعليمات ) الخاص بالآلة الافتراضية (لا ينبغي الخلط بينه وبين عداد البرنامج الخاص بالأجهزة الأساسية التي تنفذ الآلة الافتراضية)
  • w (مؤشر العمل)
  • rp أو r (إرجاع مؤشر المكدس )
  • sp أو s ( مؤشر مكدس المعلمات لتمرير المعلمات بين الكلمات)

غالباً ما تحتوي الآلات الافتراضية متعددة الخيوط ، مثل تطبيقات لغة فورث، على آلة افتراضية بسيطة في جوهرها، تتكون من ثلاثة عناصر أساسية . وهي:

  1. عش ، ويسمى أيضًا دوكول
  2. فك التشابك ، أو شبه_s (;s)
  3. التالي

في الآلة الافتراضية ذات الخيوط غير المباشرة، كما هو موضح هنا، تكون العمليات كالتالي:

التالي : * ip ++ -> w jump ** w ++ nest : ip -> * rp ++ w -> ip next unnest : *-- rp -> ip next

انظر أيضاً

ملحوظات

  1. كان برنامج Dartmouth BASIC ، الذي يعتمد عليه برنامج Microsoft BASIC في نهاية المطاف، عبارة عن مترجم يعمل على أجهزة الكمبيوتر المركزية.
  2. لا ينبغي الخلط بينها وبين الكائنات المرتبطة بالبرمجة الكائنية التوجه

مراجع

  1. 1 2 "سرعة تقنيات إرسال المترجمين المختلفة الإصدار 2" .
  2. دينيس م. ريتشي، "تطوير لغة سي" ، 1993. اقتباس: "لم يُولّد مُترجم لغة بي على جهاز PDP-7 تعليمات الآلة، بل قام بدلاً من ذلك بتوليد "رمز مُتشابك" ..."
  3. ديفيد فريش. "ملف قراءة muforth" . القسم "مترجم أصلي بسيط وذو استدعاءات ذيلية".
  4. 1 2 ستيف هيلر. "برمجة C/C++ الفعالة: أصغر حجمًا، وأسرع، وأفضل" . 2014. الفصل 5: "هل تحتاج إلى مترجم؟" ص 195.
  5. جان بول تريمبلاي؛ بي جي سورنسون. "نظرية وممارسة كتابة المترجمات" . 1985. ص 527
  6. "العالم اللاسلكي: الإلكترونيات، الراديو، التلفزيون، المجلد 89" . ص 73.
  7. "بايت، المجلد 5" . 1980. ص 212
  8. بيل، جيمس ر. (1973). "البرمجة المترابطة" . اتصالات رابطة آلات الحوسبة . 16 (6): 370-372 . doi : 10.1145/362248.362270 . S2CID 19042952 . 
  9. نشر تشارلز هـ. مور ملاحظات في العدد الرابع من مجلة بايت
  10. إرتل، أنطون. "ما هو الكود المترابط؟" .
  11. لاتيندريس، ماريو؛ فيلي، مارك. توليد مترجمات سريعة لرمز بايت مضغوط باستخدام هوفمان . إلسيفير. CiteSeerX 10.1.1.156.2546 . 
  12. لولينجر، آر جي (1981) [أغسطس 1979]. كُتب في دايتون، أوهايو، الولايات المتحدة الأمريكية. لغات تفسيرية مترابطة: تصميمها وتنفيذها (الطبعة الثانية، الطبعة الأولى). بيتربورو، نيو هامبشاير، المملكة المتحدة: كتب بايت ، منشورات بايت. رقم ISBN  0-07038360-XLCCN 80-19392 . ISBN  978-0-07038360-9تم الاطلاع عليه بتاريخ 2023-08-03 .(xiv+2+251 صفحة)
  13. 1 2 بوسبي، جوناثان (2018-09-07). "شرح الحلقة الداخلية لـ RPL" . متحف آلات حاسبة HP . مؤرشف من الأصل في 2023-08-03 . تم الاسترجاع في 2019-12-27 .
  14. ويكس، ويليام سي. (30 مايو 1986). "نظام وطريقة معالجة البيانات للتنفيذ المباشر وغير المباشر لأنواع الكائنات ذات البنية الموحدة" . uspto.gov . تم الاطلاع عليه بتاريخ 27 ديسمبر 2019 .
  15. ويكس، ويليام سي. (1988-10-01) [14-18 يونيو 1988]. فورسلي، لورانس ب. (محرر). RPL: ​​لغة تحكم رياضية . وقائع مؤتمر روتشستر فورث لعام 1988: بيئات البرمجة. المجلد 8. روتشستر، نيويورك، الولايات المتحدة الأمريكية: معهد أبحاث فورث التطبيقية، جامعة روتشستر . ISBN  978-0-91459308-9. OCLC 839704944 . (ملاحظة: يُشار إلى هذا العنوان غالبًا باسم "RPL: لغة تحكم رياضية". يتوفر مقتطف منه على الرابط التالي: RPLMan من ملف Goodies Disk 4 Zip File )

للمزيد من القراءة

  • يصف كتاب "تطوير لغة C" المؤرشف بتاريخ 2015-03-28 في Wayback Machine بواسطة دينيس إم. ريتشي لغة B (وهي سلف للغة C) بأنها مُنفذة باستخدام "التعليمات البرمجية المترابطة".
  • هورن، جوزيف ك. "ما هو RPL؟" . مؤرشف من الأصل بتاريخ 17-09-2017 . تم الاسترجاع بتاريخ 17-09-2017 .(ملاحظة: نظرة عامة موجزة على لغات البرمجة المترابطة، وRPL النظام وRPL المستخدم، المستخدمة في آلات حاسبة HP مثل HP 48. )