NumPy 2.5: هل أصبحت searchsorted أسرع 25 مرة فعلاً؟ الشرط الذي لا يذكره العنوان
NumPy 2.5 يسرّع np.searchsorted حتى 25 ضعفاً، لكن بشرط واحد. نشرح ما تغيّر، ومتى تلمس الفرق في كودك، وكيف ترقّي مشروعك بأمان وتقيس النتيجة.

عندما يقول عنوان تدوينة «أسرع 25 مرة»، يميل أغلب المطورين إلى تحديث الحزمة فوراً وانتظار المعجزة. لكن إصدار NumPy 2.5 يقدّم مثالاً ممتازاً على أن الرقم الكبير يحمل شرطاً صغيراً: الدالة np.searchsorted أصبحت أسرع بكثير، ولكن في حالة محددة جداً هي البحث عن عدد كبير من القيم في استدعاء واحد. في هذا المقال نشرح ما الذي تغيّر، ومتى ستلمس الفرق في مشروعك، ومتى لن يتغير شيء.
ما هي searchsorted ولماذا تهم؟

الدالة np.searchsorted تأخذ مصفوفة مرتبة وقيماً للبحث، وتعيد المكان الذي يجب إدراج كل قيمة فيه للحفاظ على الترتيب. تعتمد عليها مهام يومية كثيرة في علوم البيانات:
- حساب المدرجات التكرارية (Histograms) وتقسيم القيم إلى فئات.
- البحث عن الفترات الزمنية التي ينتمي إليها كل حدث في سلسلة زمنية.
- ربط جداول مرتبة ببعضها بدل الاعتماد على الحلقات البطيئة.
- مكتبات أعلى مستوى مثل SciPy وscikit-learn وpandas التي تستفيد منها داخلياً.
لأنها لبنة أساسية، فإن أي تحسين فيها ينعكس على طبقات كثيرة فوقها دون أن يغيّر المستخدم سطراً واحداً من شيفرته.
ماذا تغيّر في NumPy 2.5؟
بحسب مدونة Scientific Python، أعيدت كتابة التنفيذ بحيث تُجمَّع عمليات البحث المستقلة معاً، مع استخدام ذاكرة إضافية ثابتة، ونُقل التنفيذ إلى C++. أما ملاحظات الإصدار الرسمية فتصف الفكرة بدقة أكبر: تُنفَّذ خطوات البحث الثنائي لكل المفاتيح على دفعات، فتستفيد من ذاكرة التخزين المؤقت للمعالج ومن التنفيذ خارج الترتيب (Out-of-order execution).
الفرق بين الطريقتين
في الطريقة القديمة، كان كل مفتاح يُبحث عنه بحثاً ثنائياً كاملاً قبل الانتقال إلى المفتاح التالي. في الطريقة الجديدة تتقدم كل عمليات البحث خطوة واحدة معاً، ثم الخطوة التالية، وهكذا. هذا يسمح للمعالج بتشغيل عدة قراءات من الذاكرة في وقت متداخل بدل الانتظار على كل قراءة على حدة.
الخوارزمية نفسها لم تتغير، بل تغيّر ترتيب العمل بما يناسب طريقة عمل المعالجات الحديثة.
الأرقام: 25 مرة أم 20 مرة؟
تتحدث التدوينة عن تسريع يصل إلى 25 ضعفاً مقارنة بإصدار 2.4، بينما تكتفي ملاحظات الإصدار بالقول إن التسريع قد يصل إلى نحو 20 ضعفاً عند مئات الآلاف من المفاتيح. الصياغتان لا تتناقضان؛ كلتاهما تصف «الحد الأعلى» في ظروف مناسبة. القاعدة العملية هنا أن تقرأ كلمة «يصل إلى» كسقف لا كمتوسط.
| الحالة | التوقع |
|---|---|
| مئات الآلاف من المفاتيح في استدعاء واحد | أكبر تسريع ممكن |
| آلاف المفاتيح | تسريع ملحوظ على الأرجح |
| مفتاح واحد في كل استدعاء | أداء مماثل للإصدارات السابقة |
| حلقة Python تستدعي الدالة مفتاحاً مفتاحاً | لا استفادة تقريباً |
كيف تستفيد عملياً؟
الدرس الأهم أن شكل استدعائك هو ما يحدد المكسب. إذا كان كودك يبحث عن القيم داخل حلقة، فأنت لن تلمس شيئاً. أما إذا مررت المصفوفة كاملة دفعة واحدة، فستحصل على التحسين تلقائياً بعد الترقية.
قبل وبعد
بدل كتابة حلقة تستدعي np.searchsorted(arr, x) لكل قيمة x، مرّر المصفوفة كلها:
import numpy as np
edges = np.sort(np.random.rand(1_000_000))
queries = np.random.rand(500_000)
# الأسلوب المناسب للتسريع: استدعاء واحد لكل المفاتيح
positions = np.searchsorted(edges, queries)
هذا الأسلوب متجهي (Vectorized) أصلاً، وهو ما كان يُنصح به قبل هذا الإصدار أيضاً، لكن الفارق الآن أن تنفيذه الداخلي صار يستغل المعالج بكفاءة أفضل.
خطوات الترقية الآمنة
- جرّب الإصدار الجديد في بيئة معزولة قبل ترقية المشروع الرئيسي.
- شغّل اختباراتك كاملة، وانتبه إلى أي تحذيرات جديدة (Deprecation Warnings).
- قِس أداء الأجزاء الحساسة قبل الترقية وبعدها بالبيانات الحقيقية، لا ببيانات عشوائية فقط.
- راجع ملاحظات الإصدار بحثاً عن تغييرات سلوكية تمس مكتباتك المعتمدة عليه.
هل هذا الخبر مهم لمن لا يعمل في علوم البيانات؟
نعم، لسببين. الأول أن مكتبات كثيرة تعتمد على NumPy من الداخل، فقد يظهر التحسين في أدوات لم تتوقع أنها تستعمل هذه الدالة. والثاني أن القصة مثال تعليمي جيد: كثير من مكاسب الأداء الحديثة لا تأتي من خوارزمية جديدة، بل من احترام طريقة عمل العتاد، أي الذاكرة المؤقتة وتوازي التعليمات.
وقد علّق أحد المشاركين في نقاش على موقع Lobsters بأن الادعاء أضيق مما يبدو: أنت تسرّع البحث الثنائي «الدفعي»، لا البحث الثنائي بوجه عام. وهذا توصيف دقيق يستحق أن يتذكره كل من يقرأ عناوين الأداء.
أسئلة شائعة
هل سيصبح كودي أسرع 25 مرة بمجرد الترقية؟
لا. التسريع الكبير يظهر فقط حين تبحث عن عدد كبير من القيم في استدعاء واحد. إن كنت تبحث عن قيمة واحدة في كل استدعاء، فأداؤك سيبقى قريباً من السابق.
هل أحتاج إلى تعديل شيفرتي؟
في الغالب لا. الواجهة لم تتغير، والتحسين داخلي. لكن إن كانت لديك حلقة تستدعي الدالة مفتاحاً مفتاحاً، فتحويلها إلى استدعاء واحد لكل المفاتيح هو ما سيجعلك تستفيد فعلاً.
هل الترقية آمنة لمشروع إنتاجي؟
اختبرها أولاً في بيئة منفصلة وقارن النتائج والأداء. هذه نصيحة عامة لكل ترقية لمكتبة أساسية، وليست خاصة بهذا الإصدار.
الخلاصة
إصدار NumPy 2.5 يقدّم تحسيناً حقيقياً في np.searchsorted لكنه مشروط بطريقة الاستخدام: دفعات كبيرة من المفاتيح تعني مكسباً كبيراً، ومفتاح واحد يعني لا شيء يذكر. قبل أن تعيد بناء أي شيء، قِس أداء حالتك أنت، ثم قرّر. وإن كنت تعمل أصلاً بأسلوب متجهي، فالأرجح أنك ستجني الفائدة بمجرد الترقية، دون أي جهد إضافي.
المصادر: Scientific Python، ملاحظات إصدار NumPy 2.5