---
title: "نصائح لتحسين أداء OpenResty بمقدار 10 أضعاف: هيكل البيانات `Table` - API7.ai"
description: "في OpenResty، يؤثر الجدول (table) بشكل كبير على الأداء. تتناول هذه المقالة كيفية تحسين الأداء من خلال الحفاظ يدويًا على الفهارس (subscripts)، وإعادة استخدام الجدول (table)، واستخدام tabelpool."
url: "https://api7.ai/ar/learning-center/openresty/tips-10x-performance-improvement-openresty-table-data-structure"
---

# نصائح لتحسين أداء OpenResty بمقدار 10 أضعاف: هيكل البيانات \`Table\`

API7.ai

December 9, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

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

هناك سببان رئيسيان يجعلان المطورين يعرفون القليل عن تحسينات الأداء المتعلقة بـ `table` مقارنة بعمليات `string`.

1. Lua المستخدمة في OpenResty هي فرع LuaJIT الذي يتم صيانته ذاتيًا، وليس LuaJIT القياسية أو Lua القياسية. معظم المطورين لا يدركون الفرق ويميلون إلى استخدام مكتبة `table` القياسية في Lua لكتابة كود OpenResty.
2. سواء في LuaJIT القياسية أو في فرع LuaJIT الذي يتم صيانته بواسطة OpenResty نفسها، فإن الوثائق المتعلقة بعمليات `table` مخفية بعمق ويصعب على المطورين العثور عليها. ولا توجد أمثلة للكود في الوثائق، لذا يحتاج المطورون إلى البحث عن أمثلة في المشاريع مفتوحة المصدر.

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

قبل الدخول في تفاصيل تحسين `table`، أود أن أؤكد على مبدأ بسيط متعلق بتحسين `table`.

**حاول إعادة استخدام الجداول وتجنب إنشاء جداول غير ضرورية.**

سنقدم التحسين من حيث إنشاء `table`، إدراج العناصر، تفريغها، واستخدام الحلقات.

## إنشاء مصفوفة مسبقًا

الخطوة الأولى هي إنشاء مصفوفة. في Lua، الطريقة التي ننشئ بها المصفوفة بسيطة.

```
local t = {}
```

السطر السابق من الكود ينشئ مصفوفة فارغة. يمكنك أيضًا إضافة البيانات المهيأة عند الإنشاء:

```
local color = {first = "red", "blue", third = "green", "yellow"}
```

ومع ذلك، فإن الطريقة الثانية أكثر تكلفة من حيث الأداء لأنها تتضمن تخصيص المساحة، `resize` و `rehash` للمصفوفة في كل مرة يتم فيها إضافة أو حذف عنصر من المصفوفة.

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

تمت إضافة الدالة `table.new(narray, nhash)` في LuaJIT.

هذه الدالة تقوم بتخصيص مساحة المصفوفة والهاش المحددة مسبقًا بدلاً من النمو الذاتي عند إدراج العناصر. هذا هو المقصود من المعلمتين `narray` و `nhash`.

إليك مثال بسيط لمعرفة كيفية استخدامها. نظرًا لأن هذه الدالة هي امتداد لـ LuaJIT، نحتاج إلى استدعاء ما يلي قبل استخدامها.

```
local new_tab = require "table.new"
 local t = new_tab(100, 0)
 for i = 1, 100 do
   t[i] = i
 end
```

أيضًا، لأن OpenResty السابقة لم تكن مربوطة بالكامل بـ LuaJIT ولا تزال تدعم Lua القياسية، فإن بعض الأكواد القديمة ستفعل ذلك من أجل التوافق. إذا لم يتم العثور على الدالة `table.new`، فسيتم محاكاة دالة فارغة لضمان توحيد المتصل.

```
local ok, new_tab = pcall(require, "table.new")
  if not ok then
    new_tab = function (narr, nrec) return {} end
  end
```

## حساب الفهرس يدويًا

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

```
local new_tab = require "table.new"
 local t = new_tab(100, 0)
 for i = 1, 100 do
   table.insert(t, i)
 end
```

بدلاً من ذلك، يمكن الحصول على طول المصفوفة الحالية أولاً وإدراج العناصر باستخدام الفهارس:

```
local new_tab = require "table.new"
 local t = new_tab(100, 0)
 for i = 1, 100 do
   t[#t + 1] = i
 end
```

ومع ذلك، فإن كلاهما يحتاج إلى حساب طول المصفوفة أولاً ثم إضافة عناصر جديدة. تعقيد الوقت لهذه العملية هو `O(n)`. في مثال الكود أعلاه، ستقوم الحلقة `for` بحساب طول المصفوفة `100` مرة، لذا فإن الأداء ليس جيدًا، وكلما كانت المصفوفة أكبر، كان الأداء أسوأ.

لنلقي نظرة على كيفية حل مكتبة `lua-resty-redis` الرسمية لهذه المشكلة.

```
local function _gen_req(args)
    local nargs = #args

    local req = new_tab(nargs * 5 + 1, 0)
    req[1] = "*" .. nargs .. "\r\n"
    local nbits = 2

    for i = 1, nargs do
        local arg = args[i]
        req[nbits] = "$"
        req[nbits + 1] = #arg
        req[nbits + 2] = "\r\n"
        req[nbits + 3] = arg
        req[nbits + 4] = "\r\n"
        nbits = nbits + 5
    end
    return req
end
```

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

تستخدم المتغير `nbits` للحفاظ على فهرس `req` يدويًا، بدلاً من استخدام الدالة المضمنة في Lua `table.insert` والمشغل `#` للحصول على الطول.

يمكنك أن ترى في الحلقة `for`، بعض العمليات مثل `nbits + 1` تقوم بإدراج العناصر مباشرة كفهارس والحفاظ على الفهرس في القيمة الصحيحة في النهاية مع `nbits = nbits + 5`.

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

## إعادة استخدام `table` واحد

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

هنا تأتي الدالة `table.clear` لتكون مفيدة. من اسمها يمكنك أن ترى ما تفعله، فهي تقوم بمسح جميع البيانات في المصفوفة، ولكن طول المصفوفة لا يتغير. أي إذا كنت تستخدم `table.new(narray, nhash)` لإنشاء مصفوفة بطول 100، بعد مسحها، سيكون الطول لا يزال `100`.

لإعطائك فكرة أفضل عن تنفيذها، قمت بتقديم مثال للكود أدناه والذي يتوافق مع Lua القياسية:

```
local ok, clear_tab = pcall(require, "table.clear")
  if not ok then
    clear_tab = function (tab)
      for k, _ in pairs(tab) do
        tab[k] = nil
      end
    end
  end
```

كما ترى، تقوم الدالة `clear` بتعيين كل عنصر إلى `nil`.

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

لنلقي نظرة على مثال تطبيقي عملي. الكود التالي [شبه الكود](https://github.com/apache/apisix/blob/master/apisix/plugin.lua) مأخوذ من بوابة API للخدمات الصغيرة مفتوحة المصدر Apache APISIX، وهذا هو منطقها عند تحميل المكونات الإضافية.

```
local local_plugins = core.table.new(32, 0)
local function load(plugin_names, wasm_plugin_names)
    local processed = {}
    for _, name in ipairs(plugin_names) do
        if processed[name] == nil then
            processed[name] = true
        end
    end
    for _, attrs in ipairs(wasm_plugin_names) do
        if processed[attrs.name] == nil then
            processed[attrs.name] = attrs
        end
    end

    core.log.warn("new plugins: ", core.json.delay_encode(processed))

    for name, plugin in pairs(local_plugins_hash) do
        local ty = PLUGIN_TYPE_HTTP
        if plugin.type == "wasm" then
            ty = PLUGIN_TYPE_HTTP_WASM
        end
        unload_plugin(name, ty)
    end

    core.table.clear(local_plugins)
    core.table.clear(local_plugins_hash)

    for name, value in pairs(processed) do
        local ty = PLUGIN_TYPE_HTTP
        if type(value) == "table" then
            ty = PLUGIN_TYPE_HTTP_WASM
            name = value
        end
        load_plugin(name, local_plugins, ty)
    end

    -- sort by plugin's priority
    if #local_plugins > 1 then
        sort_tab(local_plugins, sort_plugin)
    end

    for i, plugin in ipairs(local_plugins) do
        local_plugins_hash[plugin.name] = plugin
        if enable_debug() then
            core.log.warn("loaded plugin and sort by priority:",
                          " ", plugin.priority,
                          " name: ", plugin.name)
        end
    end

    _M.load_times = _M.load_times + 1
    core.log.info("load plugin times: ", _M.load_times)
    return true
end
```

يمكنك أن ترى أن المصفوفة `local_plugins` هي المتغير الأعلى لوحدة `plugin`. في بداية دالة التحميل، يتم مسح `table`، ويتم إنشاء قائمة جديدة من المكونات الإضافية بناءً على الوضع الحالي.

## `tablepool`

حتى الآن، لقد أتقنت تحسين الدوران عبر `table` واحد. ثم يمكنك الذهاب خطوة أبعد واستخدام تجمع ذاكرة مؤقت للحفاظ على عدة جداول متاحة للوصول في أي وقت، وهي وظيفة `lua-tablepool` الرسمية.

يوضح الكود التالي الاستخدام الأساسي لتجمع الجداول. يمكننا جلب `table` من تجمع محدد وإعادته عند الانتهاء من استخدامه:

```
local tablepool = require "tablepool"
local tablepool_fetch = tablepool.fetch
local tablepool_release = tablepool.release

local pool_name = "some_tag"
local function do_sth()
     local t = tablepool_fetch(pool_name, 10, 0)
     -- -- using t for some purposes
    tablepool_release(pool_name, t)
end
```

يستخدم `tablepool` عدة من الطرق التي قدمناها سابقًا، ولديه أقل من مئة سطر من الكود، لذا أوصي بشدة بالبحث ودراسته بنفسك. هنا، سأقدم بشكل رئيسي واجهتي برمجة التطبيقات (APIs) الخاصة به.

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

```
tablepool.fetch(pool_name, narr, nrec)
```

الثانية هي `release`، وهي دالة تعيد `table` إلى التجمع. من بين وسيطاتها، الأخير `no_clear`، يستخدم لتكوين ما إذا كان سيتم استدعاء `table.clear` لمسح المصفوفة.

```
tablepool.release(pool_name, tb, [no_clear])
```

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

## الخلاصة

تحسين الأداء، منطقة صعبة في OpenResty، هي نقطة ساخنة. اليوم قدمت نصائح تحسين الأداء المتعلقة بـ `table`. آمل أن تساعدك في مشروعك الفعلي.

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

Share article link

Share

Related Posts

[![أسئلة وأجوبة حول OpenResty | كيفية استخدام OpenResty في الممارسة العملية](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [أسئلة وأجوبة حول OpenResty | كيفية استخدام OpenResty في الممارسة العملية](https://api7.ai/learning-center/openresty/faq-5)

[![الجزء الثالث: كيفية بناء بوابة API للخدمات المصغرة باستخدام OpenResty](https://static.api7.ai/uploads/2023/02/07/3XJXY9Jy_45.png)](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

## [الجزء الثالث: كيفية بناء بوابة API للخدمات المصغرة باستخدام OpenResty](https://api7.ai/learning-center/openresty/how-to-build-a-microservices-api-gateway-part-3)

Table of Contents

- [إنشاء مصفوفة مسبقًا](#%D8%A5%D9%86%D8%B4%D8%A7%D8%A1-%D9%85%D8%B5%D9%81%D9%88%D9%81%D8%A9-%D9%85%D8%B3%D8%A8%D9%82%D9%8B%D8%A7)
- [حساب الفهرس يدويًا](#%D8%AD%D8%B3%D8%A7%D8%A8-%D8%A7%D9%84%D9%81%D9%87%D8%B1%D8%B3-%D9%8A%D8%AF%D9%88%D9%8A%D9%8B%D8%A7)
- [إعادة استخدام `table` واحد](#%D8%A5%D8%B9%D8%A7%D8%AF%D8%A9-%D8%A7%D8%B3%D8%AA%D8%AE%D8%AF%D8%A7%D9%85-table-%D9%88%D8%A7%D8%AD%D8%AF)
- [`tablepool`](#tablepool)
- [الخلاصة](#%D8%A7%D9%84%D8%AE%D9%84%D8%A7%D8%B5%D8%A9)
