في مشروع شخصي مع Go، الذي يحصل على معلومات حول الأصول المالية من Bovespa.
يستخدم النظام بشكل مكثف التزامن والتوازي مع goroutines، ويقوم بتحديث معلومات الأصول (جنبًا إلى جنب مع حسابات الأعمال) كل 8 ثوانٍ.
في البداية، لم تظهر أي أخطاء أو تحذيرات، لكنني لاحظت أن بعض goroutines كانت تستغرق وقتًا أطول من غيرها للتنفيذ.
لكي نكون أكثر تحديدًا، بينما كان وقت p99 عند 0.03 مللي ثانية، في بعض النقاط، زاد إلى 0.9 مللي ثانية. قادني هذا إلى مزيد من التحقيق في المشكلة.
اكتشفت أنني كنت أستخدم تجمع goroutine للإشارة، والذي تم إنشاؤه بناءً على متغير GOMAXPROCS.
ومع ذلك، أدركت أن هناك مشكلة في هذا النهج.
عندما نستخدم المتغير GOMAXPROCS، فإنه لا يلتقط عدد النوى المتوفرة في الحاوية بشكل صحيح. إذا كانت الحاوية تحتوي على عدد من النوى المتوفرة أقل من إجمالي الأجهزة الافتراضية، فإنها تأخذ في الاعتبار إجمالي الأجهزة الافتراضية. على سبيل المثال، يحتوي جهاز VM الخاص بي على 8 مراكز متاحة، لكن الحاوية تحتوي على 4 مراكز فقط. وقد أدى ذلك إلى إنشاء 8 goroutines للتشغيل في نفس الوقت، مما تسبب في الاختناق.
بعد الكثير من البحث بين عشية وضحاها، وجدت مكتبة تم تطويرها بواسطة Uber والتي تقوم تلقائيًا بضبط متغير GOMAXPROCS بشكل أكثر كفاءة، بغض النظر عما إذا كان في حاوية أم لا. أثبت هذا الحل أنه مستقر وفعال للغاية: automaxprocs
ضبط GOMAXPROCS تلقائيًا ليتوافق مع حصة وحدة المعالجة المركزية لحاوية Linux.
اذهب واحصل على -u go.uber.org/automaxprocs
import _ "go.uber.org/automaxprocs"
func main() {
// Your application logic here.
}
تم قياس البيانات من موازن التحميل الداخلي لشركة Uber. قمنا بتشغيل موازن التحميل بحصة 200% لوحدة المعالجة المركزية (أي نواتين):
جوماكسبروكس | RPS | P50 (مللي ثانية) | P99.9 (مللي ثانية) |
---|---|---|---|
1 | 28,893.18 | 1.46 | 19.70 |
2 (يساوي الحصة) | 44,715.07 | 0.84 | 26.38 |
3 | 44,212.93 | 0.66 | 30.07 |
4 | 41,071.15 | 0.57 | 42.94 |
8 | 33,111.69 | 0.43 | 64.32 |
الافتراضي (24) | 22,191.40 | 0.45 | 76.19 |
عندما يتم زيادة GOMAXPROCS فوق حصة وحدة المعالجة المركزية، نرى انخفاضًا طفيفًا في P50، ولكننا نرى زيادات كبيرة إلى P99. ونرى أيضًا أن إجمالي RPS الذي تم التعامل معه يتناقص أيضًا.
عندما تكون GOMAXPROCS أعلى من حصة وحدة المعالجة المركزية المخصصة، شهدنا أيضًا اختناقًا كبيرًا:
$ cat /sys/fs/cgroup/cpu,cpuacct/system.slice/[...]/cpu.stat nr_periods 42227334 nr_throttled 131923 throttled_time 88613212216618
بمجرد تخفيض GOMAXPROCS لتتناسب مع حصة وحدة المعالجة المركزية، لم نشهد أي اختناق لوحدة المعالجة المركزية.
بعد تنفيذ استخدام هذه المكتبة، تم حل المشكلة، وظل الوقت p99 عند 0.02 مللي ثانية باستمرار. سلطت هذه التجربة الضوء على أهمية إمكانية الملاحظة والتوصيف في الأنظمة المتزامنة.
ما يلي مثال بسيط للغاية، ولكنه يوضح الفرق في الأداء.
باستخدام اختبار Go الأصلي وحزمة benckmak، قمت بإنشاء ملفين:
قياس الأداء_with_enhancement_test.go:
package main import ( _ "go.uber.org/automaxprocs" "runtime" "sync" "testing" ) // BenchmarkWithEnhancement Função com melhoria, para adicionar o indice do loop em um array de inteiro func BenchmarkWithEnhancement(b *testing.B) { // Obtém o número de CPUs disponíveis numCPUs := runtime.NumCPU() // Define o máximo de CPUs para serem usadas pelo programa maxGoroutines := runtime.GOMAXPROCS(numCPUs) // Criação do semáforo semaphore := make(chan struct{}, maxGoroutines) var ( // Espera para grupo de goroutines finalizar wg sync.WaitGroup // Propriade mu sync.Mutex // Lista para armazenar inteiros list []int ) // Loop com mihão de indices for i := 0; iقياس الأداء_without_enhancement_test.go:
package main import ( "runtime" "sync" "testing" ) // BenchmarkWithoutEnhancement Função sem a melhoria, para adicionar o indice do loop em um array de inteiro func BenchmarkWithoutEnhancement(b *testing.B) { // Obtém o número de CPUs disponíveis numCPUs := runtime.NumCPU() // Define o máximo de CPUs para serem usadas pelo programa maxGoroutines := runtime.GOMAXPROCS(numCPUs) // Criação do semáforo semaphore := make(chan struct{}, maxGoroutines) var ( // Espera para grupo de goroutines finalizar wg sync.WaitGroup // Propriade mu sync.Mutex // Lista para armazenar inteiros list []int ) // Loop com mihão de indices for i := 0; iالفرق بينهما هو أن المرء يستخدم استيراد مكتبة أوبر.
عند تشغيل المعيار بافتراض أنه سيتم استخدام وحدتي معالجة مركزية، كانت النتيجة:
ns/op: يوفر متوسطًا بالنانو ثانية للمدة التي يستغرقها تنفيذ عملية معينة.
لاحظ أن إجمالي وحدة المعالجة المركزية المتوفرة لدي هو 8 مراكز، وهذا ما أعادته خاصية runtime.NumCPU(). ومع ذلك، كما هو الحال في تشغيل المعيار، حددت أن الاستخدام سيكون فقط وحدتي معالجة مركزية، والملف الذي لم يستخدم automaxprocs، حدد أن حد التنفيذ في المرة الواحدة سيكون 8 goroutines، في حين أن الأكثر كفاءة سيكون 2، لأن بهذه الطريقة، فإن استخدام تخصيص أقل يجعل التنفيذ أكثر كفاءة.
لذا، فإن أهمية إمكانية المراقبة وتوصيف تطبيقاتنا واضحة.
تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.
Copyright© 2022 湘ICP备2022001581号-3