الحوسبة العودية الآلية

1 المقدمة


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

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

البرمجة أكثر مرونة وتوفر ميزات أكثر من ممارسة تصميم الدوائر الرقمية. يجب أن نتأكد من ذلك ، مع مراعاة المزيد من تنفيذ البرامج للأتمتة المتداخلة ، ثم مفهوم الخوارزميات العودية الآلية.

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

2. الخوارزميات العودية الآلية


في المادة السابقة [3] ، تم تقديم تعريف رسمي فقط لنموذج التحكم في البرامج التلقائية (AP) دون النظر في تنفيذه وأمثلة محددة للاستخدام. كما ذُكرت إمكانية تنفيذ الخوارزميات العودية. بعد ذلك ، باستخدام مثال الحساب العاملي ، أولاً ، سننظر في تنفيذ البرنامج لآليات إنشاء إجراءات تلقائية في إطار نموذج الكائن الآلي C ++ ، وثانيًا ، نظرًا لأننا سننظر في خوارزمية متكررة ، سنحدد أساسًا مبدأ عامًا لتنفيذ أي من هذه الخوارزميات في إطار البرمجة التلقائية.

يمكن أن يكون استخدام الوظائف العودية في واجهة برمجة التطبيقات بسيطًا للغاية. يوضح هذا رمز سرد 1 من برنامج آلة الكائن. هنا فئة FSimpleFactorial automaton ليست عملية ، بل روتين آلي ، لأن يحتوي على الحالة النهائية "00" (لمزيد من التفاصيل حول البرامج الفرعية انظر [3]). على مستوى السلوك ، يتوافق الكائن الآلي الذي تم إنشاؤه مع الأوتوماتي مع انتقال واحد غير مشروط من الحالة الأولية "f0" إلى الحالة النهائية "00" ودعوة لهذا الانتقال في إطار الإجراء y1 لوظيفة العامل العاملي المعتادة (عاملي (...)).

القائمة 1. استدعاء دالة العوامل من AMS
#include "lfsaappl.h"

class FSimpleFactorial : public LFsaAppl
{
public:
    double dF{1};		//	 
    FSimpleFactorial(int n);
    virtual ~FSimpleFactorial() {};
private:
    int nN{0};
    void y1();
    double factorial(int n);
};

#include "stdafx.h"
#include "FSimpleFactorial.h"

LArc TT_SimpleFactorial[] = {
    LArc("f0", "00", "--",	"y1"),		//
    LArc()
};
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
FSimpleFactorial::FSimpleFactorial(int n)
    :LFsaAppl(TT_SimpleFactorial, "FSimpleFactorial")
{
    nN = n;
}
//
void FSimpleFactorial::y1() { dF = factorial(nN); }

double FSimpleFactorial::factorial(int n)
{
    if (n == 0) return 1;
    else return n*factorial(n-1);
}


نظرًا لأن كائن FSimpleFactorial عبارة عن آلية متداخلة (روتين فرعي) ، يجب أن يكون هناك "غلاف" له - العملية التي يطلق عليها. مثاله هو عملية ولدت من كائن يسمى FTskSimple ، يظهر رمزه في القائمة 2.

سرد 2. عملية إنشاء إنسان متداخلة
#include "lfsaappl.h"

class FTskSimple : public LFsaAppl
{
public:
    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FTskSimple(nameFsa); }
    bool FCreationOfLinksForVariables();
    FTskSimple(string strNam);
    virtual ~FTskSimple();

    CVar    *pVarF;
    CVar    *pVarN;
    CVar    *pVarStart;

    LFsaAppl *pFCall{nullptr};
protected:
    int x1();
    void y1(); void y2(); void y3();
};

#include "stdafx.h"
#include "FTskSimple.h"
#include "FSimpleFactorial.h"
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
LArc TT_Task[] = {
    LArc("st", "t1", "--",	"y1"),	//
    LArc("t1", "t2", "x1",	"y3"),	//
    LArc("t2", "t1", "--",	"y2"),	//
    };

FTskSimple::FTskSimple(string strNam)
    :LFsaAppl(TT_Task, strNam)
{
}

FTskSimple::~FTskSimple() { if (pFCall) delete pFCall; }

bool FTskSimple::FCreationOfLinksForVariables() {
    pVarF = CreateLocVar("dF", CLocVar::vtDouble, "factorial value");
    pVarN = CreateLocVar("nN", CLocVar::vtInteger, "input value");
    pVarStart = CreateLocVar("bStart", CLocVar::vtBool, "start?");
    return true;
}

int FTskSimple::x1() { return pVarStart->GetDataSrc(); }

void FTskSimple::y1()
{
    // reset flag start
    pVarStart->SetDataSrc(this, 0.0);
    // creating and calling a subroutine
    pFCall = new FSimpleFactorial(pVarN->GetDataSrc());
}
void FTskSimple::y2()
{
    // display factorial value
    pVarF->SetDataSrc(this, static_cast<FSimpleFactorial*>(pFCall)->dF);
}

void FTskSimple::y3()
{
    // reset flag start
    pVarStart->SetDataSrc(this, 0.0);
    static_cast<FSimpleFactorial*>(pFCall)->nN = pVarN->GetDataSrc();
    // creating and calling a subroutine
    pFCall->FCall(this);
}


تؤدي هذه العملية عند الانتقال من الحالة الأولية "st" إلى الحالة "t1" إلى إنشاء كائن من نوع FSimpleFactorial ، والذي سيكون أساسًا لإنشاء جهاز آلي متداخلة (بالمعنى المعتاد ، استدعاء برنامج فرعي). علاوة على ذلك ، استنادًا إلى الحالة الحالية للمتغير المحلي من النوع البولي b bart (انظر أيضًا الرمز الأصلي x1) ، فإنه يتسبب في الانتقال من الحالة "t1" إلى "t2". في هذا الانتقال ، يقوم الإجراء y1 ، أولاً ، بإعادة تعيين قيمة هذا المتغير المحلي (لمنع إعادة التشغيل الخاطئ) ، وثانيًا ، استدعاء إجراء FCall ، الذي ينشئ آلية متداخلة.

ينظم مترجم البيانات التلقائية لبيئة VKPA الانتقال إلى الحالة الأولية "f0" للجهاز المضمن وتصبح حالات هذا الأخير هي الحالات الحالية للعملية. يؤدي انتقال الجهاز الآلي المتداخل إلى الحالة النهائية "00" إلى إرجاع الحساب إلى المستوى السابق للتداخل ، وفي حالتنا ، إلى إكمال انتقال المستوى الأعلى التلقائي إلى الحالة t2. ثم تقوم العملية ، على انتقال غير مشروط من الحالة "t2" إلى الحالة "t1" ، بإجراء الإجراء y2 ، الذي يحدد نتيجة حساب معامل المتغير المحلي للعملية dF.

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

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

في حالتنا ، يكون حساب العامل صغيرًا جدًا ويصل إلى الحد الأقصى لقيمة النتيجة المزدوجة لـ n = 170 يناسب 1 ميكروثانية. لحساب القيم الكبيرة ، يحتاج المرء إلى التبديل إلى الحساب الطويل (انظر ، على سبيل المثال ، [4 ، 5]). في الوقت نفسه ، سيزداد وقت حساب العوامل بشكل أكبر وسيكاد يكون مضمونًا أن يتجاوز دورة الساعة المنفصلة ، مما يؤثر على سرعة أجهزة الشبكة المتبقية التي تعمل في وضع تعدد المهام غير الاستباقي وتفاعلية استجابة التطبيق ككل ، والتي ستظهر نفسها في "فراملها".

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

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

قائمة 3. وظائف البرمجيات لحساب عاملي
int factorial(int n)
{
    if (n == 0) return 1;
    else return n*factorial(n-1);
}

double Factorial(int n)
{
    double dF = 1;
    if (n == 0)
        return dF;
    else {
        double dPrev = Factorial(n-1);
        double dF = n*dPrev;
        return dF;
    }
}


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

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

في التين. يوضح الشكل 1 مخططًا تخطيطيًا يتضمن ترميزًا يعكس حالات آلة الحالة المحدودة المكافئة وأسماء المسندات والإجراءات الخاصة بها. يظهر نموذج الأوتوماتي نفسه في شكل رسم بياني للأوتوماتون في الشكل. 2.

صورة
الشكل. 1. مخطط تخطيطي للخوارزمية العودية عاملي

صورة
الشكل. 2. نموذج آلي لخوارزمية عاملة متكررة

يظهر رمز الروتين التلقائي لحساب عاملي في القائمة 4. وتعكس التعليقات في جدول الانتقال (TP) وقت حساب العامل في مللي ثانية وعدد الخطوات المنفصلة التي تم إنفاقها على إجراء الحساب لـ n = 170. وإذا كان وقت الحساب يعتمد على سرعة النظام الأساسي للحوسبة و / أو على نوع المشروع (تصحيح / تحرير) ، فإن عدد الخطوات المنفصلة (دورات الساعة) يتم تحديدها فقط من خلال خصائص الخوارزمية ويمكن أن تكون بمثابة تقييم موضوعي لسرعة الخوارزمية ، بغض النظر عن شكل عرضها وتنفيذها.

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

القائمة 4. روتين العامل الآلي
#include "lfsaappl.h"

class FFactRec : public LFsaAppl
{
public:
    FFactRec(int n);
    virtual ~FFactRec() { if (pFFactRec) delete pFFactRec; };
    int nN;		//	n
    double dF;	//	n!
private:
	int x1();
    void y1(); void y2(); void y3();
protected:
    void CallFactorial();
    double  PrevFactorial();
    FFactRec *pFFactRec{nullptr};
};

#include "stdafx.h"
#include "FFactRec.h"

#define QT_NO_DEBUG_OUTPUT

extern LArc TT_FactRec[];
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
//	   
FFactRec::FFactRec(int n)
    :LFsaAppl(TT_FactRec, "FFactRec")
{
    nN=n; dF=1;
}

LArc TT_FactRec[] = {
//        Debug    Release     Steps
//msec: 120-129   98-101        340
    LArc("f1", "00", "x1",	"y3"),	//
    LArc("f1", "f2", "^x1",	"y1"),	//
    LArc("f2", "00", "--",	"y2"),	//
    LArc()
};

int FFactRec::x1() { return nN==0; }
//   
void FFactRec::y1() { CallFactorial(); }
//	1.      
//	2.    
void FFactRec::y2() { dF = PrevFactorial()*nN; }
// 0!
void FFactRec::y3() { dF = 1; }
//   (  )
void FFactRec::CallFactorial()	{
    //	     
    pFFactRec = new FFactRec(nN-1);
    pFFactRec->FCall(this);
};
//    
double  FFactRec::PrevFactorial()	{ return pFFactRec->dF; };


3. الاستنتاجات


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

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

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

مكافحة العودية لا طائل من ورائه. يجب أن تكون قادرة على استخدام. سأقتبس ما أتفق معه تمامًا: "للوهلة الأولى ، قد يبدو العودية معقدًا. ولكن في بعض الحالات ، تكون الطريقة العودية فعالة بشكل لا يصدق إذا تم كل شيء بشكل صحيح. ومع ذلك ، من الأفضل في بعض الأحيان استخدام الحلقات. سيساعدك فهم كلتا الطريقتين والقدرة على استخدامها بشكل فعال في عملك وسيكون ميزة في المقابلة "[7]. لا يسعني إلا أن أضيف بنفسي أن النماذج التلقائية للخوارزميات العودية تجعل من الممكن ، باستخدام "الخصائص التلقائية" للنموذج الحسابي ، فهم العودية وتصحيح هذه الخوارزمية وتحسينها بشكل أسرع.

وآخر واحد. ومع ذلك ، أود أن أحصل على إجابة للسؤال - كيف تسير الأمور مع عودة كوروتين؟ لقد طلبت ذلك بالفعل ، لكنني لم أتلق إجابة ... بعد كل شيء ، إن إنشاء مليون كوروتين (انظر [8] للحصول على أمثلة) شيء واحد لتطبيق خوارزمية متكررة ذات مستوى تداخل ليس متشابهًا ولكنه مرتفع بما يكفي. ويبدو أن الإجابة على هذا السؤال لا تهمني وحدي فقط ... [9]

المؤلفات
1. .., .. . I, . ., 1981, 2, 135-144. [ ], : mi.mathnet.ru/at5725 . . . ( 10.03.2020).
2. .., .. . II, . ., 1981, 3, 112-121. [ ], : mi.mathnet.ru/at5743 . . . ( 10.03.2020).
3. . [ ], : habr.com/ru/post/484588 . . . ( 10.03.2020).
4. . [ ], : habr.com/ru/post/255761 . . . ( 10.03.2020).
5. C++. [ ], : habr.com/ru/post/172285 . . . ( 10.03.2020).
6. . . : . . – .: , 1983. – 256 .
7. , ? Python. Ethan Jarrell. Recursion vs. Looping in Python [ ], : nuancesprog.ru/p/3325 . . . ( 15.03.2020).
8. Your first coroutine with Kotlin. [ ], : kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html . . . ( 18.03.2020).
9. . CyberForum.ru. [ ], : www.cyberforum.ru/unity/thread2479923.html . . . ( 18.03.2020).

All Articles