تحليل أداء كود Go: إيجاد نقاط الضعف عملياً

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

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

ابدأ الدورة

مقدمة: لماذا يعتبر تحليل أداء Go أمراً ضرورياً للمطور

\n

تشتهر Go بأدائها العالي، ولكن حتى أسرع كود يمكن أن يتباطأ بسبب الخوارزميات غير الفعالة، أو تسرب الذاكرة، أو التعامل غير الصحيح مع الـ Goroutines. تحليل الأداء هو الطريقة الوحيدة لمعرفة أين يستهلك تطبيقك الموارد بالضبط. في هذه المقالة، سنستعرض الطرق العملية لتحليل أداء كود Go باستخدام الأدوات المدمجة: pprof و trace و benchstat. ستتعلم كيفية إيجاد نقاط الضعف وإصلاحها.

\n\n

1. تحليل أداء وحدة المعالجة المركزية (CPU) باستخدام pprof: إيجاد النقاط الساخنة

\n

يُظهر تحليل أداء وحدة المعالجة المركزية (CPU) أي الدوال تستهلك معظم وقت المعالج. تتيح لك الحزمة المدمجة runtime/pprof جمع ملفات الأداء بسهولة.

\n\n

1.1. إضافة تحليل الأداء إلى الكود

\n

لننظر إلى مثال لخادم HTTP بسيط يقوم بعمليات حسابية ثقيلة:

\n
package main\n\nimport (\n    "fmt"\n    "log"\n    "net/http"\n    _ "net/http/pprof"\n    "os"\n    "runtime/pprof"\n)\n\nfunc heavyTask() {\n    sum := 0\n    for i := 0; i < 1_000_000; i++ {\n        sum += i * i\n    }\n    _ = sum\n}\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n    heavyTask()\n    fmt.Fprintf(w, "Done")\n}\n\nfunc main() {\n    f, _ := os.Create("cpu.prof")\n    pprof.StartCPUProfile(f)\n    defer pprof.StopCPUProfile()\n\n    http.HandleFunc("/", handler)\n    log.Fatal(http.ListenAndServe(":8080", nil))\n}\n
\n

بعد تشغيل الخادم وإجراء عدة طلبات، سيكون لديك ملف cpu.prof. قم بتحليله باستخدام الأمر:

\n
go tool pprof -http=:8081 cpu.prof
\n

سيتم فتح رسم بياني تفاعلي في المتصفح. لاحظ العقد الحمراء - فهي تشير إلى الدوال الأكثر "سخونة". في حالتنا هذه، هي heavyTask.

\n\n

1.2. تفسير النتائج

\n

في واجهة الويب الخاصة بـ pprof، سترى:

\n
    \n
  • Graph — رسم بياني للاستدعاءات بأحجام عقد تتناسب مع وقت التنفيذ.
  • \n
  • Flame Graph — رسم بياني "لهبي"، حيث تمثل الأشرطة الطويلة الأجزاء الأبطأ.
  • \n
  • Top — جدول بالدوال مرتبة حسب وقت وحدة المعالجة المركزية (CPU).
  • \n
\n

إذا رأيت أن heavyTask تستهلك 90% من الوقت، فهذا يعني أنك بحاجة إلى تحسينها (على سبيل المثال، استبدال الحلقة بصيغة مجموع المربعات).

\n\n

2. تحليل أداء الذاكرة: إيجاد التسريبات والتخصيصات

\n

تسريبات الذاكرة في Go نادرة، لكن التخصيصات الزائدة يمكن أن تبطئ التطبيق بشكل كبير. نستخدم runtime/pprof لملف أداء heap.

\n\n

2.1. جمع ملف أداء heap

\n

أضف إلى الكود جمع ملف أداء الذاكرة عند استقبال إشارة SIGUSR1:

\n
import (\n    "os"\n    "os/signal"\n    "runtime/pprof"\n    "syscall"\n)\n\nfunc main() {\n    // ... الكود الخاص بك ...\n    sigs := make(chan os.Signal, 1)\n    signal.Notify(sigs, syscall.SIGUSR1)\n    go func() {\n        <-sigs\n        f, _ := os.Create("mem.prof")\n        pprof.WriteHeapProfile(f)\n        f.Close()\n        os.Exit(0)\n    }()\n    // ...\n}\n
\n

بعد إرسال الإشارة kill -SIGUSR1 <PID> ستحصل على ملف mem.prof. قم بتحليله:

\n
go tool pprof -http=:8082 mem.prof
\n\n

2.2. المشاكل النموذجية المتعلقة بالذاكرة

\n
    \n
  • عدد كبير من التخصيصات الصغيرة — على سبيل المثال، إنشاء سلاسل نصية باستخدام + بدلاً من strings.Builder.
  • \n
  • الاحتفاظ بمراجع لكائنات كبيرة — بعد الاستخدام، يجب تصفير المؤشرات.
  • \n
  • Goroutines التي لا تنتهي — كل Goroutine تستهلك على الأقل 4 كيلوبايت من المكدس.
  • \n
\n

مثال على كود غير أمثل:

\n
func badConcat(n int) string {\n    s := ""\n    for i := 0; i < n; i++ {\n        s += "x"  // كل تكرار ينشئ سلسلة نصية جديدة\n    }\n    return s\n}\n
\n

تحسين

المدونات

توصيات الكتب