سؤال

لقد طرحت سؤالاً حول أداء Lua، وعن استجابات طلبت:

هل قمت بدراسة النصائح العامة للحفاظ على أداء Lua عاليًا؟أي.تعرف على كيفية إنشاء الجدول وبدلاً من ذلك إعادة استخدامه بدلاً من إنشاء جدول جديد، استخدم "طباعة محلية = طباعة" وما شابه ذلك لتجنب عمليات الوصول العالمية.

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

نصيحة واحدة لكل إجابة ستكون مثالية.

هل كانت مفيدة؟

المحلول

ردًا على بعض الإجابات والتعليقات الأخرى:

صحيح أنه عليك كمبرمج عمومًا تجنب التحسين المبكر. لكن.هذا ليس صحيحًا بالنسبة للغات البرمجة النصية حيث لا يقوم المترجم بتحسينها كثيرًا - أو على الإطلاق.

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

هذه مجموعة مما اكتشفته مع مرور الوقت.لقد اكتشفت بعضًا منها عبر الشبكة، ولكن كانت ذات طبيعة مشبوهة عندما الشبكات البينية يشعرون بالقلق لقد اختبرت كل ذلك بنفسي.لقد قرأت أيضًا ورقة أداء Lua على موقع Lua.org.

بعض المراجع:

تجنب العولمة

هذه واحدة من التلميحات الأكثر شيوعًا، لكن ذكرها مرة أخرى لن يضر.

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

do
  x = gFoo + gFoo;
end
do -- this actually performs better.
  local lFoo = gFoo;
  x = lFoo + lFoo;
end

(ليس هذا الاختبار البسيط قد يؤدي إلى نتائج مختلفة.على سبيل المثال. local x; for i=1, 1000 do x=i; end هنا يستغرق رأس الحلقة وقتًا أطول في الواقع من جسم الحلقة، وبالتالي يمكن أن تكون نتائج التوصيف مشوهة.)

تجنب إنشاء السلسلة

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

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

-- do NOT do something like this
local ret = "";
for i=1, C do
  ret = ret..foo();
end

لو foo() سيعود فقط الحرف A, ، ستنشئ هذه الحلقة سلسلة من السلاسل مثل "", "A", "AA", "AAA", ، إلخ.سيتم تجزئة كل سلسلة وتبقى في الذاكرة حتى انتهاء التطبيق - هل ترى المشكلة هنا؟

-- this is a lot faster
local ret = {};
for i=1, C do
  ret[#ret+1] = foo();
end
ret = table.concat(ret);

لا تقوم هذه الطريقة بإنشاء سلاسل على الإطلاق أثناء الحلقة، بل يتم إنشاء السلسلة في الوظيفة foo ويتم نسخ المراجع فقط في الجدول.بعد ذلك، يقوم concat بإنشاء سلسلة ثانية "AAAAAA..." (اعتمادا على حجمها C يكون).لاحظ أنك استطاع يستخدم i بدلاً من #ret+1 ولكن في كثير من الأحيان لا يكون لديك مثل هذه الحلقة المفيدة ولن يكون لديك متغير مكرر يمكنك استخدامه.

هناك خدعة أخرى وجدتها في مكان ما على lua-users.org وهي استخدام gsub إذا كان عليك تحليل سلسلة

some_string:gsub(".", function(m)
  return "A";
end);

يبدو هذا غريبًا في البداية، والفائدة هي أن gsub ينشئ سلسلة "مرة واحدة" في لغة C والتي يتم تجزئتها فقط بعد إعادتها إلى lua عندما يعود gsub.يؤدي هذا إلى تجنب إنشاء الجدول، ولكن من المحتمل أن يكون لديه المزيد من الحمل الوظيفي (ليس إذا قمت باستدعاء foo() على أية حال، ولكن إذا foo() هو في الواقع تعبير)

تجنب الحمل الوظيفي

استخدم بنيات اللغة بدلاً من الوظائف حيثما أمكن ذلك

وظيفة ipairs

عند تكرار جدول، فإن الحمل الزائد للوظيفة من ipairs لا يبرر استخدامه.لتكرار جدول، استخدم بدلاً من ذلك

for k=1, #tbl do local v = tbl[k];

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

ملاحظة لوا 5.2:في 5.2 يمكنك في الواقع تحديد أ __ipairs الحقل في metatable، والتي يفعل يصنع ipairs مفيدة في بعض الحالات.ومع ذلك، Lua 5.2 يجعل أيضًا __len العمل الميداني للجداول، لذلك يمكنك ما زال تفضل الكود أعلاه ipairs كما ثم __len يتم استدعاء metamethod مرة واحدة فقط، بينما يتم استدعاء for ipairs سوف تحصل على استدعاء دالة إضافية لكل تكرار.

المهام table.insert, table.remove

استخدامات بسيطة ل table.insert و table.remove يمكن استبداله باستخدام # المشغل بدلاً من ذلك.هذا في الأساس مخصص لعمليات الدفع والبوب ​​البسيطة.وهنا بعض الأمثلة:

table.insert(foo, bar);
-- does the same as
foo[#foo+1] = bar;

local x = table.remove(foo);
-- does the same as
local x = foo[#foo];
foo[#foo] = nil;

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

استخدم الجداول لمقارنات SQL-IN على حد سواء

قد يكون لديك - أو لا يكون لديك - قرارات في التعليمات البرمجية الخاصة بك كما يلي

if a == "C" or a == "D" or a == "E" or a == "F" then
   ...
end

الآن هذه حالة صحيحة تمامًا، ولكن (من خلال اختباري الخاص) بدءًا من 4 مقارنات وباستثناء إنشاء الجدول، فإن هذا في الواقع أسرع:

local compares = { C = true, D = true, E = true, F = true };
if compares[a] then
   ...
end

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

تجنب إنشاء الجدول بشكل متكرر

تمت مناقشة هذا بالتفصيل في نصائح أداء لوا.المشكلة الأساسية هي أن Lua تقوم بتخصيص طاولتك حسب الطلب والقيام بذلك بهذه الطريقة سيستغرق في الواقع وقتًا أطول من تنظيف محتواه وملئه مرة أخرى.

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

إذا كان بإمكانك تحديد دالة C التي تقوم بمسح جدول، فيجب أن يكون هذا حلاً جيدًا لإعادة استخدام الجدول.

تجنب القيام بنفس الشيء مرارًا وتكرارًا

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

حفظ

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

إليك تطبيق الحفظ لوسيطة واحدة باستخدام جدول التعريف.(مهم:هذا البديل يفعل لا دعم وسيطة القيمة الصفرية، ولكنها سريعة جدًا بالنسبة للقيم الموجودة.)

function tmemoize(func)
    return setmetatable({}, {
        __index = function(self, k)
            local v = func(k);
            self[k] = v
            return v;
        end
    });
end
-- usage (does not support nil values!)
local mf = tmemoize(myfunc);
local v  = mf[x];

يمكنك بالفعل تعديل هذا النمط لقيم إدخال متعددة

تطبيق جزئي

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

-- Normal function
function foo(a, b, x)
    return cheaper_expression(expensive_expression(a,b), x);
end
-- foo(a,b,x1);
-- foo(a,b,x2);
-- ...

-- Partial application
function foo(a, b)
    local C = expensive_expression(a,b);
    return function(x)
        return cheaper_expression(C, x);
    end
end
-- local f = foo(a,b);
-- f(x1);
-- f(x2);
-- ...

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

البديل المتطرف لهذا سيكون الكاري, ، ولكن هذه في الواقع طريقة لتقليد البرمجة الوظيفية أكثر من أي شيء آخر.

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

function LinearColorBlender(col_from, col_to)
    local cfr, cfg, cfb, cfa = get_color_values(col_from);
    local ctr, ctg, ctb, cta = get_color_values(col_to);
    local cdr, cdg, cdb, cda = ctr-cfr, ctg-cfg, ctb-cfb, cta-cfa;
    if not cfr or not ctr then
        error("One of given arguments is not a color.");
    end

    return function(pos)
        if type(pos) ~= "number" then
            error("arg1 (pos) must be in range 0..1");
        end
        if pos < 0 then pos = 0; end;
        if pos > 1 then pos = 1; end;
        return cfr + cdr*pos, cfg + cdg*pos, cfb + cdb*pos, cfa + cda*pos;
    end
end
-- Call 
local blender = LinearColorBlender({1,1,1,1},{0,0,0,1});
object:SetColor(blender(0.1));
object:SetColor(blender(0.3));
object:SetColor(blender(0.7));

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

نصائح أخرى

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

وأول قانون للتحسين: لا

.

وأنا أحب أن أرى مشكلة حيث لديك خيار بين ipairs وأزواج ويمكن قياس تأثير الفرق.

وقطعة من السهل واحدة من الفواكه المتدلية هو أن نتذكر أن استخدام المتغيرات المحلية داخل كل وحدة. انها عموما لا يستحق تفعل أشياء مثل

local strfind = string.find

وإذا لم تتمكن من العثور على قياس أقول لك خلاف ذلك.

  • جعل الوظائف الأكثر استخداما من قبل السكان المحليين
  • الاستفادة الجيدة من الجداول مثل HashSets
  • خفض إنشاء الجدول عن طريق إعادة الاستخدام
  • باستخدام لواجيت!

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

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

ويمكنك أيضا حتى تقليد جوانب ثابتة من لغات أخرى مثل struct، C ++ / جافا class، الخ .. السكان المحليون والمصفوفات ما يكفي.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top