الغرض من الحزمة المستقبلية هو توفير طريقة بسيطة وموحدة للغاية لتقييم تعبيرات R بشكل غير متزامن باستخدام موارد مختلفة متاحة للمستخدم.
في البرمجة ، فإن المستقبل هو تجريد لقيمة قد تكون متاحة في مرحلة ما في المستقبل. يمكن أن تكون حالة المستقبل إما حلها أو حلها . بمجرد حلها ، تتوفر القيمة على الفور. إذا تم الاستعلام عن القيمة أثناء عدم حل المستقبل ، يتم حظر العملية الحالية حتى يتم حل المستقبل. من الممكن التحقق مما إذا كان المستقبل قد تم حله أم لا بدون حظر. بالضبط كيف ومتى يتم حل العقود المستقبلية يعتمد على الإستراتيجية المستخدمة لتقييمها. على سبيل المثال ، يمكن حل المستقبل باستخدام استراتيجية متسلسلة ، مما يعني أنه يتم حله في جلسة R الحالية. قد تكون الاستراتيجيات الأخرى هي حل العقود المستقبلية بشكل غير متزامن ، على سبيل المثال ، من خلال تقييم التعبيرات بالتوازي على الجهاز الحالي أو بشكل متزامن على كتلة حسابية.
فيما يلي مثال يوضح كيف تعمل أساسيات العقود الآجلة. أولاً ، ضع في اعتبارك مقتطف الرمز التالي الذي يستخدم رمز R العادي:
> v <- {
+ cat( " Hello world! n " )
+ 3.14
+ }
Hello world !
> v
[ 1 ] 3.14
إنه يعمل عن طريق تعيين قيمة التعبير إلى المتغير v
ثم نطبع قيمة v
علاوة على ذلك ، عندما يتم تقييم التعبير عن v
، نطبع أيضًا رسالة.
فيما يلي نفس قصاصة الكود المعدلة لاستخدام العقود المستقبلية بدلاً من ذلك:
> library( future )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
الفرق هو في كيفية بناء v
؛ مع Plain R ، نستخدم <-
بينما مع العقود المستقبلية نستخدم %<-%
. الفرق الآخر هو أنه يتم نقل الإخراج بعد حل المستقبل (وليس أثناء) وعندما يتم الاستعلام عن القيمة (انظر "نص الإخراج").
فلماذا العقود المستقبلية مفيدة؟ لأنه يمكننا اختيار تقييم التعبير المستقبلي في عملية R منفصلة بشكل غير متزامن من خلال تبديل الإعدادات على النحو التالي:
> library( future )
> plan( multisession )
> v % <- % {
+ cat( " Hello world! n " )
+ 3.14
+ }
> v
Hello world !
[ 1 ] 3.14
مع العقود المستقبلية غير المتزامنة ، لا يتم حظر عملية R الحالية/الرئيسية ، مما يعني أنها متاحة لمزيد من المعالجة أثناء حل العقود المستقبلية في عمليات منفصلة تعمل في الخلفية. بمعنى آخر ، توفر العقود المستقبلية بناءًا بسيطًا ولكنه قوي للمعالجة المتوازية و / أو الموزعة في R.
الآن ، إذا لم يكن من الممكن أن تزعجك قراءة جميع التفاصيل الدقيقة حول العقود المستقبلية ، ولكنك تريد فقط تجربتها ، ثم انتقل إلى النهاية للعب مع عرض Mandelbrot باستخدام كل من التقييم المتوازي وغير الموازي.
يمكن إنشاء العقود المستقبلية إما ضمنيًا أو صريحًا . في المثال التمهيدي أعلاه ، استخدمنا العقود الآجلة الضمنية التي تم إنشاؤها عبر بنية v %<-% { expr }
. البديل هو العقود المستقبلية الصريحة باستخدام بنيات f <- future({ expr })
و v <- value(f)
. مع هذه ، يمكن بدلاً من ذلك كتابة مثالنا على النحو التالي:
> library( future )
> f <- future({
+ cat( " Hello world! n " )
+ 3.14
+ })
> v <- value( f )
Hello world !
> v
[ 1 ] 3.14
إما نمط البناء المستقبلي يعمل على قدم المساواة (*) بشكل جيد. يشبه النمط الضمني الطريقة التي يتم بها كتابة رمز R العادي. من حيث المبدأ ، كل ما عليك فعله هو استبدال <-
بـ %<-%
لتحويل المهمة إلى مهمة مستقبلية. من ناحية أخرى ، يمكن أن تكون هذه البساطة خادعة أيضًا ، خاصةً عند استخدام العقود الآجلة غير المتزامنة. على النقيض من ذلك ، فإن النمط الصريح يجعل من الواضح أن العقود الآجلة يتم استخدامها ، مما يقلل من خطر الأخطاء ويقوم بتوصيل التصميم بشكل أفضل إلى الآخرين الذين يقرؤون الكود الخاص بك.
(*) هناك حالات لا يمكن استخدامها %<-%
بدون بعض التعديلات (الصغيرة). سنعود إلى هذا في القسم "قيود عند استخدام العقود المستقبلية الضمنية" بالقرب من نهاية هذا المستند.
لتلخيص ، من أجل المستقبل الصريح ، نستخدم:
f <- future({ expr })
- يخلق مستقبلًاv <- value(f)
- يحصل على قيمة المستقبل (الكتل إن لم يتم حلها بعد)من أجل العقود الآجلة الضمنية ، نستخدم:
v %<-% { expr }
- يخلق مستقبلًا ووعد بقيمة قيمتهللحفاظ على الأمر بسيطًا ، سنستخدم النمط الضمني في بقية هذا المستند ، ولكن كل شيء تمت مناقشته سيطبق أيضًا على مستقبل واضحة.
تنفذ الحزمة المستقبلية الأنواع التالية من العقود الآجلة:
اسم | أنفار | وصف |
---|---|---|
متزامن: | غير متوازي: | |
sequential | الجميع | بالتتابع وفي عملية r الحالية |
غير متزامن: | موازي : | |
multisession | الجميع | خلفية جلسات (على الجهاز الحالي) |
multicore | ليس Windows/ليس rstudio | العمليات R متشوق (على الجهاز الحالي) |
cluster | الجميع | جلسات R الخارجية على الآلات الحالية والمحلية و/أو البعيدة |
تم تصميم الحزمة المستقبلية بحيث يمكن تنفيذ دعم الاستراتيجيات الإضافية أيضًا. على سبيل المثال ، توفر حزمة Future.Callr خلفية مستقبلية تقوم بتقييم العقود الآجلة في عملية R الخلفية باستخدام حزمة Callr - فهي تعمل بشكل مشابه للعقود المستقبلية multisession
ولكن لديها بعض المزايا. باستمرار ، توفر حزمة Future.BatchTools العقود الآجلة لجميع أنواع وظائف الكتلة ("Backends") التي تدعمها حزمة BatchTools. على وجه التحديد ، تتوفر أيضًا العقود الآجلة لتقييم التعبيرات R عبر جدولة الوظائف مثل SLURM و Torque/PBS و Oracle/Sun Grid Engine (SGE) ومرفق مشاركة الحمل (LSF).
بشكل افتراضي ، يتم تقييم التعبيرات المستقبلية بفارغ الصبر (= على الفور) ومزامنة (في الجلسة R الحالية). يشار إلى استراتيجية التقييم هذه على أنها "متسلسلة". في هذا القسم ، سوف نمر بكل من هذه الاستراتيجيات ونناقش ما تشترك فيه وكيف تختلف.
قبل المرور بكل من الاستراتيجيات المستقبلية المختلفة ، ربما يكون من المفيد توضيح أهداف واجهة برمجة التطبيقات المستقبلية (كما هو محدد في الحزمة المستقبلية). عند البرمجة مع العقود المستقبلية ، لا ينبغي أن يهم حقًا ما هي الاستراتيجية المستقبلية المستخدمة لتنفيذ التعليمات البرمجية. هذا لأننا لا نستطيع أن نعرف حقًا الموارد الحسابية التي يمكن للمستخدم الوصول إليها ، وبالتالي يجب أن يكون اختيار استراتيجية التقييم في أيدي المستخدم وليس المطور. بمعنى آخر ، يجب ألا يضع الرمز أي افتراضات على نوع العقود المستقبلية المستخدمة ، مثل المتزامن أو غير متزامن.
كان أحد تصاميم واجهة برمجة التطبيقات المستقبلية هو تغليف أي اختلافات بحيث يبدو أن جميع أنواع العقود المستقبلية تعمل كما هي. هذا على الرغم من التعبيرات قد يتم تقييمها محليًا في جلسة R الحالية أو في جميع أنحاء العالم في جلسات R البعيدة. ميزة أخرى واضحة تتمثل في وجود واجهة برمجة تطبيقات ثابتة وسلوك بين أنواع مختلفة من العقود المستقبلية هي أنها تساعد أثناء النماذج الأولية. عادةً ما يستخدم المرء التقييم المتسلسل أثناء بناء نص ، وبعد ذلك ، عندما يتم تطوير البرنامج النصي بالكامل ، قد يقوم المرء بتشغيل المعالجة غير المتزامنة.
لهذا السبب ، فإن الإعدادات الافتراضية للاستراتيجيات المختلفة هي أن النتائج والآثار الجانبية لتقييم التعبير المستقبلي متشابهة قدر الإمكان. وبشكل أكثر تحديدًا ، فإن ما يلي صحيح لجميع العقود المستقبلية:
تتم جميع التقييم في بيئة محلية (أي local({ expr })
) بحيث لا تؤثر الواجبات على بيئة الاتصال. هذا أمر طبيعي عند التقييم في عملية R خارجية ، ولكن يتم تطبيقه أيضًا عند التقييم في جلسة R الحالية.
عندما يتم بناء مستقبل ، يتم تحديد المتغيرات العالمية . للتقييم غير المتزامن ، يتم تصدير Globals إلى عملية/جلسة R التي ستقوم بتقييم التعبير المستقبلي. بالنسبة للمستقبل المتسلسل مع التقييم البطيء ( lazy = TRUE
) ، يتم "تجميد" الكرات (مستنسخة لبيئة محلية في المستقبل). أيضًا ، من أجل الحماية من تصدير الأشياء الكبيرة جدًا عن طريق الخطأ ، هناك تأكيد مدمج على أن الحجم الإجمالي لجميع الكرات أقل من عتبة معينة (يمكن التحكم فيها عبر خيار ، راجع help("future.options")
). إذا تم تجاوز العتبة ، يتم طرح خطأ مفيد.
يتم تقييم التعبيرات المستقبلية فقط مرة واحدة . بمجرد جمع القيمة (أو خطأ) ، ستكون متاحة لجميع الطلبات اللاحقة.
فيما يلي مثال يوضح أن جميع المهام تتم لبيئة محلية:
> plan( sequential )
> a <- 1
> x % <- % {
+ a <- 2
+ 2 * a
+ }
> x
[ 1 ] 4
> a
[ 1 ] 1
نحن الآن على استعداد لاستكشاف الاستراتيجيات المستقبلية المختلفة.
يتم حل العقود المستقبلية المتزامنة واحدة تلو الأخرى ، والأكثر شيوعًا من خلال عملية R التي تخلقها. عندما يتم حل مستقبل متزامن ، فإنه يحظر العملية الرئيسية حتى يتم حلها.
العقود المستقبلية المتسلسلة هي الافتراضي ما لم ينص على خلاف ذلك. لقد تم تصميمها لتتصرف بأكبر قدر ممكن من التقييم R المنتظم مع الاستمرار في الوفاء واجهة برمجة تطبيقات المستقبل وسلوكياتها. فيما يلي مثال يوضح خصائصهم:
> plan( sequential )
> pid <- Sys.getpid()
> pid
[ 1 ] 1437557
> a % <- % {
+ pid <- Sys.getpid()
+ cat( " Future 'a' ... n " )
+ 3.14
+ }
> b % <- % {
+ rm( pid )
+ cat( " Future 'b' ... n " )
+ Sys.getpid()
+ }
> c % <- % {
+ cat( " Future 'c' ... n " )
+ 2 * a
+ }
Future ' a ' ...
> b
Future ' b ' ...
[ 1 ] 1437557
> c
Future ' c ' ...
[ 1 ] 6.28
> a
[ 1 ] 3.14
> pid
[ 1 ] 1437557
نظرًا لأن التقييم المتسلسل المتحمس يحدث ، يتم حل كل من العقود الآجلة الثلاثة على الفور في اللحظة التي يتم إنشاؤها. لاحظ أيضًا كيف أن pid
في بيئة الاتصال ، التي تم تعيين معرف العملية للعملية الحالية ، ليست مكتوبة أو إزالتها. وذلك لأن العقود المستقبلية يتم تقييمها في بيئة محلية. نظرًا لاستخدام المعالجة المتزامنة (UNI-) ، يتم حل المستقبل b
بواسطة عملية R الرئيسية (لا تزال في بيئة محلية) ، وهذا هو السبب في أن قيمة b
و pid
هي نفسها.
بعد ذلك ، سننتقل إلى العقود المستقبلية غير المتزامنة ، والتي يتم حلها في الخلفية. حسب التصميم ، فإن هذه العقود المستقبلية غير محظورة ، أي بعد إنشاء عملية الاتصال متاحة للمهام الأخرى بما في ذلك إنشاء مستقبل إضافي. فقط عندما تحاول عملية الاتصال الوصول إلى قيمة المستقبل الذي لم يتم حله بعد ، أو محاولة إنشاء مستقبل آخر غير متزامن عندما تكون جميع عمليات R المتاحة مشغولة بتقديم العقود المستقبلية الأخرى ، فإنها تحظرها.
نبدأ بالعقود المستقبلية المتعددة لأنها مدعومة من قبل جميع أنظمة التشغيل. يتم تقييم مستقبل متعدد المهن في جلسة R الخلفية التي تعمل على نفس الجهاز مثل عملية الاتصال R. فيما يلي مثالنا مع تقييم Multisession:
> plan( multisession )
> pid <- Sys.getpid()
> pid
[ 1 ] 1437557
> a % <- % {
+ pid <- Sys.getpid()
+ cat( " Future 'a' ... n " )
+ 3.14
+ }
> b % <- % {
+ rm( pid )
+ cat( " Future 'b' ... n " )
+ Sys.getpid()
+ }
> c % <- % {
+ cat( " Future 'c' ... n " )
+ 2 * a
+ }
Future ' a ' ...
> b
Future ' b ' ...
[ 1 ] 1437616
> c
Future ' c ' ...
[ 1 ] 6.28
> a
[ 1 ] 3.14
> pid
[ 1 ] 1437557
أول شيء نلاحظه هو أن قيم a
و c
و pid
هي نفسها سابقًا. ومع ذلك ، نلاحظ أن b
يختلف عن السابق. وذلك لأن المستقبل b
يتم تقييمه في عملية R مختلفة ، وبالتالي فإنه يعيد معرف عملية مختلف.
عند استخدام التقييم المتعدد ، تطلق الحزمة مجموعة من جلسات R في الخلفية التي ستخدم العقود المستقبلية متعددة المسالك من خلال تقييم تعبيراتها عند إنشائها. إذا كانت جميع جلسات الخلفية مشغولة بخدمة العقود الآجلة الأخرى ، يتم إنشاء مستقبل متعدد المقبل حتى تصبح جلسة الخلفية متاحة مرة أخرى. يتم تحديد العدد الإجمالي لعمليات الخلفية التي availableCores()
إطلاق
> availableCores()
mc.cores
2
تخبرنا هذه النتيجة المحددة أن خيار mc.cores
قد تم تعيينه بحيث يُسمح لنا باستخدام العمليات الإجمالية (2) بما في ذلك العملية الرئيسية. بمعنى آخر ، مع هذه الإعدادات ، ستكون هناك عمليات خلفية (2) التي تخدم العقود المستقبلية المتعددة. يعد availableCores()
أيضًا رشيقًا لخيارات مختلفة ومتغيرات بيئة النظام. على سبيل المثال ، إذا تم استخدام جدولة كتلة الحساب (مثل عزم الدوران/PBS و SLURM) ، فإنهم يضعون متغيرًا محددًا للبيئة يحدد عدد النوى التي تم تخصيصها لأي وظيفة معينة ؛ availableCores()
يعترف هذه كذلك. إذا لم يتم تحديد شيء آخر ، فسيتم استخدام جميع النوى المتاحة على الجهاز ، راجع parallel::detectCores()
. لمزيد من التفاصيل ، يرجى الاطلاع على help("availableCores", package = "parallelly")
.
على أنظمة التشغيل حيث تدعم R عملية التقدم في العمليات ، والتي هي في الأساس جميع نظام التشغيل باستثناء Windows ، فإن بديل لتفريخ جلسات R في الخلفية هو تغطية عملية R الحالية. لاستخدام العقود المستقبلية متعددة الأوساخ ، عند الدعم ، حدد:
plan( multicore )
تمامًا كما هو الحال بالنسبة للعقود المستقبلية المتعددة ، سيتم تحديد الحد الأقصى لعدد العمليات المتوازية بواسطة availableCores()
، لأنه في كلتا الحالتين يتم التقييم على الجهاز المحلي.
يمكن أن تكون عملية Forking A R هي أسرع من العمل مع جلسة R منفصلة تعمل في الخلفية. أحد الأسباب هو أن النفقات العامة لتصدير الكرات الكبيرة إلى جلسة الخلفية يمكن أن تكون أكبر من عندما يتم استخدام الذاكرة المشتركة ، وبالتالي الذاكرة المشتركة. من ناحية أخرى ، تتم قراءة الذاكرة المشتركة فقط ، مما يعني أي تعديلات على الكائنات المشتركة من خلال إحدى العمليات المتشددة ("العمال") ستؤدي إلى نسخة من نظام التشغيل. يمكن أن يحدث هذا أيضًا عندما يعمل جامع القمامة R في إحدى العمليات المتشعبة.
من ناحية أخرى ، تعتبر عملية Forking غير مستقرة في بعض بيئات R. على سبيل المثال ، عند تشغيل R من داخل عملية RSTUDIO ، قد يؤدي Forking إلى جلسات R تحطمت. ولهذا السبب ، فإن الحزمة المستقبلية تعطل العقود المستقبلية المتعددة بشكل افتراضي عند الركض من rstudio. انظر help("supportsMulticore")
لمزيد من التفاصيل.
تقوم العقود المستقبلية للكتلة بتقييم التعبيرات على كتلة مخصصة (كما تم تنفيذها بواسطة الحزمة الموازية). على سبيل المثال ، افترض أنه يمكنك الوصول إلى ثلاث عقد n1
و n2
و n3
، يمكنك بعد ذلك استخدامها للتقييم غير المتزامن على النحو التالي:
> plan( cluster , workers = c( " n1 " , " n2 " , " n3 " ))
> pid <- Sys.getpid()
> pid
[ 1 ] 1437557
> a % <- % {
+ pid <- Sys.getpid()
+ cat( " Future 'a' ... n " )
+ 3.14
+ }
> b % <- % {
+ rm( pid )
+ cat( " Future 'b' ... n " )
+ Sys.getpid()
+ }
> c % <- % {
+ cat( " Future 'c' ... n " )
+ 2 * a
+ }
Future ' a ' ...
> b
Future ' b ' ...
[ 1 ] 1437715
> c
Future ' c ' ...
[ 1 ] 6.28
> a
[ 1 ] 3.14
> pid
[ 1 ] 1437557
يمكن استخدام أي أنواع من المجموعات parallel::makeCluster()
على سبيل المثال ، يمكن إعداد المجموعة أعلاه بشكل صريح على النحو التالي:
cl <- parallel :: makeCluster(c( " n1 " , " n2 " , " n3 " ))
plan( cluster , workers = cl )
أيضًا ، يعتبر أسلوبًا جيدًا لإغلاق Cluster cl
عندما لم يعد هناك حاجة إليه ، أي استدعاء parallel::stopCluster(cl)
. ومع ذلك ، سيتم إغلاق نفسه إذا تم إنهاء العملية الرئيسية. لمزيد من المعلومات حول كيفية إعداد هذه المجموعات وإدارتها ، راجع help("makeCluster", package = "parallel")
. يتم أيضًا إغلاق المجموعات التي تم إنشاؤها ضمنيًا باستخدام plan(cluster, workers = hosts)
حيث يكون hosts
متجهًا أحرفًا عندما تنتهي جلسة R الرئيسية ، أو عندما يتم تغيير الاستراتيجية المستقبلية ، على سبيل المثال عن طريق plan(sequential)
.
لاحظ أنه مع إعداد المصادقة التلقائي (مثل أزواج مفتاح SSH) ، لا يوجد شيء يمنعنا من استخدام نفس النهج لاستخدام مجموعة من الآلات البعيدة.
إذا كنت ترغب في تشغيل العديد من العمال على كل عقدة ، فقم بتكرار اسم العقدة عدة مرات مثل عدد العمال الذين يجب تشغيلهم على تلك العقدة. على سبيل المثال،
> plan(cluster, workers = c(rep("n1", times = 3), "n2", rep("n3", times = 5)))
سوف يدير ثلاثة عمال على n1
، واحد على n2
، وخمس على n3
، في إجمالي تسعة عمال متوازيين.
لقد ناقشنا هذا بعيدًا ما يمكن الإشارة إليه باسم "طوبولوجيا مسطحة" للعقود المستقبلية ، أي أن جميع العقود المستقبلية يتم إنشاؤها وتعيينها في نفس البيئة. ومع ذلك ، لا يوجد شيء يمنعنا من استخدام "طوبولوجيا متداخلة" للعقود الآجلة ، حيث يجوز لمجموعة من العقود المستقبلية ، بدورها ، إنشاء مجموعة أخرى من العقود الآجلة داخليًا وما إلى ذلك.
على سبيل المثال ، فيما يلي مثال على العقود الآجلة "العلوية" ( a
و b
) التي تستخدم تقييم متعدد الالتزام وحيث يستخدم المستقبل الثاني ( b
) بدوره عقدين داخليين:
> plan( multisession )
> pid <- Sys.getpid()
> a % <- % {
+ cat( " Future 'a' ... n " )
+ Sys.getpid()
+ }
> b % <- % {
+ cat( " Future 'b' ... n " )
+ b1 % <- % {
+ cat( " Future 'b1' ... n " )
+ Sys.getpid()
+ }
+ b2 % <- % {
+ cat( " Future 'b2' ... n " )
+ Sys.getpid()
+ }
+ c( b.pid = Sys.getpid(), b1.pid = b1 , b2.pid = b2 )
+ }
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1437804
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1437805 1437805 1437805
عن طريق الفحص معرفات العملية ، نرى أن هناك في إجمالي ثلاث عمليات مختلفة متورطة لحل العقود الآجلة. هناك عملية R الرئيسية (PID 1437557) ، وهناك العمليتان المستخدمتان من قبل a
(PID 1437804) و b
(PID 1437805). ومع ذلك ، يتم تقييم العقود الآجلة ( b1
و b2
) التي يتم اختبارها بواسطة b
بواسطة نفس عملية R مثل b
وذلك لأن العقود الآجلة المتداخلة تستخدم التقييم المتسلسل ما لم ينص على خلاف ذلك. هناك بعض الأسباب لذلك ، ولكن السبب الرئيسي هو أنه يحمينا من تفريغ عدد كبير من عمليات الخلفية عن طريق الخطأ ، على سبيل المثال عبر المكالمات العودية.
لتحديد نوع مختلف من طوبولوجيا التقييم ، بخلاف المستوى الأول من العقود المستقبلية التي يتم حلها عن طريق التقييم المتعدد والمستوى الثاني عن طريق التقييم المتسلسل ، يمكننا تقديم قائمة باستراتيجيات التقييم plan()
. أولاً ، يمكن تحديد استراتيجيات التقييم نفسها على النحو الوارد أعلاه بشكل صريح على النحو التالي:
plan( list ( multisession , sequential ))
سنحصل في الواقع على نفس السلوك إذا حاولنا بمستويات متعددة من التقييمات المتعددة ؛
> plan( list ( multisession , multisession ))
[ ... ]
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1437901
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1437902 1437902 1437902
والسبب في ذلك هو ، هنا أيضًا ، حمايتنا من إطلاق عمليات أكثر مما يمكن أن تدعمه الماكينة. داخليًا ، يتم ذلك عن طريق تعيين mc.cores = 1
بحيث تعمل مثل parallel::mclapply()
لتشغيلها بالتتابع. هذا هو الحال بالنسبة لكل من التقييم متعدد الوسيح.
مستمر ، إذا بدأنا بالتقييم المتسلسل ثم استخدموا تقييم متعدد الأجزاء لأي مستقبل متداخل ، نحصل على:
> plan( list ( sequential , multisession ))
[ ... ]
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1437557
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1437557 1438017 1438016
والتي تظهر بوضوح أنه يتم حل a
و b
في عملية الاتصال (PID 1437557) في حين يتم حل العقود الآجلة المتداخلة ( b1
و b2
) في عمليتين منفصلتين (PIDS 1438017 و 1438016).
بعد قولي هذا ، من الممكن بالفعل استخدام استراتيجيات تقييم متعددة التداخل المتداخلة ، إذا حددنا صراحة ( قوة القراءة) عدد النوى المتاحة في كل مستوى. من أجل القيام بذلك ، نحتاج إلى "تعديل" الإعدادات الافتراضية ، والتي يمكن القيام بها على النحو التالي:
> plan( list (tweak( multisession , workers = 2 ), tweak( multisession ,
+ workers = 2 )))
[ ... ]
> pid
[ 1 ] 1437557
> a
Future ' a ' ...
[ 1 ] 1438105
> b
Future ' b ' ...
Future ' b1 ' ...
Future ' b2 ' ...
b.pid b1.pid b2.pid
1438106 1438211 1438212
أولاً ، نرى أن كلا من a
و b
يتم حلهما في عمليات مختلفة (PIDS 1438105 و 1438106) عن عملية الاتصال (PID 1437557). ثانياً ، يتم حل العقود الآجلة المتداخلة ( b1
و b2
) في عمليتين آخرين R (PIDS 1438211 و 1438212).
لمزيد من التفاصيل حول العمل مع العقود الآجلة المتداخلة واستراتيجيات التقييم المختلفة في كل مستوى ، راجع VIGNETTE "Futures in R: Future Topologies".
من الممكن التحقق مما إذا كان قد تم حل المستقبل أم لا دون حظر. يمكن القيام بذلك باستخدام دالة resolved(f)
، والتي تأخذ مستقبلًا صريحًا f
. إذا عملنا مع العقود المستقبلية الضمنية (كما في جميع الأمثلة أعلاه) ، فيمكننا استخدام وظيفة f <- futureOf(a)
لاسترداد المستقبل الصريح من واحد ضمني. على سبيل المثال،
> plan( multisession )
> a % <- % {
+ cat( " Future 'a' ... " )
+ Sys.sleep( 2 )
+ cat( " done n " )
+ Sys.getpid()
+ }
> cat( " Waiting for 'a' to be resolved ... n " )
Waiting for ' a ' to be resolved ...
> f <- futureOf( a )
> count <- 1
> while ( ! resolved( f )) {
+ cat( count , " n " )
+ Sys.sleep( 0.2 )
+ count <- count + 1
+ }
1
2
3
4
5
6
7
8
9
10
> cat( " Waiting for 'a' to be resolved ... DONE n " )
Waiting for ' a ' to be resolved ... DONE
> a
Future ' a ' ... done
[ 1 ] 1438287
في بعض الأحيان ليس المستقبل ما توقعته. في حالة حدوث خطأ أثناء تقييم المستقبل ، يتم نشر الخطأ وإلقاءه كخطأ في بيئة الاتصال عند طلب القيمة المستقبلية . على سبيل المثال ، إذا استخدمنا التقييم البطيء في المستقبل الذي يولد خطأ ، فقد نرى شيئًا مثل
> plan( sequential )
> b <- " hello "
> a % <- % {
+ cat( " Future 'a' ... n " )
+ log( b )
+ } % lazy % TRUE
> cat( " Everything is still ok although we have created a future that will fail. n " )
Everything is still ok although we have created a future that will fail.
> a
Future ' a ' ...
Error in log( b ) : non - numeric argument to mathematical function
يتم طرح الخطأ في كل مرة يتم فيها طلب القيمة ، أي إذا حاولنا الحصول على القيمة مرة أخرى ، فسيقوم بإنشاء نفس الخطأ (والإخراج):
> a
Future ' a ' ...
Error in log( b ) : non - numeric argument to mathematical function
In addition : Warning message :
restarting interrupted promise evaluation
للاطلاع على آخر مكالمة في مكدس المكالمات الذي أعطى الخطأ ، يمكننا استخدام وظيفة backtrace()
(*) في المستقبل ، أي
> backtrace( a )
[[ 1 ]]
log( a )
(*) لا يوفر traceback()
المعلومات ذات الصلة في سياق العقود المستقبلية. علاوة على ذلك ، لا يمكن للأسف رؤية قائمة المكالمات (التعبيرات التي تم تقييمها) التي أدت إلى الخطأ ؛ فقط المكالمة التي أعطت الخطأ (هذا بسبب قيود في tryCatch()
المستخدم داخليًا).
عندما يتم تقييم تعبير R بشكل غير متزامن (بالتوازي) أو بالتتابع عن طريق التقييم الكسول ، يجب تحديد الكائنات العالمية (الملقب "الحرة") وتمريرها إلى المقيِّم. يجب أن يتم تمريرها تمامًا كما كانوا في الوقت الذي تم فيه إنشاء المستقبل ، لأنه ، للتقييم البطيء ، قد تتغير الكرات على خلاف ذلك عند إنشائها وعندما يتم حلها. بالنسبة للمعالجة غير المتزامنة ، فإن السبب في حاجة إلى تحديد Globals هو أن يتم تصديرها إلى العملية التي تقيم المستقبل.
تحاول الحزمة المستقبلية أتمتة هذه المهام قدر الإمكان. يقوم بذلك بمساعدة حزمة Globals ، والتي تستخدم فحص الرمز الثابت لتحديد المتغيرات العالمية. إذا تم تحديد متغير عالمي ، يتم التقاطه وإتاحته لعملية التقييم. علاوة على ذلك ، إذا تم تعريف عالم عالمي في حزمة ، فلا يتم تصدير هذا العالم. بدلاً من ذلك ، يتم التأكد من إرفاق الحزمة المقابلة عند تقييم المستقبل. لا يعكس هذا بشكل أفضل إعداد جلسة R الرئيسية ، ولكنه يقلل أيضًا من الحاجة إلى تصدير الكرات ، مما يوفر ليس فقط الذاكرة ولكن أيضًا الوقت والعرض ، خاصة عند استخدام العقد عن بُعد.
أخيرًا ، يجب توضيح أن تحديد الكرات من فحص الكود الثابت وحده يمثل مشكلة صعبة. ستكون هناك دائمًا حالات زاوية حيث فشل التعرف التلقائي للكرات على أنه يتم تحديد إما كرات كاذبة (أقل من القلق) أو أن بعض الكرات الحقيقية مفقودة (مما سيؤدي إلى خطأ في وقت التشغيل أو ربما النتائج الخاطئة). توفر المقالات القصيرة "المستقبلية في R: المشكلات الشائعة مع الحلول" أمثلة على الحالات الشائعة وتشرح كيفية تجنبها وكذلك كيفية مساعدة الحزمة على تحديد الكرات أو تجاهل الكرات المحددة زوراً. إذا لم يكن ذلك كافياً ، فمن الممكن دائمًا تحديد المتغيرات العالمية يدويًا بأسمائها (على سبيل المثال globals = c("a", "slow_sum")
) أو كأزواج ذات قيمة اسم (على سبيل المثال globals = list(a = 42, slow_sum = my_sum)
).
هناك قيود واحدة مع العقود المستقبلية الضمنية التي لا توجد بالنسبة للآثار الصريحة. نظرًا لأن المستقبل الصريح يشبه أي كائن آخر في R ، يمكن تعيينه في أي مكان/أي شيء. على سبيل المثال ، يمكننا إنشاء العديد منها في حلقة وتعيينها إلى قائمة ، على سبيل المثال
> plan( multisession )
> f <- list ()
> for ( ii in 1 : 3 ) {
+ f [[ ii ]] <- future({
+ Sys.getpid()
+ })
+ }
> v <- lapply( f , FUN = value )
> str( v )
List of 3
$ : int 1438377
$ : int 1438378
$ : int 1438377
هذا ليس من الممكن القيام بذلك عند استخدام العقود الآجلة الضمنية. وذلك لأنه لا يمكن استخدام عامل التعيين %<-%
في جميع الحالات التي يمكن فيها استخدام عامل التعيين <-
. لا يمكن استخدامه إلا لتعيين القيم المستقبلية للبيئات (بما في ذلك بيئة الاتصال) مثل كيفية عمل assign(name, value, envir)
. ومع ذلك ، يمكننا تعيين العقود المستقبلية الضمنية للبيئات باستخدام المؤشرات المسماة ، على سبيل المثال
> plan( multisession )
> v <- new.env()
> for ( name in c( " a " , " b " , " c " )) {
+ v [[ name ]] % <- % {
+ Sys.getpid()
+ }
+ }
> v <- as.list( v )
> str( v )
List of 3
$ a : int 1438485
$ b : int 1438486
$ c : int 1438485
هنا as.list(v)
يتم حلها حتى يتم حل جميع العقود المستقبلية في البيئة v
ثم يتم جمع قيمها وإعادتها كقائمة عادية.
إذا كانت المؤشرات الرقمية مطلوبة ، فيمكن استخدام بيئات القائمة . بيئات القائمة ، التي يتم تنفيذها بواسطة حزمة الاستماع ، هي بيئات منتظمة مع مشغلي الإعداد الفرعي المخصص مما يجعل من الممكن فهرسة لهم مثل كيفية فهرسة القوائم. باستخدام بيئات قائمة حيث نستخدم القوائم بخلاف ذلك ، يمكننا أيضًا تعيين العقود المستقبلية الضمنية إلى الكائنات التي تشبه القائمة باستخدام مؤشرات رقمية. على سبيل المثال،
> library( listenv )
> plan( multisession )
> v <- listenv()
> for ( ii in 1 : 3 ) {
+ v [[ ii ]] % <- % {
+ Sys.getpid()
+ }
+ }
> v <- as.list( v )
> str( v )
List of 3
$ : int 1438582
$ : int 1438583
$ : int 1438582
كما سابقًا ، يتم حل as.list(v)
حتى يتم حل جميع العقود المستقبلية.
لرؤية توضيح مباشر كيف يتم تقييم أنواع مختلفة من العقود المستقبلية ، قم بتشغيل عرض Mandelbrot لهذه الحزمة. أولاً ، حاول التقييم المتسلسل ،
library( future )
plan( sequential )
demo( " mandelbrot " , package = " future " , ask = FALSE )
الذي يشبه كيفية تشغيل البرنامج النصي إذا لم يتم استخدام العقود المستقبلية. بعد ذلك ، جرب تقييم متعدد المهن ، الذي يحسب طائرات Mandelbrot المختلفة باستخدام عمليات R المتوازية التي تعمل في الخلفية. يحاول،
plan( multisession )
demo( " mandelbrot " , package = " future " , ask = FALSE )
أخيرًا ، إذا كان لديك وصول إلى آلات متعددة ، فيمكنك محاولة إعداد مجموعة من العمال واستخدامها ، على سبيل المثال
plan( cluster , workers = c( " n2 " , " n5 " , " n6 " , " n6 " , " n9 " ))
demo( " mandelbrot " , package = " future " , ask = FALSE )
R Package Future متاح على Cran ويمكن تثبيته في R على النحو التالي:
install.packages( " future " )
لتثبيت إصدار ما قبل الإصدار المتوفر في GIT Branch develop
على Github ، استخدم:
remotes :: install_github( " futureverse/future " , ref = " develop " )
سيؤدي هذا إلى تثبيت الحزمة من المصدر.
للمساهمة في هذه الحزمة ، يرجى الاطلاع على المساهمة.