لقد توصلت إلى حل مرضٍ إلى حد ما، لذلك إذا كان أي شخص مهتمًا، ففكرت في مشاركته.ما زلت أقدر التعليقات حول كيفية تحسين/ضبط النهج.
في الأساس، قررت أن الطريقة الوحيدة المعقولة هي بناء نموذج بدائي (جدًا) للمجدول للحلقة المتوازية:
function c=est_cost_para(cost_blocks,cost_it,num_cores)
% Estimate cost of parallel computation
% Inputs:
% cost_blocks: Estimate of cost per block in arbitrary units. For
% consistency with the other code this must be in the reverse order
% that the scheduler is fed, i.e. cost should be ascending!
% cost_it: Base cost of iteration (regardless of number of entries)
% in the same units as cost_blocks.
% num_cores: Number of cores
%
% Output:
% c: Estimated cost of parallel computation
num_blocks=numel(cost_blocks);
c=zeros(num_cores,1);
i=min(num_blocks,num_cores);
c(1:i)=cost_blocks(end-i+1:end)+cost_it;
while i<num_blocks
i=i+1;
[~,i_min]=min(c); % which core finished first; is fed with next block
c(i_min)=c(i_min)+cost_blocks(end-i+1)+cost_it;
end
c=max(c);
end
المعلمة cost_it
بالنسبة للتكرار الفارغ فهو مزيج خام من العديد من الآثار الجانبية المختلفة، والتي يمكن فصلها:تكلفة التكرار الفارغ في ملف for
/parfor
-loop (يمكن أن تكون مختلفة أيضًا لكل كتلة)، بالإضافة إلى وقت بدء التشغيل.نقل البيانات من parfor
- حلقة (وربما أكثر).السبب الرئيسي وراء جمع كل شيء معًا هو أنني لا أريد أن أضطر إلى تقدير/تحديد التكاليف الأكثر تفصيلاً.
أستخدم الروتين أعلاه لتحديد القطع بالطريقة التالية:
% function i=cutoff_ser_para(cost_blocks,cost_it,num_cores)
% Determine cut-off between serial an parallel regime
% Inputs:
% cost_blocks: Estimate of cost per block in arbitrary units. For
% consistency with the other code this must be in the reverse order
% that the scheduler is fed, i.e. cost should be ascending!
% cost_it: Base cost of iteration (regardless of number of entries)
% in the same units as cost_blocks.
% num_cores: Number of cores
%
% Output:
% i: Number of blocks to be calculated serially
num_blocks=numel(cost_blocks);
cost=zeros(num_blocks+1,2);
for i=0:num_blocks
cost(i+1,1)=sum(cost_blocks(end-i+1:end))/num_cores + i*cost_it;
cost(i+1,2)=est_cost_para(cost_blocks(1:end-i),cost_it,num_cores);
end
[~,i]=min(sum(cost,2));
i=i-1;
end
على وجه الخصوص، أنا لا أقوم بتضخيم/تغيير قيمة est_cost_para
والذي يفترض (بصرف النظر عن cost_it
) الجدولة الأكثر تفاؤلاً ممكنة.أترك الأمر كما هو لأنني لا أعرف ما هو الأفضل.أن تكون محافظًا (أي.تجنب تغذية كتل كبيرة جدًا إلى الحلقة المتوازية)، يمكن بالطبع إضافة بعض النسبة المئوية كمخزن مؤقت أو حتى استخدام قدرة> 1 لتضخيم التكلفة الموازية.
لاحظ ذلك أيضًا est_cost_para
يتم استدعاؤه بكتل أقل على التوالي (على الرغم من أنني أستخدم اسم المتغير cost_blocks
لكلا الروتينين، أحدهما هو مجموعة فرعية من الآخر).
بالمقارنة مع النهج المتبع في سؤالي اللفظي، أرى ميزتين رئيسيتين:
- يتم التقاط الاعتماد المعقد نسبيًا بين البيانات (كل من عدد الكتل بالإضافة إلى تكلفتها) وعدد النوى بشكل أفضل بكثير باستخدام المجدول المحاكى مما قد يكون ممكنًا باستخدام صيغة واحدة.
- من خلال حساب تكلفة جميع المجموعات الممكنة للتوزيع التسلسلي/المتوازي ومن ثم أخذ الحد الأدنى، لا يمكن للمرء أن "يتعثر" في وقت مبكر جدًا أثناء قراءة البيانات من جانب واحد (على سبيل المثال:بقفزة كبيرة مقارنة بالبيانات حتى الآن، ولكنها صغيرة مقارنة بالإجمالي).
وبطبيعة الحال، فإن التعقيد المقارب يكون أعلى عن طريق الاتصال est_cost_para
مع حلقة while الخاصة بها طوال الوقت، ولكن في حالتي (num_blocks<500
) هذا لا يكاد يذكر على الاطلاق.
وأخيرا، إذا كانت قيمة لائقة cost_it
لا يقدم نفسه بسهولة، يمكن للمرء أن يحاول حسابه عن طريق قياس وقت التنفيذ الفعلي لكل كتلة، بالإضافة إلى الجزء الموازي البحت منها، ثم محاولة ملاءمة البيانات الناتجة للتنبؤ بالتكلفة والحصول على قيمة محدثة لـ cost_it
للمكالمة التالية للروتين (باستخدام الفرق بين التكلفة الإجمالية والتكلفة الموازية أو عن طريق إدراج تكلفة صفر في الصيغة المجهزة).نأمل أن "يتقارب" هذا مع القيمة الأكثر فائدة لـ cost_it
للمشكلة المعنية.