<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>هندسة الأنظمة | أحمد كمال عمارة</title><link>https://akemara.com/ar/tags/%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D8%A7%D9%86%D8%B8%D9%85%D8%A9/</link><atom:link href="https://akemara.com/ar/tags/%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D8%A7%D9%86%D8%B8%D9%85%D8%A9/index.xml" rel="self" type="application/rss+xml"/><description>هندسة الأنظمة</description><generator>Akemara Kit (https://akemara.com)</generator><language>ar</language><lastBuildDate>Tue, 23 Jun 2026 00:00:00 +0000</lastBuildDate><image><url>https://akemara.com/media/logo.svg</url><title>هندسة الأنظمة</title><link>https://akemara.com/ar/tags/%D9%87%D9%86%D8%AF%D8%B3%D8%A9-%D8%A7%D9%84%D8%A7%D9%86%D8%B8%D9%85%D8%A9/</link></image><item><title>كيف حوّلت مشكلة 100 ميلي ثانية إلى استعلام بالميكروثانية عبر هندسة أداء بمساعدة الذكاء الاصطناعي</title><link>https://akemara.com/ar/blog/ai-assisted-performance-engineering/</link><pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate><guid>https://akemara.com/ar/blog/ai-assisted-performance-engineering/</guid><description>&lt;p&gt;&lt;em&gt;مشكلة يانصيب مجرية حقيقية، وحلّان كاملان بكود Go الفعلي على الطاولة، والقصة الصادقة لكيف ساعد الذكاء الاصطناعي في خفض زمن الاستجابة من ميزانية 100 ميلي ثانية إلى نحو 1.5 ميكروثانية — ليس بأمر واحد، بل بتحسين منضبط خطوة بخطوة.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;من أنفع ما تعلمته في العمل مع الذكاء الاصطناعي على مشاكل الأداء ألا أسأل:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;«هل يمكنك تحسين هذا الكود؟»&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;بل أن أسأل:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;«ما العمل الذي يمكن نقله خارج المسار الحرج؟»&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;هذا السؤال الواحد كثيرًا ما يعيد كتابة المعمارية بأكملها. هذا المقال عن مشكلة بدأت بسطر عادي في مواصفة — &lt;strong&gt;أقصى زمن استجابة مقبول للاستعلام: 100 ميلي ثانية&lt;/strong&gt; — وانتهت في مكان لم أتوقعه.&lt;/p&gt;
&lt;p&gt;كانت المهمة الإبلاغ عن أعداد الفائزين في نظام شبيه باليانصيب يصل عدد لاعبيه إلى &lt;strong&gt;10 ملايين لاعب&lt;/strong&gt;. وجاءت النتائج النهائية هكذا:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;100 ms budget
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;12 ms optimized parallel scan (ordinary systems engineering)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1.5 µs (0.0015 ms) indexed O(1) query (AI-assisted redesign)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;بصياغة أخرى: كانت الميزانية 100 ميلي ثانية = 100,000,000 نانوثانية. وجاء زمن الاستعلام النهائي نحو 1,500 نانوثانية — أي أدنى من الميزانية بنحو &lt;strong&gt;66,000 مرة&lt;/strong&gt;. لكن الرقم النهائي ليس الجزء المشوّق. المشوّق هو كيف يستطيع الذكاء الاصطناعي المساعدة في تفكيك مشكلة إلى لبنات معروفة &lt;em&gt;تتراكب معًا&lt;/em&gt; في شيء أسرع بشكل درامي — وكم من التوجيه البشري تطلّب ذلك.&lt;/p&gt;
&lt;p&gt;قبل أن نمضي، يجدر تثبيت المقياس الذي نتحدث عنه، لأن الفرق بين الميلي ثانية والميكروثانية هو الفرق بين «سريع بما يكفي» و«مجاني عمليًا».&lt;/p&gt;
&lt;p&gt;والعلاقة بين الوحدات تستحق الحفظ — فعمل الأداء يحيا ويموت على معرفة الوحدة التي أنت فيها:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;UNIT&lt;/span&gt; &lt;span class="n"&gt;SYMBOL&lt;/span&gt; &lt;span class="n"&gt;SECONDS&lt;/span&gt; &lt;span class="n"&gt;vs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="n"&gt;NEXT&lt;/span&gt; &lt;span class="n"&gt;UNIT&lt;/span&gt; &lt;span class="n"&gt;EVERYDAY&lt;/span&gt; &lt;span class="n"&gt;FEEL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;─────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;second&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;slow&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="nb"&gt;load&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;millisecond&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="mf"&gt;0.001&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;snappy&lt;/span&gt; &lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;microsecond&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="mf"&gt;0.000001&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;few&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="n"&gt;reads&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;nanosecond&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="mf"&gt;0.000000001&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="n"&gt;CPU&lt;/span&gt; &lt;span class="n"&gt;instruction&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;─────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;scan&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt; &lt;span class="err"&gt;µ&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="n"&gt;indexed&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;227&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;000&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="n"&gt;ns&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;
&lt;figure id="figure-الرحلة-كلها-على-مقياس-واحد-الثانية-ألف-ميلي-ثانية-والميلي-ثانية-ألف-ميكروثانية-والميكروثانية-ألف-نانوثانية-الانتقال-من-12-ميلي-ثانية-إلى-15-ميكروثانية-ليس-مكسبا-صغيرا--إنه-عبور-نحو-أربع-مراتب-عشرية"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="وحدات الزمن على مقياس واحد: من الثواني إلى الميلي ثانية فالميكروثانية فالنانوثانية، مع رحلة المشروع من ميزانية 100 ميلي ثانية إلى استعلام 1.5 ميكروثانية"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_c1825071bd8a8cc4.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_a9da2cecc2191da.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_d1317e6debe1bb3a.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_c1825071bd8a8cc4.webp"
width="760"
height="372"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/article-cover-2_hu_771a8fc83770aca0.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
الرحلة كلها على مقياس واحد. الثانية ألف ميلي ثانية؛ والميلي ثانية ألف ميكروثانية؛ والميكروثانية ألف نانوثانية. الانتقال من 12 ميلي ثانية إلى 1.5 ميكروثانية ليس مكسبًا صغيرًا — إنه عبور نحو أربع مراتب عشرية.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="the-hungarian-lottery-problem"&gt;مشكلة اليانصيب المجري&lt;/h2&gt;
&lt;p&gt;القواعد بسيطة:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;يختار كل لاعب &lt;strong&gt;5 أرقام مختلفة&lt;/strong&gt; من &lt;strong&gt;1 إلى 90&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;لاحقًا، يسحب اليانصيب &lt;strong&gt;5 أرقام رابحة مختلفة&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;يفوز اللاعب إذا طابق &lt;strong&gt;2 أو 3 أو 4 أو 5&lt;/strong&gt; أرقام بالضبط.&lt;/li&gt;
&lt;li&gt;في الذروة قد يبلغ عدد اللاعبين نحو &lt;strong&gt;10 ملايين&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;بعد السحب، يجب أن يبلّغ النظام بأعداد الفائزين فورًا.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;المخرجات المطلوبة بعد كل سحب تجيب عن أربعة أسئلة: كم لاعبًا طابق رقمين بالضبط؟ وكم طابق ثلاثة؟ وأربعة؟ وخمسة؟&lt;/p&gt;
&lt;p&gt;للوهلة الأولى يبدو الأمر تافهًا — إنها مسألة عدّ. لكن هندسة الأداء تبدأ لحظة يضيف أحدهم متطلب زمن استجابة. مع وجود &lt;strong&gt;أقصى زمن استجابة بعد السحب: 100 ميلي ثانية&lt;/strong&gt; في المواصفة، يتغير السؤال بهدوء: نتوقف عن سؤال «هل نستطيع عدّ الفائزين؟» ونبدأ بالسؤال الوحيد المهم على هذا النطاق:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;كم من العمل يجب أن يحدث بعد معرفة الأرقام الرابحة؟&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;هذا التمييز يقود إلى تنفيذين مختلفين تمامًا: &lt;strong&gt;حل بالمسح&lt;/strong&gt; مُحسَّن للبساطة وقلة الذاكرة وكفاءة الـ cache؛ و&lt;strong&gt;حل بالفهرسة&lt;/strong&gt; مُحسَّن لزمن استجابة شديد الانخفاض بعد السحب. كلاهما مفيد، وكلاهما يستحق الفهم — والفجوة بينهما هي حيث تعيش هذه القصة.&lt;/p&gt;
&lt;h2 id="understanding-the-shape-of-the-problem"&gt;فهم شكل المشكلة&lt;/h2&gt;
&lt;p&gt;نادرًا ما تُحل المشاكل الثقيلة الأداء بحيلة واحدة. في معظم الأحيان، العمل الحقيقي هو فهم &lt;em&gt;شكل&lt;/em&gt; المشكلة قبل كتابة أي كود. أحاول الإجابة عن مجموعة صغيرة من الأسئلة أولًا:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ما المعروف مسبقًا؟&lt;/li&gt;
&lt;li&gt;ما الذي يجب الإجابة عنه فورًا؟&lt;/li&gt;
&lt;li&gt;ما المسار الحرج الحقيقي؟&lt;/li&gt;
&lt;li&gt;أين عنق الزجاجة — المعالج، أم نطاق الذاكرة، أم التخصيص، أم الإدخال/الإخراج، أم التزامن، أم التعقيد الخوارزمي؟&lt;/li&gt;
&lt;li&gt;ما المقايضة التي نقبلها بين الذاكرة ووقت المعالجة المسبقة وزمن الاستعلام؟&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;في اليانصيب، الإجابات كاشفة. فالتذاكر &lt;em&gt;معروفة قبل السحب&lt;/em&gt;؛ والأرقام الرابحة &lt;em&gt;لا تُعرف إلا بعده&lt;/em&gt;. والمسار الحرج — الجزء الذي ينتظره إنسان فعلًا — هو تقرير ما بعد السحب؛ وكل ما عداه تحضير نستطيع إنجازه على جدولنا الخاص. فالسؤال الصحيح ليس «ما سرعة البرنامج كله؟» بل «ما سرعة الجزء الذي ينتظره المستخدمون؟»&lt;/p&gt;
&lt;h2 id="thinking-like-an-ai-performance-agent"&gt;التفكير كوكيل ذكاء اصطناعي للأداء&lt;/h2&gt;
&lt;p&gt;عندما أعمل مع الذكاء الاصطناعي على مشاكل التحسين، أحاول تسليمه &lt;em&gt;قيودًا&lt;/em&gt; بدل تفاصيل التنفيذ. شيئًا مثل هذا:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Answer winner counts after the draw.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Constraint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Maximum latency 100 ms.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Dataset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Up to 10 million tickets.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Allowed tradeoffs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;more preprocessing,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;more memory,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lower query latency.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;What work can be moved before the draw?&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;هذه الصياغة تنتج طبيعيًا معماريتين: &lt;strong&gt;امسح كل شيء بعد السحب&lt;/strong&gt;، أو &lt;strong&gt;حضّر مسبقًا بما يكفي ليصبح السحب شبه مجاني&lt;/strong&gt;. الخيار الثاني هو حيث تصبح الأمور مشوّقة — لكنني أريد الصدق منذ البداية حول ترتيب الأحداث: لم أصل إلى الحل السريع بكتابة أمر ذكي واحد. وصلت أولًا إلى حل متين عادي، بيدي، وعندها فقط استخدمت الذكاء الاصطناعي لمهاجمة الجزء الذي عجزت الهندسة العادية عن تحسينه.&lt;/p&gt;
&lt;h2 id="the-key-idea-split-the-problem-into-phases"&gt;الفكرة المفتاحية: قسّم المشكلة إلى مراحل&lt;/h2&gt;
&lt;p&gt;
&lt;figure &gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="مخطط يقسم النظام إلى مرحلة تحضير قبل السحب ومرحلة استعلام دنيا بعده"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_ecb5a6ba4aa3ded9.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_5396a45ff4892110.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_b77e3c5202c6e549.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_ecb5a6ba4aa3ded9.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-key-idea-split-the-problem-into-phases_hu_46000d7691140d77.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;بما أن التذاكر معروفة قبل السحب والأرقام الرابحة ليست كذلك، ينقسم النظام طبيعيًا إلى مرحلتين:&lt;/p&gt;
&lt;p&gt;في المشاكل الثقيلة الأداء، هذا الفصل غالبًا أهم من الخوارزمية نفسها. فالنظام الذي ينجز عملًا أكثر &lt;em&gt;قبل&lt;/em&gt; السحب وقليلًا جدًا &lt;em&gt;بعده&lt;/em&gt; يستطيع التغلب على نظام يبدأ سريعًا لكنه يمسح كل شيء والساعة تدق. هذا من أهم الأنماط في مجالنا: &lt;strong&gt;انقل العمل من المسار الحرج إلى مرحلة التحضير.&lt;/strong&gt; محركات البحث تفعلها بالفهارس المقلوبة، وقواعد البيانات بالفهارس الثانوية، والمترجمات بالتمثيلات الوسيطة، وأنظمة التوصيات بالسمات المحسوبة مسبقًا. واليانصيب ليس مختلفًا.&lt;/p&gt;
&lt;h2 id="high-level-architecture"&gt;المعمارية العامة&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-واجهة-أمامية-واحدة-ومساران-للتنفيذ-وضع-المسح-on-لكل-سحب-ووضع-الفهرسة-ينفذ-26-قراءة-ثابتة-لكل-سحب--o1-مستقلة-عن-عدد-اللاعبين"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="معمارية بواجهة أمامية واحدة تغذي مسارين: وضع مسح O(N) ووضع فهرسة O(1)"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_d1bfa99d8b9f68b9.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_5aafced10c5a7fc2.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_2c4e4ca2d90ab016.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_d1bfa99d8b9f68b9.webp"
width="760"
height="428"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/high-level-architecture_hu_5b79eae148444e3f.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
واجهة أمامية واحدة ومساران للتنفيذ. وضع المسح O(N) لكل سحب؛ ووضع الفهرسة ينفذ 26 قراءة ثابتة لكل سحب — O(1) مستقلة عن عدد اللاعبين.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;يدعم التصميم النهائي وضعي تنفيذ يتشاركان الواجهة الأمامية نفسها. تُحلل المدخلات بالتوازي إلى تمثيل مضغوط مرصوص، ومنه تتغذى البيانات إما إلى مسار المسح أو مسار الفهرسة:&lt;/p&gt;
&lt;p&gt;حقق وضع المسح هدف زمن الاستجابة بارتياح. أما وضع الفهرسة فقد ألغى زمن الاستعلام تقريبًا. سنبني كليهما — ومن حسن الحظ أن كليهما يحقق توقيع Go المكوّن من سطر واحد نفسه، فيُكتب باقي البرنامج على الواجهة وتُختار الخوارزمية عند الإقلاع بعلم (flag):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;type winnerCounter func(d1, d2, d3, d4, d5 int) (w2, w3, w4, w5 int)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// ./lottery -mode scan input.txt → O(N) per draw, lower memory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// ./lottery -mode indexed input.txt → O(1) per draw, default
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;هذا التناظر ليس مجرد ترتيب؛ إنه ما يتيح للمسار البطيء &lt;em&gt;التحقق&lt;/em&gt; من المسار السريع لاحقًا.&lt;/p&gt;
&lt;h2 id="solution-1-the-cache-friendly-scan-12-ms-and-no-ai-required"&gt;الحل 1 — المسح الودود مع الـ Cache ‏(12 ميلي ثانية، وبلا ذكاء اصطناعي)&lt;/h2&gt;
&lt;p&gt;أريد أن أكون واضحًا: &lt;em&gt;لا يوجد سحر ذكاء اصطناعي في هذا القسم&lt;/em&gt;. نتيجة الـ 12 ميلي ثانية جاءت من هندسة أنظمة عادية منضبطة — من النوع الذي يستطيعه أي مهندس مهتم بالأداء بمحلل أداء وقليل من الصبر. وهذا مهم، لأنه يضع الحد الذي كان على الحل المدعوم بالذكاء الاصطناعي أن يتجاوزه.&lt;/p&gt;
&lt;p&gt;حل المسح هو أبسط نهج صحيح: حمّل كل التذاكر في الذاكرة، ثم لكل سحب امسح كل تذكرة وعدّ التطابقات:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;for each ticket:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; matches = count numbers that appear in the draw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 2: winners2++
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 3: winners3++
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 4: winners4++
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if matches == 5: winners5++
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;التعقيد &lt;strong&gt;O(N) لكل سحب&lt;/strong&gt;. لعشرة ملايين لاعب &lt;em&gt;يبدو&lt;/em&gt; ذلك مكلفًا، لكن الثوابت ضئيلة — وتخطيط البيانات هو مصدر المكسب.&lt;/p&gt;
&lt;h2 id="compact-representation"&gt;التمثيل المضغوط&lt;/h2&gt;
&lt;p&gt;بدل تخزين التذاكر ككائنات أو structs أو maps أو شرائح متداخلة، خزّنها مصفوفة بايتات واحدة مسطحة:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tickets []byte // player i occupies tickets[5*i : 5*i+5]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;فيصبح تخطيط الذاكرة مجرد أرقام متلاصقة بلا أي شيء بينها:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Player 0 Player 1 Player 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌─────────────┐ ┌─────────────┐ ┌─────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ n n n n n │ │ n n n n n │ │ n n n n n │ ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─────────────┘ └─────────────┘ └─────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Flat memory: [n0 n1 n2 n3 n4][n0 n1 n2 n3 n4][n0 n1 n2 n3 n4]...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ببايت واحد لكل رقم، تنضغط 10 ملايين تذكرة في نحو &lt;code&gt;10,000,000 × 5 = 50 MB&lt;/code&gt;، والمعالج الحديث يتدفق عبر 50 ميغابايت من الذاكرة المتجاورة بسرعة مدهشة. (الملف النصي الخام أقرب إلى 145 ميغابايت — المزيد عن كيفية تفادي نسخه لاحقًا.) والمزايا تتراكم: لا تخصيص لكل تذكرة، ولا مطاردة مؤشرات، وبصمة مضغوطة، ووصول تسلسلي، ومسح ودود مع الـ cache، وتقسيم بديهي بين العمال.&lt;/p&gt;
&lt;p&gt;«في الكود الثقيل الأداء، &lt;strong&gt;تخطيط البيانات جزء من الخوارزمية&lt;/strong&gt;. فالخوارزمية البسيطة نظريًا بمحلية جيدة تتفوق بانتظام على الخوارزمية الأذكى المنفذة بمحلية رديئة.»&lt;/p&gt;
&lt;h2 id="the-hot-loop-a-91-byte-table-that-lives-in-l1"&gt;الحلقة الساخنة: جدول من 91 بايت يسكن L1&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-وضع-المسح-في-كل-سحب-علم-جدول-الإصابات-ووزع-التذاكر-المرصوصة-على-العمال-ودع-كلا-منهم-يبني-مدرجه-الخاص-ثم-ادمج-واطبع-لا-أقفال-في-المسار-الساخن"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="تدفق وضع المسح: علّم جدول الإصابات ذا الـ 91 بايتًا، ووزّع التذاكر المرصوصة على عمال بمدرجات خاصة، ثم ادمج واطبع"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_9ab355ff2693c152.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_3ed1a918d421dc51.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_6d775bac6bef974e.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_9ab355ff2693c152.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/the-hot-loop-a-91-byte-table-that-lives-in-l1_hu_63a9bdc1529bef40.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
وضع المسح في كل سحب: علّم جدول الإصابات، ووزّع التذاكر المرصوصة على العمال، ودع كلًا منهم يبني مدرجه الخاص، ثم ادمج واطبع. لا أقفال في المسار الساخن.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;لكل سحب، ابنِ جدول عضوية صغيرًا — بايت واحد لكل رقم ممكن — وعلّم الأرقام الخمسة المسحوبة. عند 91 بايتًا يبقى الجدول مقيمًا في cache المستوى الأول L1، فتصبح كل قراءة شبه مجانية. وينهار عدّ اللاعب إلى خمس قراءات مفهرسة وزيادة عداد: لا تقاطع مجموعات، ولا بحث في map، ولا فرز، ولا تخصيص. هذه هي الحلقة الساخنة الفعلية، منقولة من المصدر مباشرة:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;maxNumber&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;resident&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;L1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// each worker walks its own slice of players into a private histogram:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chunkStart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chunkEnd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDrawn&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// m is 0..5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;histogram&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// buckets 2..5 are the answer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;هذه هي الحسبة بأكملها: جمع خالٍ من التفرعات لخمسة بايتات وزيادة خانة واحدة، عشرة ملايين مرة — من نوع العمل الذي يمضغه المعالج بلا شكوى.&lt;/p&gt;
&lt;h2 id="parallel-scanning"&gt;المسح المتوازي&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-نمط-map-ثم-reduce-فوق-الذاكرة-المرصوصة-ينتج-العمال-مدرجات-محلية-بالتوازي-ودمج-واحد-يجمعها-خانات-المدرج-من-2-إلى-5-هي-الإجابة-التي-طلبها-العمل"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="نمط map ثم reduce فوق ذاكرة مرصوصة: العمال يبنون مدرجات محلية بالتوازي ودمج واحد يجمع الخانات من 2 إلى 5"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_d00a2d617ad0d51c.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_6003d5193a4f565d.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_e61f970a69122f05.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_d00a2d617ad0d51c.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/parallel-scanning_hu_7f0a11db0f4495b8.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
نمط map ثم reduce فوق الذاكرة المرصوصة. ينتج العمال مدرجات محلية بالتوازي؛ ودمج واحد يجمعها. خانات المدرج من 2 إلى 5 هي الإجابة التي طلبها العمل.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;ينقسم المسح نظيفًا عبر أنوية المعالج. يعالج كل عامل نطاقًا متجاورًا من اللاعبين ويحتفظ بمدرج تكراري &lt;em&gt;خاص&lt;/em&gt;، فلا أقفال في الحلقة الساخنة:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Packed tickets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌───────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ player 0 ............................. player N │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└───────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌────────────┐ ┌────────────┐ ┌────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Worker 0 │ │ Worker 1 │ │ Worker 2 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ hist[0..5] │ │ hist[0..5] │ │ hist[0..5] │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─────┬──────┘ └─────┬──────┘ └─────┬──────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └───────────────┼───────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌──────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Merge result │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;يبقى التعقيد النظري &lt;strong&gt;O(N)&lt;/strong&gt;، لكن الزمن العملي يقترب من &lt;strong&gt;O(N / p)&lt;/strong&gt; حيث &lt;em&gt;p&lt;/em&gt; عدد الأنوية. والأفضل أن كل عامل يمشي في منطقة ذاكرة متجاورة يمكن توقعها — وهذا بالضبط نمط الوصول الذي تعشقه وحدات الجلب المسبق في العتاد.&lt;/p&gt;
&lt;h2 id="result-12-ms"&gt;النتيجة: 12 ميلي ثانية&lt;/h2&gt;
&lt;p&gt;على جهاز مقيّد عمدًا — 4 معالجات منطقية و4 غيغابايت ذاكرة — عالج تنفيذ المسح 10 ملايين لاعب بمتوسط زمن استجابة للسحب نحو &lt;strong&gt;12.2 ميلي ثانية&lt;/strong&gt;. مقابل ميزانية 100 ميلي ثانية، هذا أسرع من المطلوب بنحو &lt;strong&gt;8 مرات&lt;/strong&gt; بالفعل. أُنجزت المهمة. هكذا بدا الأمر.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;لماذا نبقي وضع المسح أصلًا؟&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;إنه بسيط، وكفء في الذاكرة، وسهل التنقيح، وسهل الثقة به. يصلح خط أساس مثاليًا للصحة، ويتألق حين تشح الذاكرة أو تندر السحوبات، و— كما سنرى — يستطيع &lt;em&gt;التحقق&lt;/em&gt; من الحل السريع. قبل بناء تحسين معقد، ابنِ أبسط نسخة سريعة بما يكفي للقياس: أحيانًا تكون كافية بالفعل، ودائمًا تدلك على عنق الزجاجة الحقيقي.&lt;/p&gt;
&lt;h2 id="asking-ai-a-better-question"&gt;سؤال أفضل للذكاء الاصطناعي&lt;/h2&gt;
&lt;p&gt;مع 12 ميلي ثانية في اليد، كانت المتابعة الكسولة أن أسأل «هل يمكن أن يصبح أسرع؟». الذكاء الاصطناعي يسعده الرد — حلقات أضيق، وSIMD، وضبط حجم القطع — فتستعيد نسبة مئوية ضئيلة. سألت بدلًا من ذلك سؤالًا مختلفًا بنيويًا:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;هل يمكن أن يصبح الاستعلام مستقلًا عن عدد اللاعبين؟&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;هذا يغيّر كل شيء. فبضعة في المئة تحسين؛ أما الاستقلال عن N فنموذج تشغيل مختلف. واتضح أن الإجابة نعم — والمعمارية الناتجة ليست رياضيات جديدة بل تركيب لعدة أفكار كلاسيكية. وتركيبها تركيبًا صحيحًا، في الكود، عند 1.5 ميكروثانية، لم يكن أمرًا من طلقة واحدة.&lt;/p&gt;
&lt;h2 id="the-ai-collaboration-was-iterative-not-one-prompt"&gt;التعاون مع الذكاء الاصطناعي كان تكراريًا لا أمرًا واحدًا&lt;/h2&gt;
&lt;p&gt;هذا هو الجزء الذي تتجاوزه بصمت معظم قصص «استخدمت الذكاء الاصطناعي فضاعف كودي 1000 مرة». سؤال إعادة الصياغة فتح الباب، لكن عبوره تطلّب جولات عدة من التوجيه &lt;em&gt;الدقيق&lt;/em&gt;، والقيود المعمارية، وقبل كل شيء: القياس والتحقق في كل خطوة. إليكم سجل التكرارات الصادق.&lt;/p&gt;
&lt;h2 id="iteration-1-the-first-design-was-correct-and-slow"&gt;التكرار 1 — التصميم الأول كان صحيحًا وبطيئًا&lt;/h2&gt;
&lt;p&gt;حين طُلب من النموذج جعل الاستعلام مستقلًا عن N، اقترح حساب تكرارات التوليفات مسبقًا والإجابة عبر القراءات. حدس جيد — لكن تنفيذه الأول بنى كل شيء على hash maps: ‏&lt;code&gt;map[pair]int&lt;/code&gt; و&lt;code&gt;map[triple]int&lt;/code&gt; وهكذا. صحيح لكنه ثقيل: حساب hash في المسار الساخن، وعبء دلاء، ومطاردة مؤشرات، وضغط على جامع المهملات، وزمن استجابة لا يمكن توقعه. أعطيته قيدًا صارمًا: &lt;strong&gt;لا hashing في مسار الاستعلام، ولا تخصيص لكل عنصر.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="iteration-2-dense-arrays-demand-a-bijection"&gt;التكرار 2 — المصفوفات الكثيفة تفرض تقابلًا&lt;/h2&gt;
&lt;p&gt;إزالة الـ maps تعني تخزين العدادات في مصفوفات مسطحة، وهذا يفرض مسألة فرعية جديدة: كل توليفة تحتاج &lt;em&gt;فهرسًا&lt;/em&gt; صحيحًا حتميًا. ثبّته بدقة — «حوّل كل توليفة k مفروزة إلى فهرس فريد في &lt;code&gt;[0, C(90,k))&lt;/code&gt; دون تخصيص» — فمدّ النموذج يده إلى &lt;strong&gt;نظام الأعداد التوافقي&lt;/strong&gt; (combinadics). كان تفصيلان غير قابلين للتفاوض: حوّل الأرقام إلى أساس صفري أولًا، و&lt;strong&gt;احسب المعاملات الثنائية مسبقًا&lt;/strong&gt; بعلاقة Pascal حتى لا تُحسب &lt;code&gt;C(n,k)&lt;/code&gt; في حلقة أبدًا.&lt;/p&gt;
&lt;h2 id="iteration-3-the-inflated-counts-bug-caught-by-the-baseline"&gt;التكرار 3 — علة الأعداد المتضخمة (اصطادها خط الأساس)&lt;/h2&gt;
&lt;p&gt;أنتج أول تشغيل كامل للحل المفهرس أرقامًا أكبر من المعقول بوضوح — اللحظة الأهم في المشروع كله، ولم ألتقطها إلا لأن خط أساس المسح منحني الثابتة &lt;code&gt;scan(draw) == indexed(draw)&lt;/code&gt;، وقد فشلت. السبب فخ كلاسيكي: &lt;em&gt;مجاميع المجموعات الجزئية ليست أعداد التطابق الدقيق&lt;/em&gt;. فتذكرة التطابق الخماسي تُعد أيضًا داخل كل مجموع زوجي وثلاثي ورباعي. طلبت من النموذج اشتقاق التصحيح لا ترقيعه، وثبّت جدول المساهمات (تذكرة التطابق الرباعي تساهم بـ 6 أزواج و4 ثلاثيات ورباعية واحدة؛ والخماسية بـ 10/10/5/1). أنتج ذلك إعادة الإحلال العكسي بمبدأ &lt;strong&gt;الإدراج والاستبعاد بصيغة «بالضبط k»&lt;/strong&gt;، وعادت الثابتة تصمد.&lt;/p&gt;
&lt;h2 id="iteration-4-make-preprocessing-fast-too"&gt;التكرار 4 — اجعل التحضير سريعًا أيضًا&lt;/h2&gt;
&lt;p&gt;الاستعلامات ثابتة الزمن بلا قيمة إذا استغرق بناء الفهرس دهرًا، وكان التحضير الساذج يخصص ذاكرة لكل توليفة ويسمّر أربعة أنوية على خطوط cache الساخنة نفسها. سد هذه الفجوة — زيادات بلا تخصيص، وتحليل متوازٍ فوق مدخلات ممثلة في الذاكرة (memory-mapped)، واختيار لكل جدول بين التجميع الخاص والعمليات الذرّية (موضوع قسم كامل أدناه) — هو حيث ذهب فعليًا كثير من الأخذ والرد.&lt;/p&gt;
&lt;h2 id="iteration-5-correctness-edge-cases-and-validation-policy"&gt;التكرار 5 — الصحة والحالات الحدّية وسياسة التحقق&lt;/h2&gt;
&lt;p&gt;أخيرًا قدت جولة اختبارات: مجموعة البيانات الصغيرة القابلة للفحص اليدوي، والتذاكر المكررة، والأسطر المشوهة، والرموز الرقمية المرضية التي تفيض على محلل مكتوب يدويًا. وجعلنا سياسة التحقق صريحة (صارمة أو متساهلة) بدل ترك المدخلات الفاسدة تفسد المخرجات بصمت.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;تقسيم العمل الحقيقي&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;قدّم الذكاء الاصطناعي اللبنات الكلاسيكية وكثيرًا من الكود. &lt;strong&gt;وقدّمت أنا المواصفة والثوابت والقياسات وبوابة الصحة.&lt;/strong&gt; «اجعله أسرع» لا توصلك إلى شيء؛ أما «اجعل الاستعلام O(1)، كثيفًا، بلا تخصيص، بدلالات تطابق دقيق، وتحقق منه مقابل خط الأساس» فتوصلك إلى 1.5 ميكروثانية. الرافعة في القيود، لا في عدد الأوامر.&lt;/p&gt;
&lt;h2 id="solution-2-indexed-counting-the-15-µs-solution"&gt;الحل 2 — العد المفهرس (حل الـ 1.5 ميكروثانية)&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-حضر-مسبقا-ثم-أجب-بالقراءات-عمل-تحويل-التذاكر-إلى-مجموعات-جزئية-المكلف-يحدث-قبل-السحب-وبعده-تتحول-قراءات-الجداول-الأربعة-إلى-t2t5-ويحولها-الإدراج-والاستبعاد-إلى-أعداد-دقيقة"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="خط العد المفهرس: احسب جداول المجموعات الجزئية من التذاكر قبل السحب، ثم أجب بعده بـ 26 قراءة وتصحيح الإدراج والاستبعاد"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_cfee16989513b13e.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_f192777ed8b92619.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_aad1bd5a23ef5a2d.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_cfee16989513b13e.webp"
width="760"
height="570"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/solution-2-indexed-counting-the-1-5-s-solution_hu_fae3c5a99dafdb9e.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
حضّر مسبقًا ثم أجب بالقراءات. عمل تحويل التذاكر إلى مجموعات جزئية المكلف يحدث قبل السحب. وبعده تتحول قراءات الجداول الأربعة إلى T2–T5، ويحوّلها الإدراج والاستبعاد إلى أعداد دقيقة.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;ينطلق الحل المفهرس من سؤال مختلف: هل نستطيع نقل &lt;em&gt;كل&lt;/em&gt; العمل تقريبًا إلى ما قبل السحب؟ كل تذكرة فيها خمسة أرقام، ومن الخمسة نستطيع توليد كل توليفة أصغر:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,2) = 10 pairs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,3) = 10 triples
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,4) = 5 quadruples
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(5,5) = 1 quintuple
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;لكل تذكرة، أثناء التحضير، نزيد عداد تكرار لكل من تلك المجموعات الجزئية. فللتذكرة &lt;code&gt;1 2 3 4 5&lt;/code&gt; تُرفع عداداتُ الأزواج العشرة &lt;code&gt;(1,2) (1,3) (1,4) (1,5) (2,3) (2,4) (2,5) (3,4) (3,5) (4,5)&lt;/code&gt; — وكذلك ثلاثياتها ورباعياتها وخماسيتها الكاملة.&lt;/p&gt;
&lt;p&gt;بعد التحضير، حين يصل السحب، لا نلمس قائمة اللاعبين إطلاقًا. نولّد أزواج &lt;em&gt;السحب نفسه&lt;/em&gt; العشرة وثلاثياته العشر ورباعياته الخمس وخماسيته الواحدة، ونقرأ كلًا منها من الجداول المحسوبة مسبقًا. تلك &lt;strong&gt;26 قراءة&lt;/strong&gt; فقط، فتعقيد كل سحب &lt;strong&gt;O(1)&lt;/strong&gt; — لم يعد يعتمد على عدد اللاعبين.&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;وضع المسح يسأل: «أي اللاعبين يطابق هذا السحب؟» ووضع الفهرسة يسأل: «كم تذكرة تحتوي أصلًا كل مجموعة جزئية من هذا السحب؟»&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="dense-arrays-instead-of-hash-maps"&gt;مصفوفات كثيفة بدل الـ Hash Maps&lt;/h2&gt;
&lt;p&gt;قد يمد التنفيذ المباشر يده إلى hash maps. لكن الكون هنا صغير وثابت — الأرقام دائمًا من 1 إلى 90 — فعدد التوليفات الممكنة منتهٍ وقابل للتوقع وصغير بما يكفي للتخزين الكثيف:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,2) = 4,005
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,3) = 117,480
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,4) = 2,555,190
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;C(90,5) = 43,949,268
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;With 32-bit counters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;005&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 16 KB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;480&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 470 KB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;555&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;190&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 10 MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;count5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;949&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;268&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;× 4 B ≈ 176 MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;───────────────────────────────────&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;total index memory ≈ 186 MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;هذا ثمن معقول لاستعلامات ثابتة الزمن، والمصفوفات الكثيفة تتفادى الـ hashing وعبء الدلاء ومطاردة المؤشرات والتخصيص وضغط جامع المهملات. واللغز الوحيد المتبقي هو تحويل كل توليفة إلى فهرس مصفوفة فريد.&lt;/p&gt;
&lt;h2 id="ranking-combinations-with-combinadics"&gt;ترتيب التوليفات بنظام Combinadics&lt;/h2&gt;
&lt;p&gt;كل توليفة تحتاج رتبة حتمية. حوّل الأرقام إلى أساس صفري أولًا (&lt;code&gt;1..90 → 0..89&lt;/code&gt;)، ثم لتوليفة مفروزة &lt;code&gt;c0 &amp;lt; c1 &amp;lt; … &amp;lt; c(k-1)&lt;/code&gt; احسب:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rank = C(c0,1) + C(c1,2) + ... + C(c(k-1),k)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;For a 5-number combination:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rank = C(c0,1) + C(c1,2) + C(c2,3) + C(c3,4) + C(c4,5)Example — combination 1 2 3 4 5, zero-based 0 1 2 3 4:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rank = C(0,1) + C(1,2) + C(2,3) + C(3,4) + C(4,5) → a unique index into count5[]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;تصبح الرتبة فهرس المصفوفة، فتكتب &lt;code&gt;count5[rank]++&lt;/code&gt;. لا maps، ولا مفاتيح متسلسلة، ولا تخصيص heap لكل توليفة — مجرد زيادات عدادات مباشرة. والمعاملات الثنائية نفسها محسوبة مسبقًا مرة واحدة بقاعدة Pascal، فلا يجري الترتيب أبدًا حسابًا أثقل من بضع قراءات مصفوفة وجمع:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;binomial&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;uint64&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initBinomialTable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="c1"&gt;// rank of a sorted, zero-based quintuple c0&amp;lt;c1&amp;lt;c2&amp;lt;c3&amp;lt;c4:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c2&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c3&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;binomial&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;c4&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// lands in [0, C(90,5))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;لفضاء توليفات ثابت صغير، هذه بالضبط التحويلة التي تنقل حلًا عام الغرض إلى حل عالي الأداء.&lt;/p&gt;
&lt;h2 id="the-subtle-part-exact-match-counts"&gt;الجزء الدقيق: أعداد التطابق الدقيق&lt;/h2&gt;
&lt;p&gt;في النهج المفهرس فخ واحد مهم — العلة التي لدغتني في التكرار 3. الأعداد الخام للمجموعات الجزئية &lt;em&gt;ليست&lt;/em&gt; الإجابة النهائية. فالتذكرة المطابقة للأرقام الخمسة كلها تحتوي 10 أزواج و10 ثلاثيات و5 رباعيات وخماسية واحدة، فجمع مجاميع الأزواج والثلاثيات والرباعيات بسذاجة يعدّها مرات كثيرة. والتذكرة رباعية التطابق تساهم بالمثل بـ 6 أزواج و4 ثلاثيات ورباعية واحدة.&lt;/p&gt;
&lt;p&gt;الجداول المحسوبة مسبقًا تجيب عن «كم تذكرة &lt;em&gt;تحتوي&lt;/em&gt; هذا الزوج/الثلاثي/الرباعي؟». لكن العمل يريد «كم تذكرة فيها &lt;em&gt;بالضبط&lt;/em&gt; 2 أو 3 أو 4 أو 5 تطابقات؟». إنهما سؤالان مختلفان، والخلط بينهما هو أشيع علة في الحلول المفهرسة بالتوليفات.&lt;/p&gt;
&lt;h2 id="from-subset-totals-to-exact-counts"&gt;من مجاميع المجموعات الجزئية إلى الأعداد الدقيقة&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-لماذا-التصحيح-بهذه-المعاملات-تحديدا-التطابقات-الأعلى-مطوية-داخل-كل-مجموع-جزئي-أدنى-والإدراج-والاستبعاد-ينزعها-عائدا-من-العدد-الأدق-m5-نزولا-إلى-الأعم-m2"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="مخطط تصحيح الإدراج والاستبعاد وهو ينزع مساهمات التطابقات الأعلى من كل مجموع جزئي، من m5 نزولًا إلى m2"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_1e9d5afad9572cd0.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_bde308187c5f6ba3.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_b5983705433853f5.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_1e9d5afad9572cd0.webp"
width="760"
height="608"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/from-subset-totals-to-exact-counts_hu_fc01840f29a10529.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
لماذا التصحيح بهذه المعاملات تحديدًا: التطابقات الأعلى مطوية داخل كل مجموع جزئي أدنى، والإدراج والاستبعاد ينزعها عائدًا من العدد الأدق (m5) نزولًا إلى الأعم (m2).
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;لتكن &lt;code&gt;T2, T3, T4, T5&lt;/code&gt; مجاميع المجموعات الجزئية للسحب، و&lt;code&gt;m2, m3, m4, m5&lt;/code&gt; أعداد التطابق الدقيق المطلوبة. بعدّ مساهمة كل فئة دقيقة في كل مجموع:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 2 matches: C(2,2)=1 pair
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 3 matches: C(3,2)=3 pairs, C(3,3)=1 triple
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 4 matches: C(4,2)=6 pairs, C(4,3)=4 triples, C(4,4)=1 quad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exactly 5 matches: C(5,2)=10 pairs, C(5,3)=10 triples, C(5,4)=5 quads, C(5,5)=1 quint
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;So the totals expand as:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T5 = m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T4 = m4 + 5·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T3 = m3 + 4·m4 + 10·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; T2 = m2 + 3·m3 + 6·m4 + 10·m5And we solve backward:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m5 = T5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m4 = T4 - 5·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m3 = T3 - 4·m4 - 10·m5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; m2 = T2 - 3·m3 - 6·m4 - 10·m5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;هذه خطوة التصحيح الجوهرية. بدونها يبلّغ الحل المفهرس أعدادًا متضخمة — إجابة خاطئة سريعة، وهي مجرد علة أغلى ثمنًا.&lt;/p&gt;
&lt;h2 id="why-the-query-is-constant-time"&gt;لماذا الاستعلام ثابت الزمن؟&lt;/h2&gt;
&lt;p&gt;
&lt;figure id="figure-استعلام-السحب-من-البداية-للنهاية-افرز-وتحقق-وولد-الرتب-ونفذ-26-قراءة-وصحح-بالإدراج-والاستبعاد-ثم-اطبع-عدد-العمليات-لا-ينمو-مع-عدد-اللاعبين-أبدا"&gt;
&lt;div class="flex justify-center "&gt;
&lt;div class="w-full" &gt;
&lt;img alt="استعلام السحب من طرف إلى طرف: فرز وتحقق، وتوليد الرتب، و26 قراءة، وتصحيح الإدراج والاستبعاد، ثم الطباعة — عمل ثابت مهما بلغ عدد اللاعبين"
srcset="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_a7060ed37fcff872.webp 320w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_a0ba60c589af1bff.webp 480w, https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_5fdd10b87b2d9dd2.webp 760w"
sizes="(max-width: 480px) 100vw, (max-width: 768px) 90vw, (max-width: 1024px) 80vw, 760px"
src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_a7060ed37fcff872.webp"
width="760"
height="507"
loading="lazy" data-zoomable data-zoom-src="https://akemara.com/en/blog/ai-assisted-performance-engineering/images/webp/why-the-query-is-constant-time_hu_cd6d74cb481eda96.webp" /&gt;&lt;/div&gt;
&lt;/div&gt;&lt;figcaption&gt;
استعلام السحب من البداية للنهاية: افرز وتحقق، وولّد الرتب، ونفّذ 26 قراءة، وصحّح بالإدراج والاستبعاد، ثم اطبع. عدد العمليات لا ينمو مع عدد اللاعبين أبدًا.
&lt;/figcaption&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;اجمع الأجزاء فيصبح مسار ما بعد السحب عملًا ثابتًا: ولّد 10 أزواج و10 ثلاثيات و5 رباعيات وخماسية واحدة؛ نفّذ 26 قراءة من مصفوفات كثيفة للحصول على T2–T5؛ طبّق تصحيح الإدراج والاستبعاد؛ اطبع. سواء كان اللاعبون 10 آلاف أو 10 ملايين أو 100 مليون، ينجز السحب كمية العمل نفسها. في الكود، التصحيح كله أربعة أسطر — وتتراكم مجاميع المجموعات الجزئية في &lt;code&gt;int64&lt;/code&gt; مع أن كل عداد مخزن &lt;code&gt;uint32&lt;/code&gt;، لأن مجموع المجاميع قد يتجاوز أي عداد منفرد:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// total2..total5 are int64 sums of the 26 looked-up counts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners5 = int(total5)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners4 = int(total4) - 5*winners5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners3 = int(total3) - 4*winners4 - 10*winners5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;winners2 = int(total2) - 3*winners3 - 6*winners4 - 10*winners5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="building-the-index-without-melting-the-cache"&gt;بناء الفهرس دون إذابة الـ Cache&lt;/h2&gt;
&lt;p&gt;هذا هو الجزء الذي لا تحذرك منه الرياضيات أبدًا. على الورق، التحضير تافه: لكل لاعب ارفع 26 عدادًا — عشرة ملايين لاعب، 260 مليون زيادة، انتهينا. فتكتب الحلقة المتوازية البديهية، وتقسّم اللاعبين على أربعة أنوية، وتدع كل نواة تزيد الجداول المشتركة… وإذا باستعلامك الجميل ذي الـ O(1) يقبع خلف بناء &lt;em&gt;يزحف&lt;/em&gt;، لأن أربعة أنوية تتزاحم على حفنة خطوط الـ cache نفسها.&lt;/p&gt;
&lt;p&gt;الجاني هو التنازع، وهو غير متساوٍ بشدة عبر الجداول الأربعة. جدول الأزواج فيه 4,005 خانات فقط لكنه يمتص نحو 100 مليون زيادة، فتشتعل بضعة خطوط cache بياضًا — والجمع الذرّي هناك سيجعل البناء كله يقف في طابور عليها. وجدول الخماسيات على النقيض: 44 مليون خانة تتقاسم نحو 10 ملايين زيادة فقط، فالاصطدامات نادرة. استراتيجية واحدة لا يمكن أن تصح للاثنين، والكود الفعلي ينقسم تبعًا لذلك:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// SMALL + HOT (pairs: 4,005 slots, ~100M hits; triples: 117,480 slots):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// each worker counts into a PRIVATE copy, merged by index shard afterward.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;local.pairs[binomial[a][1]+binomial[b][2]]++
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// LARGE + SPARSE (quads: 2.5M slots; quintuples: 44M slots):
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// atomic adds straight into the shared table — collisions are rare, and
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// cloning 176 MB per worker would be absurd.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;atomic.AddUint32(&amp;amp;quintupleCounts[rank], 1)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;فيحصل الجدولان الصغيران على نسخ خاصة لكل عامل (صفر تنازع أثناء العد، ثم دمج سريع مقسّم بنطاق الفهارس)، بينما يأخذ الجدولان الكبيران زيادات ذرّية مباشرة في الذاكرة المشتركة (لا تكرار 10 ميغابايت و176 ميغابايت لكل نواة، ولا جولة دمج إطلاقًا). والمكسب: بناء تبقى ذروة ذاكرته مستوية تقريبًا مع إضافة الأنوية، ينجز عملًا O(N) حقيقيًا بمدى O(N/p) — دون إعادة مسح مجموعة البيانات مرة لكل عامل.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;الدرس المختبئ في البناء&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;قالت الخوارزمية: «ارفع 26 عدادًا». وقال العتاد: «شاهد أربعة أنوية تتقاتل على خط cache واحد». اختيار «تجميع خاص» أو «ذرّي» لكل جدول، حسب حجمه ومعدل اصطداماته، هو بالضبط نوع القرارات التي تبادلت فيها الملاحظات مع الذكاء الاصطناعي — وبالضبط النوع الذي لا ينجو أبدًا إلى الملخص النهائي الأنيق. صحة التزامن رخيصة القول باهظة الكسب.&lt;/p&gt;
&lt;h2 id="why-the-indexed-solution-is-not-new-mathematics"&gt;لماذا الحل المفهرس ليس رياضيات جديدة&lt;/h2&gt;
&lt;p&gt;يجدر التأكيد — من باب الصدق ولأنه الدرس الأكثر قابلية لإعادة الاستخدام هنا — أن الحل المفهرس لا يبتكر شيئًا. إنه تركيب &lt;strong&gt;لأربع نتائج كلاسيكية&lt;/strong&gt; ليست أي منها أصيلة فيه — وتمتد أكثر من قرن.&lt;/p&gt;
&lt;h2 id="1-the-addressing-layer-the-combinatorial-number-system-combinadics"&gt;1 · طبقة العنونة — نظام الأعداد التوافقي (Combinadics)&lt;/h2&gt;
&lt;p&gt;تمثيل كل توليفة k مفروزة بـ &lt;code&gt;C(c₁,1) + C(c₂,2) + … + C(c_k,k)&lt;/code&gt; ينشئ تقابلًا على &lt;code&gt;{0, …, C(n,k)−1}&lt;/code&gt;. لاحظ الفكرة &lt;strong&gt;D. H. Lehmer ‏(1964)&lt;/strong&gt;، وسماها لاحقًا &lt;strong&gt;Donald Knuth&lt;/strong&gt; بـ&lt;em&gt;نظام الأعداد التوافقي&lt;/em&gt; في &lt;em&gt;The Art of Computer Programming&lt;/em&gt; المجلد 4A ‏(2011)، وهو يرجع أصل المفهوم إلى &lt;strong&gt;Ernesto Pascal ‏(1887)&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="2-the-arithmetic-binomial-coefficients"&gt;2 · الحساب — المعاملات الثنائية&lt;/h2&gt;
&lt;p&gt;تحت الترتيب لا يوجد أغرب من &lt;code&gt;C(n,k) = C(n−1,k−1) + C(n−1,k)&lt;/code&gt;، المنسوبة عادة إلى &lt;strong&gt;Blaise Pascal ‏(1654)&lt;/strong&gt; وإن عُرفت أقدم من ذلك بكثير في الهند وفارس والصين. وحساب هذا المثلث مسبقًا هو ما يجعل الترتيب سريعًا وخاليًا من التخصيص.&lt;/p&gt;
&lt;h2 id="3-the-counting-layer-itemset-support-counting"&gt;3 · طبقة العد — عد دعم مجموعات العناصر&lt;/h2&gt;
&lt;p&gt;زيادة عداد لكل مجموعة جزئية تحتويها كل تسجيلة هي في جوهرها &lt;em&gt;عد دعم مجموعات العناصر&lt;/em&gt; (Itemset Support Counting)، الفكرة الجوهرية خلف خوارزمية &lt;strong&gt;Apriori&lt;/strong&gt; التي قدمها &lt;strong&gt;Rakesh Agrawal وRamakrishnan Srikant ‏(1994)&lt;/strong&gt;. «تذاكرنا» هي المعاملات؛ و«توليفاتنا» هي مجموعات العناصر.&lt;/p&gt;
&lt;h2 id="4-the-recovery-layer-inclusionexclusion-exactly-k"&gt;4 · طبقة الاسترجاع — الإدراج والاستبعاد (بالضبط k)&lt;/h2&gt;
&lt;p&gt;استرجاع أعداد الفائزين الدقيقة من مجاميع المجموعات الجزئية يستخدم صيغة «بالضبط k» لمبدأ الإدراج والاستبعاد:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;m_j = Σ_{k≥j} (−1)^{k−j} · C(k,j) · T_k
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;تُنسب هذه إلى &lt;strong&gt;Charles Jordan&lt;/strong&gt;، وعُممت لاحقًا عبر &lt;strong&gt;صيغة Schuette–Nesbitt&lt;/strong&gt;، وكلها تقوم على مبدأ الإدراج والاستبعاد الكلاسيكي.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;المساهمة الفعلية&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;الحركة الأصيلة الوحيدة في الحل هي &lt;em&gt;ملاحظة أن هذه القطع الأربع تتراكب معًا&lt;/em&gt; بطريقة تجعل كلفة الاستعلام مستقلة عن حجم البيانات. هذه هندسة لا رياضيات جديدة — وتمييز مثل هذه التراكيب هو بالضبط حيث يستحق المتعاون الاصطناعي أجره، شرط أن تمده أنت بالقيود التي تجبر القطع على التموضع.&lt;/p&gt;
&lt;h2 id="loading-and-parsing-efficiently"&gt;التحميل والتحليل بكفاءة&lt;/h2&gt;
&lt;p&gt;لمجموعة بيانات من 10 ملايين تذكرة، قد يهيمن التحليل على وقت الإقلاع. المدخلات نص ASCII، تذكرة في كل سطر (&lt;code&gt;4 16 22 35 78&lt;/code&gt;، &lt;code&gt;1 2 3 4 5&lt;/code&gt;، …). المحلل الساذج الذي يستخدم تقطيع النصوص عالي المستوى وتحويل الأعداد سطرًا سطرًا جيد للملفات الصغيرة لكنه يخلق عبئًا حقيقيًا على هذا النطاق: نصوص مؤقتة، وتخصيص متكرر، وتحويلات مكلفة، وضغط على جامع المهملات.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;مثّل الملف في الذاكرة (&lt;code&gt;mmap&lt;/code&gt;).&lt;/strong&gt; بدل نسخ الـ ~145 ميغابايت إلى شريحة يملكها الـ heap كما تفعل &lt;code&gt;os.ReadFile&lt;/code&gt;، يُمثَّل الملف للقراءة فقط: يجلب نظام التشغيل الصفحات مباشرة من cache خاصته، بالتوازي، كلما لمسها العمال — ضغط أقل على الـ heap وتعاون مجاني مع cache الصفحات (مع مسار قراءة عادي احتياطي حين يتعذر التمثيل). ولأن التحليل هو الكلفة المهيمنة، ينخفض وقت التحميل الفعلي انخفاضًا شبه خطي مع عدد الأنوية.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;قسّم العمل على حدود الأسطر.&lt;/strong&gt; يمكن للتحليل أن يجري بالتوازي، لكن يجب ألا تشطر القطعُ سطرَ تذكرة أبدًا. لذا يُقسّم الملف إلى نطاقات بايتات ويُدفع كل حد إلى السطر الجديد التالي، فيضمن كل عامل تحليل أسطر كاملة والكتابة في مخزن مخرجاته الخاص بلا كتابات مشتركة:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;File bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────────────────────────────────────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ line line line line line line line line line line line line │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────────────────────────────────────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ┌────────────┐ ┌────────────┐ ┌────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ Worker 0 │ │ Worker 1 │ │ Worker 2 │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ whole lines │ │ whole lines │ │ whole lines │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └────────────┘ └────────────┘ └────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;يصدر كل عامل مخزنه المرصوص الخاص؛ ثم يمنح مجموعٌ تراكمي على أحجام القطع كلَّ قطعة إزاحةً منفصلة في الشريحة النهائية، فتُخاط المخازن معًا بنسخ متوازٍ دون أن يتداخل عامل مع آخر أبدًا.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ثم اجعل الحلقة الداخلية شرسة.&lt;/strong&gt; حيلتان صغيرتان تبقيان تحليل البايتات مشدودًا: طرح واحد بلا إشارة يقرر هل البايت رقم (إذا التفّ &lt;code&gt;ch-'0'&lt;/code&gt; فوق 9 فليس رقمًا)، والقيمة الجارية تُحد &lt;em&gt;أثناء&lt;/em&gt; تراكمها، فلا يستطيع رمز طويل عبثي أن يفيض على &lt;code&gt;int&lt;/code&gt; ويلتف عائدًا إلى النطاق الصحيح — يُقرأ كاملًا ثم يُرفض كخارج نطاق:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if ch-&amp;#39;0&amp;#39; &amp;gt; 9 { // unsigned wrap-around: &amp;#34;is this a digit?&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; valid = false
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;if value &amp;lt;= maxNumber { // clamp WHILE accumulating: no overflow, ever
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; value = value*10 + int(digit)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;} // 99999999999999999999 is read, then rejected
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ومخزن المخرجات مُحجّم إلى حد أعلى مثبت الأمان — فالتذكرة الصحيحة تستهلك تسعة بايتات مدخلات على الأقل وتصدر خمسة — فيُلحق دون أي خطر تجاوز.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;تحقق أثناء التحليل.&lt;/strong&gt; التذكرة الصحيحة فيها بالضبط خمسة أرقام مختلفة في 1..90 — فحص رخيص. لكن &lt;em&gt;سياسة&lt;/em&gt; التحقق قرار منتج لا قرار أداء. &lt;em&gt;الوضع الصارم&lt;/em&gt; يرفض المدخلات كلها إذا تشوه أي سطر (الصواب حين يكون كل سطر معاملة حقيقية)؛ و&lt;em&gt;الوضع المتساهل&lt;/em&gt; يتخطى الأسطر الفاسدة ويبلّغ عددها (مفيد للاستيعاب بأفضل جهد). اجعل الخيار صريحًا — فقدان البيانات الصامت ليس تحسينًا.&lt;/p&gt;
&lt;h2 id="benchmarks-interpretation"&gt;القياسات والتفسير&lt;/h2&gt;
&lt;p&gt;قست كلا التنفيذين على بيئة مقيّدة عمدًا:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;CPU&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;logical CPUs used by the Go runtime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;RAM&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Go&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go1.23.2 linux/amd64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Setting&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GOMAXPROCS=4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Draws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;| 10 20 30 40 50 | 5 15 25 35 45&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;time-to-READY, per-draw latency, max RSS (/usr/bin/time -v)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;كلمة عن طريقة تشغيل البرنامج، لأنها تشكّل معنى هذه الأرقام. يحمّل الملف، يطبع &lt;code&gt;READY&lt;/code&gt;، ثم يقرأ السحوبات من stdin ويجيب عن كل منها على stdout مع تدفق فوري لكل سحب حتى يصل كل ناتج إلى المستهلك مباشرة. تذهب التشخيصات إلى stderr ليبقى مجرى الإجابات نظيفًا. والأهم أن توقيت كل سحب يحيط &lt;em&gt;بالحساب فقط&lt;/em&gt; — لا تحليل السطر ولا الطباعة — فزمن السحب يعكس الحسبة ذاتها مستقلة عن الإدخال/الإخراج.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;مليون لاعب:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MODE TIME→READY DRAW1 DRAW2 DRAW3 AVG DRAW MAX RSS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;──────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Scan 23.69 ms 0.884 ms 0.882 ms 0.915 ms 0.894 ms 40.5 MiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Indexed 177.77 ms 1.472 µs 0.650 µs 0.628 µs 0.917 µs 202.9 MiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;عند مليون لاعب، وضع المسح سريع كفاية بالفعل — لكن وضع الفهرسة يجيب أسرع بنحو &lt;strong&gt;ثلاث مراتب عشرية لكل سحب&lt;/strong&gt;، مقابل وقت أطول قبل READY وذاكرة أكبر.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;10 ملايين لاعب:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MODE TIME→READY DRAW1 DRAW2 DRAW3 AVG DRAW MAX RSS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;──────────────────────────────────────────────────────────────────────────────
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Scan 210.64 ms 7.978 ms 15.017 ms 13.687 ms 12.227 ms 255.9 MiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Indexed 1.220 s 2.649 µs 1.044 µs 0.791 µs 1.495 µs 301.5 MiB
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;عند 10 ملايين لاعب، ما زال وضع المسح يؤدي جيدًا لأنه يتدفق عبر ذاكرة متجاورة مضغوطة على أربعة أنوية — متوسط نحو &lt;strong&gt;12.2 ميلي ثانية&lt;/strong&gt; محترم جدًا. لكن وضع الفهرسة في طبقة أخرى: يجيب في نحو &lt;strong&gt;1.5 ميكروثانية&lt;/strong&gt; بعد بناء الفهرس. الفرق ببساطة: المسح يلمس &lt;em&gt;كل تذكرة&lt;/em&gt; بعد السحب؛ والفهرسة تلمس &lt;em&gt;26 عدادًا&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="putting-the-gap-on-a-bar"&gt;وضع الفجوة على شريط&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10 million players · average draw latency
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Scan mode 12.227 ms ██████████████████████████████████████████████████
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Indexed mode 0.0015 ms ▏ratio ≈ 12.227 ms / 0.001495 ms ≈ 8,178×
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;فكان وضع الفهرسة أسرع بنحو &lt;strong&gt;8,000 مرة لكل سحب&lt;/strong&gt; هنا. هذا لا يجعل وضع المسح رديئًا — بل يعني أن الوضعين يحسّنان جزأين مختلفين من النظام.&lt;/p&gt;
&lt;h2 id="startup-vs-query-latency-the-break-even"&gt;الإقلاع مقابل زمن الاستعلام (نقطة التعادل)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;million players&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;before READY ~211 ms | per draw ~12 ms&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;before READY ~1.22 s | per draw ~1.5 µs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;extra startup cost of indexed = 1.220 s − 0.211 s ≈ 1.009 s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;per-draw saving = 12.227 ms − 0.0015 ms ≈ 12.2255 ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;break-even = 1.009 s / 12.2255 ms ≈ 83 draws
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;إن كان همك الوحيد إجمالي وقت التشغيل على هذا الجهاز وهذه البيانات، يسدد وضع الفهرسة كلفة تحضيره الإضافية بعد نحو &lt;strong&gt;83 سحبًا&lt;/strong&gt;. لكن إجمالي وقت التشغيل كثيرًا ما يكون المقياس الخطأ: إذا كان المطلوب &lt;em&gt;أدنى زمن استجابة بعد السحب&lt;/em&gt;، فوضع الفهرسة مبرر حتى لسحب واحد، لأنه ينقل العمل خارج المسار الحرج.&lt;/p&gt;
&lt;p&gt;عن الذاكرة: بلغ أقصى RSS مقاس نحو 256 ميبيبايت (مسح) و302 ميبيبايت (فهرسة). الجداول النظرية نحو 186 ميغابايت زائد نحو 50 ميغابايت تذاكر مرصوصة؛ والـ RSS المقاس يشمل أيضًا عبء وقت التشغيل والصفحات الممثلة، فلن يتطابق تمامًا. لمسة صغيرة مقصودة: بعد بناء الجداول المفهرسة يسقط البرنامج مخزن التذاكر البالغ نحو 50 ميغابايت (&lt;code&gt;packedTickets = nil&lt;/code&gt;) ويفرض دورة جمع مهملات، لأن سحوبات الفهرسة تقرأ الجداول وحدها — فتبقى ذاكرة الحالة المستقرة أقرب إلى الجداول نفسها. وكلا الوضعين يتسع بارتياح في 4 غيغابايت.&lt;/p&gt;
&lt;h2 id="comparing-the-two-approaches"&gt;مقارنة النهجين&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DIMENSION&lt;/span&gt; &lt;span class="n"&gt;SCAN&lt;/span&gt; &lt;span class="n"&gt;MODE&lt;/span&gt; &lt;span class="n"&gt;INDEXED&lt;/span&gt; &lt;span class="n"&gt;MODE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;────────────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Preprocessing&lt;/span&gt; &lt;span class="n"&gt;minimal&lt;/span&gt; &lt;span class="n"&gt;heavier&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Per&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="n"&gt;complexity&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Memory&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="n"&gt;lower&lt;/span&gt; &lt;span class="n"&gt;higher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Implementation&lt;/span&gt; &lt;span class="n"&gt;complexity&lt;/span&gt; &lt;span class="n"&gt;lower&lt;/span&gt; &lt;span class="n"&gt;higher&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Scaling&lt;/span&gt; &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;players&lt;/span&gt; &lt;span class="n"&gt;linear&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="n"&gt;constant&lt;/span&gt; &lt;span class="n"&gt;per&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Debuggability&lt;/span&gt; &lt;span class="n"&gt;very&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="n"&gt;requires&lt;/span&gt; &lt;span class="n"&gt;stronger&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Best&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;baseline&lt;/span&gt; &lt;span class="n"&gt;lowest&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;اختر &lt;strong&gt;وضع المسح&lt;/strong&gt; حين تكون الذاكرة شحيحة، أو البيانات متواضعة، أو السحوبات نادرة، أو حين تريد خط أساس للصحة. واختر &lt;strong&gt;وضع الفهرسة&lt;/strong&gt; حين يكون زمن ما بعد السحب حرجًا، وقد تكثر الاستعلامات، وقد ينمو عدد اللاعبين، وتتوفر ذاكرة كافية. وضع الفهرسة ليس «أفضل» في كل بعد — بل في هدف واحد هو تقليل زمن المسار الحرج. تلك هي المقايضة الهندسية.&lt;/p&gt;
&lt;h2 id="performance-lessons-from-this-problem"&gt;دروس أداء من هذه المشكلة&lt;/h2&gt;
&lt;p&gt;مشكلة اليانصيب المجري صغيرة، لكنها تجسّد عدة مبادئ تنطبق على كثير من الأنظمة الثقيلة الأداء.&lt;/p&gt;
&lt;h2 id="1-optimize-the-critical-path"&gt;1. حسّن المسار الحرج&lt;/h2&gt;
&lt;p&gt;المسار الحرج هو العمل الذي يجب أن يحدث بينما أحدهم ينتظر.&lt;/p&gt;
&lt;p&gt;هنا، هو تقرير ما بعد السحب.&lt;/p&gt;
&lt;p&gt;الفهرسة قيّمة لأنها تنقل العمل من المسار الحرج إلى مرحلة التحضير.&lt;/p&gt;
&lt;p&gt;يظهر هذا النمط في كل مكان:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;محركات البحث تبني الفهارس المقلوبة قبل الاستعلامات&lt;/li&gt;
&lt;li&gt;قواعد البيانات تصون الفهارس قبل القراءات&lt;/li&gt;
&lt;li&gt;المترجمات تبني التمثيلات الوسيطة قبل التحسين&lt;/li&gt;
&lt;li&gt;أنظمة التوصيات تحسب السمات مسبقًا قبل التقديم&lt;/li&gt;
&lt;li&gt;أنظمة التحليلات تجمّع البيانات قبل استعلامات اللوحات&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;الحساب المسبق ليس تفصيل تنفيذ.&lt;/p&gt;
&lt;p&gt;إنه استراتيجية زمن استجابة.&lt;/p&gt;
&lt;h2 id="2-data-layout-matters"&gt;2. تخطيط البيانات مهم&lt;/h2&gt;
&lt;p&gt;حل المسح سريع لأن تخطيط بياناته بسيط ومضغوط.&lt;/p&gt;
&lt;p&gt;مصفوفة البايتات المسطحة ليست فاتنة، لكنها تعمل جيدًا.&lt;/p&gt;
&lt;p&gt;كثير من مشاكل الأداء لا يحلها مزيد من التجريد. بل تحلها إخفاقات cache أقل.&lt;/p&gt;
&lt;p&gt;قبل تحسين الحساب، انظر إلى أنماط الوصول للذاكرة.&lt;/p&gt;
&lt;h2 id="3-avoid-allocations-in-hot-paths"&gt;3. تجنّب التخصيص في المسارات الساخنة&lt;/h2&gt;
&lt;p&gt;مسار السحب يجب ألا يخصص ذاكرة لكل تذكرة.&lt;/p&gt;
&lt;p&gt;ومسار الاستعلام المفهرس يجب ألا يخصص لكل توليفة.&lt;/p&gt;
&lt;p&gt;والمحلل يجب أن يتجنب النصوص المؤقتة ما أمكن.&lt;/p&gt;
&lt;p&gt;التخصيص ليس مكلفًا دائمًا، لكن تكراره في المسارات الساخنة يخلق تكاليف ثانوية:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;عبء المخصص&lt;/li&gt;
&lt;li&gt;جمع المهملات&lt;/li&gt;
&lt;li&gt;تشظي الذاكرة&lt;/li&gt;
&lt;li&gt;محلية أضعف&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="4-measure-phases-separately"&gt;4. قِس المراحل منفصلة&lt;/h2&gt;
&lt;p&gt;رقم «إجمالي وقت التشغيل» وحده قد يضلل.&lt;/p&gt;
&lt;p&gt;في هذه المشكلة، قد يبدأ وضع المسح أسرع، بينما يجيب وضع الفهرسة أسرع.&lt;/p&gt;
&lt;p&gt;لا يحكي أي من الرقمين وحده القصة كاملة.&lt;/p&gt;
&lt;p&gt;الأنظمة الثقيلة الأداء لها عادة عدة أزمنة مهمة:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;الإقلاع البارد&lt;/li&gt;
&lt;li&gt;الاستعلام الدافئ&lt;/li&gt;
&lt;li&gt;زمن p50&lt;/li&gt;
&lt;li&gt;زمن p99&lt;/li&gt;
&lt;li&gt;ذروة الذاكرة&lt;/li&gt;
&lt;li&gt;الإنتاجية&lt;/li&gt;
&lt;li&gt;سلوك الذيل تحت التزامن&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;قِس ما يهم.&lt;/p&gt;
&lt;h2 id="5-keep-a-simple-implementation-around"&gt;5. أبقِ تنفيذًا بسيطًا في الجوار&lt;/h2&gt;
&lt;p&gt;تنفيذ المسح مفيد حتى حين يكون وضع الفهرسة هو الافتراضي.&lt;/p&gt;
&lt;p&gt;يساعد على التحقق من الصحة.&lt;/p&gt;
&lt;p&gt;ويوفر مسارًا احتياطيًا.&lt;/p&gt;
&lt;p&gt;ويمنح مهندسي المستقبل نموذجًا ذهنيًا أبسط.&lt;/p&gt;
&lt;p&gt;في الأنظمة عالية الأداء يميل التعقيد إلى التراكم. وخط الأساس الواضح يبقي المشروع على الأرض.&lt;/p&gt;
&lt;h2 id="6-exact-semantics-matter"&gt;6. الدلالات الدقيقة مهمة&lt;/h2&gt;
&lt;p&gt;أكبر مخاطر الحل المفهرس ليست الأداء.&lt;/p&gt;
&lt;p&gt;بل الدلالات.&lt;/p&gt;
&lt;p&gt;أعداد المجموعات الجزئية ليست أعداد التطابق الدقيق.&lt;/p&gt;
&lt;p&gt;فرق يسهل تفويته، ويغيّر الإجابة تمامًا.&lt;/p&gt;
&lt;p&gt;عمل الأداء يجب ألا يطمس معنى النتيجة أبدًا.&lt;/p&gt;
&lt;p&gt;الإجابة الخاطئة السريعة مجرد علة أغلى ثمنًا.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;أفكار ختامية&lt;/h2&gt;
&lt;p&gt;تبدأ هذه المسألة تمرين عدّ في يانصيب، لكنها تتحول سريعًا إلى مسألة هندسة أداء.&lt;/p&gt;
&lt;p&gt;الدرس الجوهري لا يخص اليانصيب.&lt;/p&gt;
&lt;p&gt;الدرس الأوسع هو:&lt;/p&gt;
&lt;blockquote class="border-l-4 border-neutral-300 dark:border-neutral-600 pl-4 italic text-neutral-600 dark:text-neutral-400 my-6"&gt;
&lt;p&gt;&lt;em&gt;انقل العمل خارج المسار الحرج متى سمحت المشكلة بذلك.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;يُري حل المسح إلى أي مدى يمكن أن يذهب تخطيط بيانات متقن وحلقة بسيطة صديقة للمعالج.&lt;/p&gt;
&lt;p&gt;ويُري الحل المفهرس كيف يستطيع التحضير المسبق تغيير نموذج التشغيل كليًا.&lt;/p&gt;
&lt;p&gt;على بيئة بأربعة معالجات و4 غيغابايت ذاكرة، عالج وضع المسح 10 ملايين لاعب بمتوسط زمن سحب نحو &lt;strong&gt;12 ميلي ثانية&lt;/strong&gt;. وهذا سريع جدًا بالفعل.&lt;/p&gt;
&lt;p&gt;وعالج وضع الفهرسة الملايين العشرة نفسها بمتوسط زمن سحب نحو &lt;strong&gt;1.5 ميكروثانية&lt;/strong&gt; بعد التحضير.&lt;/p&gt;
&lt;h2 id="the-bottom-line"&gt;خلاصة القول&lt;/h2&gt;
&lt;p&gt;في نهاية المطاف، نادرًا ما تسلّمنا هندسة البرمجيات مسارًا «صحيحًا» واحدًا — بل مقايضات فقط. كلا الحلين قيّم للغاية؛ إنهما يحسّنان بُعدين مختلفين تمامًا من أبعاد الأداء.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;أحيانًا يكون الجواب الصحيح تقشفيًا:&lt;/strong&gt; مصفوفة مسطحة مع حلقة مشدودة صديقة للـ cache من أجل السرعة الخام.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;وأحيانًا يكون الجواب الصحيح بنيويًا:&lt;/strong&gt; فهرس محسوب مسبقًا يقصف أزمنة البحث كلما تعاظم التعقيد.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;أصمد المعماريات لا تعامل هذين النمطين كخصمين. إنها تدرك أن أفضل نظام كثيرًا ما لا يُجبر على الاختيار بينهما — بل يوظفهما معًا في انسجام.&lt;/p&gt;</description></item></channel></rss>