570,000 سطر من كود LLM تُجمَّع بلا أخطاء. لكنه كان أبطأ من SQLite بـ 20,171 مرة.
قام أحدهم بقياس أداء إعادة كتابة SQLite بلغة Rust باستخدام LLM. الهوة بين كود يبدو صحيحاً وكود صحيح فعلاً بلغت خمسة أوامر من حيث الحجم.
إعادة كتابة SQLite بلغة Rust، نُفِّذت بالكامل بواسطة LLM، خضعت مؤخراً لاختبار أداء شامل. الكود تجمَّع بنجاح، واجتازت الاختبارات، وجاء الكود نظيفاً ومنظماً ومكتوباً بأسلوب Rust الصحيح. لكن على عملية بحث بسيطة بالمفتاح الأساسي، كان أبطأ من SQLite بمقدار 20,171 مرة.
هذا الرقم أوقفني. ليس لأن بطء الكود المُولَّد من LLM أمر مفاجئ، بل بسبب مصدر هذا البطء. الكود لم يكن خاطئاً بأي طريقة يمكن للمُجمِّع أو مجموعة الاختبارات اكتشافها. كانت B-tree مُنفَّذة بشكل صحيح. query planner موجود. محرك التخزين يعمل. كل قطعة قابلة للدفاع عنها بشكل مستقل. النظام ككل كان شبه غير قابل للاستخدام.
قضيت وقتاً في قراءة تحليل الأداء والكود المصدري. الأنماط التي وجدتها تتكرر باستمرار في المشاريع المُولَّدة بـ LLM، وأعتقد أنها تشير إلى شيء جوهري في طريقة كتابة هذه النماذج للكود.
شجرة B-tree كانت موجودة. الـ query planner تجاهلها.
في SQLite، البحث عن PRIMARY KEY يسلك مسار B-tree وينتهي في زمن O(log n). أربعة أسطر في where.c تفحص iPKey وتوجّه الاستعلام مباشرة إلى الشجرة. هذا من تلك التحسينات الدقيقة التي لا معنى لها إلا إن فهمت كيف يتشابك النظام ككل.
النسخة المُولَّدة بـ LLM كانت تمتلك تنفيذاً صحيحاً لـ B-tree أيضاً. يعمل بشكل صحيح في عزلة. المشكلة أن query planner لم يستدعِه أبداً في حالة البحث بالمفتاح الأساسي. دالة is_rowid_ref() لم تتعرف إلا على ثلاثة نصوص حرفية: “rowid” و”rowid” و”oid”. إذا أعلنت عموداً بصيغة id INTEGER PRIMARY KEY، فإن المخطط لم يتعرف عليه باعتباره اسماً مستعاراً للـ rowid. كل استعلام كان يقع في full table scan عوضاً عن ذلك.
الرياضيات هنا قاسية. لمئة صف يُستعلم عنها مئة مرة، مسار B-tree يأخذ نحو 700 خطوة مقارنة. مسار المسح الكامل يأخذ أكثر من 10,000. لكن الضرر الحقيقي يأتي من التعقيد الخوارزمي: O(log n) لكل بحث تصبح O(n)، وعبر مجموعة الاختبارات الكاملة، يتراكم ذلك ليُنتج الفجوة البالغة 20,171 مرة.
هذا النوع من الأخطاء لا تكتشفه أي اختبارات وحدة ما لم تكتب benchmark متعمداً. B-tree تعمل. المسح يعمل. المخطط يختار الخيار الخاطئ. كل شيء يجتاز الاختبارات.
الإعدادات الآمنة تتراكم كالفوائد المركبة
ما جعل هذه الحالة أكثر إثارة من مجرد خطأ في التوجيه هو ما تبقّى بعده. حتى بعد احتساب مشكلة query planner، كانت إعادة الكتابة لا تزال أبطأ بنحو 2,900 مرة. الفجوة المتبقية جاءت من طبقات قرارات منطقية بشكل فردي.
كل تنفيذ لاستعلام كان يستنسخ AST الكامل ويعيد تجميعه إلى bytecode. SQLite يُعيد استخدام مقابض prepared statement. كلا المقاربتين صالحتان، لكن استنساخ AST في كل تنفيذ مُكلف على نطاق واسع.
كل قراءة لصفحة كانت تخصص buffer جديداً بحجم 4KB على الـ heap. ذاكرة التخزين المؤقت لـ SQLite تُعيد مؤشراً مباشراً إلى الذاكرة المحملة مسبقاً. النسخة المُولَّدة بـ LLM اختارت المسار الآمن الواضح: خصص، اقرأ، أرجع. يعمل. لكنه أبطأ بأوامر كاملة حين تقرأ آلاف الصفحات في كل استعلام.
كل commit كان يُعيد بناء المخطط الكامل من الصفر. SQLite يقارن قيمة integer واحدة تُسمى cookie. إن لم تتغير، المخطط لا يزال صالحاً. إعادة الكتابة لم تمتلك هذا المفهوم، فكانت تُنجز العمل الكامل في كل مرة.
كل statement كانت تُشغِّل sync_all() لتفريغ كل metadata الملف إلى القرص. SQLite يستخدم fdatasync()، الذي يُفرِّغ بيانات الملف فقط ويتجاوز مزامنة الـ metadata. الفرق ضخم في أعباء الكتابة الثقيلة.
أُسمِّي هذا التأثير المركب للإعدادات الدفاعية. كل خيار يمتلك مبرراً معقولاً منفرداً. استنساخ AST يتجنب تعقيدات الملكية في Rust. تخصيص buffers جديدة يمنع أخطاء use-after-free. إعادة بناء المخطط تتجنب مشاكل التخزين المؤقت القديم. استدعاء sync_all() يوفر أقوى ضمان للمتانة.
لكن تكاليف الأداء تتضاعف، لا تتجمع. حين تتراكم أربع عقوبات بمقدار 10x، لا تحصل على بطء 40x، بل 10,000x. لا يفكر LLM في هذا التراكم لأنه يُولِّد كل دالة في عزلة نسبية. يُحسِّن محلياً ويدفع الثمن عالمياً.
اثنان وثمانون ألف سطر لاستبدال سطر cron واحد
مشروع آخر لنفس المطور، مُولَّد بـ LLM، أظهر النمط ذاته بطريقة مختلفة. المشكلة: ملفات البناء في مجلد target/ الخاص بـ Rust تستهلك مساحة القرص بمرور الوقت. حل LLM: daemon بلغة Rust يضم 82,000 سطر مع سبعة dashboards ومحرك تسجيل Bayesian يقرر أي ملفات بناء يُنظِّفها.
الحل القائم هو find ./target -type f -atime +30 -delete، سطر واحد في cron job. صفر تبعيات. أو cargo-sweep، أداة مجتمع رسمية موجودة مسبقاً وتتعامل مع حالات الحافة التي لا يعالجها daemon المُولَّد.
المشروع المُولَّد بـ LLM استقدم 192 تبعية. للمقارنة، ripgrep، أحد أكثر أدوات البحث تطوراً في نظام Rust البيئي، يستخدم 61.
هذا نمط أراه باستمرار: LLMs تبني ما طلبته، لا ما تحتاجه. إن طلبت “ابنِ نظاماً يدير ملفات بناء Rust بذكاء مع مراقبة وتسجيل”، تحصل على ذلك بالضبط. النموذج لا يمتلك آلية للتراجع والتساؤل عما إذا كانت المشكلة تستلزم نظاماً أصلاً. لا يعرف أن حجم مجلد target/ شكوى متكررة في مجتمع Rust لها حلول معروفة. لا يأخذ في الحسبان تكلفة صيانة 192 تبعية مقابل لا شيء.
الأبحاث تشير إلى الاتجاه ذاته
كنت أتساءل إن كان هذان المشروعان استثناءين، فراجعت الأبحاث الأوسع. ليسا كذلك.
أجرت METR تجربة عشوائية محكومة مع 16 مطوراً خبيراً في المصدر المفتوح. المجموعة التي استخدمت أدوات الذكاء الاصطناعي أنجزت المهام أبطأ بنسبة 19% مقارنة بالمجموعة الضابطة. ما لفت انتباهي: بعد انتهاء التجربة، اعتقدت مجموعة الذكاء الاصطناعي أنها كانت أسرع بنسبة 20%. التجربة الذاتية للإنتاجية كانت معكوسة تماماً للواقع المقاس.
حللت GitClear 210 مليون سطر كود ووجدت أن الكود المنسوخ واللصق تجاوز الكود المُعاد هيكلته للمرة الأولى. الاتجاه يرتبط مباشرة بتبني أدوات البرمجة بالذكاء الاصطناعي. الكود يُضاف بسرعة أكبر مما يُحسَّن.
تقرير Google DORA 2024 وجد أن زيادة تبني الذكاء الاصطناعي بنسبة 25% ارتبطت بانخفاض استقرار النشر بنسبة 7.2%. المزيد من الكود المُولَّد بالذكاء الاصطناعي يدخل الإنتاج، والمزيد من الحوادث تخرج منه.
معيار Mercury من NeurIPS 2024 أضاف مقاييس الكفاءة إلى معايير البرمجة القياسية. حين تقيس ليس فقط “هل يُنتج مخرجات صحيحة” بل “هل يُنتج مخرجات صحيحة دون إهدار موارد”، انخفضت معدلات النجاح إلى ما دون 50%.
لا يعني هذا أن LLMs عديمة الفائدة في البرمجة. أستخدمها باستمرار. لكنه يعني أن “يُجمَّع ويجتاز الاختبارات” معيار خطير في انخفاضه. الفجوة بين كود معقول وكود صحيح هي حيث تحدث الهندسة الحقيقية.
ما تتطلبه الحالة فعلاً من المطورين
المشكلة الجوهرية ليست أن LLMs تكتب كوداً سيئاً. إنها تكتب كوداً متماسكاً محلياً ومتفككاً عالمياً. كل دالة منطقية. النظام ليس كذلك. هذا نمط الفشل الذي تفوته الاختبارات التقليدية تماماً، لأن الاختبارات تتحقق من السلوك المحلي.
المطلوب تقييم يستهدف الفجوات. Benchmarks لا مجرد اختبارات. ميزانيات أداء في CI لا مجرد فحوصات صحة. مراجعة معمارية تسأل “لماذا توجد هذه الوحدة” قبل أن تفحص إن كانت تعمل. تدقيق في التبعيات يقارن تعقيد الحل بتعقيد المشكلة.
السؤال ليس “هل يبدو هذا الكود صحيحاً؟” بل “كيف نُثبت أنه صحيح؟” والإثبات يتطلب نوع التفكير على مستوى الأنظمة الذي تفتقره LLMs حالياً.
الفجوة بين ما طلبته وما يتطلبه الإنتاج هي حيث يعيش حكم المهندس. بدون قياس، توليد الكود ليس إلا توليد tokens.
انضم إلى النشرة الإخبارية
احصل على تحديثات حول أحدث مشاريعي ومقالاتي وتجاربي في الذكاء الاصطناعي وتطوير الويب.