مقارنة الشرائح في Go: تحسين الأداء والأداء

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقارنة الشرائح في Go: كيف لا تفقد الأداء ولا تصاب بالجنون

\n\n

تخيل الموقف: أنت تكتب خدمة عالية التحميل بلغة Go، وتحتاج إلى مقارنة شريحتين من البايت أو السلاسل النصية. قد يبدو الأمر بسيطًا، أليس كذلك؟ لكن الشيطان، كما هو الحال دائمًا، يكمن في التفاصيل. على Stack Overflow، جمع سؤال "Checking the equality of two slices" آلاف المشاهدات - وهذا ليس من قبيل الصدفة. في هذه المقالة، سنقوم بتحليل المخاطر الخفية التي تنتظر المطور، وكيفية كتابة مقارنة صحيحة، والأهم من ذلك، كيفية القيام بذلك بأسرع ما يمكن.

\n\n

لن نستخدم تعريفات أكاديمية مملة. بدلاً من ذلك - أمثلة حية، واختبارات أداء، ونصائح عملية يمكنك تطبيقها اليوم. هيا بنا!

\n\n

لماذا المقارنة البسيطة ليست دائمًا الصحيحة؟

\n\n

في Go، الشريحة (slice) ليست مصفوفة. إنها واصف يحتوي على مؤشر للمصفوفة الأساسية، والطول، والسعة. لذلك، عامل المقارنة == لا يعمل مع الشرائح. محاولة كتابة slice1 == slice2 ستؤدي إلى خطأ في الترجمة:

\n\n
invalid operation: slice1 == slice2 (slice can only be compared to nil)
\n\n

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

\n\n

إذن، ماذا نفعل؟ هناك عدة طرق، وسننظر في كل منها.

\n\n

الطريقة 1: حلقة يدوية (أساسية ولكن موثوقة)

\n\n

الطريقة الأكثر وضوحًا هي كتابة دالة تمر عبر جميع العناصر وتقارنها واحدًا تلو الآخر. وهذا يمنحك تحكمًا كاملاً في العملية.

\n\n
func EqualSlices(a, b []int) bool {\n    if len(a) != len(b) {\n        return false\n    }\n    for i := range a {\n        if a[i] != b[i] {\n            return false\n        }\n    }\n    return true\n}
\n\n

الإيجابيات: الشفافية، عدم وجود تبعيات، إمكانية التخصيص (مثل المقارنة مع تسامح للأعداد العشرية).
\nالسلبيات: تحتاج إلى كتابة دالة لكل نوع بيانات.

\n\n

الطريقة 2: reflect.DeepEqual (شامل ولكن بطيء)

\n\n

في المكتبة القياسية، توجد دالة reflect.DeepEqual التي يمكنها مقارنة أي هياكل بيانات - الشرائح، الخرائط، الهياكل. تبدو كالتالي:

\n\n
import "reflect"\n\nfunc main() {\n    a := []int{1, 2, 3}\n    b := []int{1, 2, 3}\n    fmt.Println(reflect.DeepEqual(a, b)) // true\n}
\n\n

قد يبدو هذا مثاليًا. ولكن هناك فارق بسيط: reflect.DeepEqual تستخدم الانعكاس (reflection)، وهذا دائمًا مكلف. في الشرائح الصغيرة، الفرق غير ملحوظ، ولكن إذا كان لديك آلاف العناصر - سينخفض الأداء بشكل كبير.

\n\n

هام: reflect.DeepEqual تعتبر أيضًا أن nil والشريحة الفارغة مختلفتان! قد يكون هذا مفاجئًا:

\n\n
var a []int = nil\nb := []int{}\nfmt.Println(reflect.DeepEqual(a, b)) // false!
\n\n

الطريقة 3: bytes.Equal لشرائح البايت (الأمثل)

\n\n

إذا كنت تعمل مع []byte، فإن الخيار الأفضل هو دالة bytes.Equal من المكتبة القياسية. إنها مكتوبة بلغة التجميع ومحسّنة إلى أقصى حد:

\n\n
import "bytes"\n\nfunc main() {\n    a := []byte("hello")\n    b := []byte("hello")\n    fmt.Println(bytes.Equal(a, b)) // true\n}
\n\n

هذه هي أسرع طريقة لشرائح البايت. داخليًا، تستخدم memcmp على مستوى C، مما يعطي زيادة هائلة في الأداء.

\n\n

الطريقة 4: المقارنة باستخدام crypto/subtle (للتشفير)

\n\n

تستحق حزمة crypto/subtle ذكرًا خاصًا. وظيفتها ConstantTimeCompare مصممة لمقارنة البيانات السرية (كلمات المرور، الرموز المميزة) دون تسريب الوقت. المقارنة العادية قد تكون عرضة لهجمات التوقيت:

\n\n
import "crypto/subtle"\n\nfunc                

المدونات

توصيات الكتب