« Tüm yayınlar

Paylaşılan ArrayPool neden thread'ler arttıkça yavaşladı

ArrayPool<byte>.Shared'daki gizli monitor çekişmesi, canlı CPU profilleme ve decompile ile ortaya çıkarıldı: %20 CPU artışının gerçek nedeni.

Bir market-data-gateway servisinde CPU kullanımı, sözde zararsız bir refactor sonrası %20 arttı: özel bir byte-array önbelleği ArrayPool<byte>.Shared ile değiştirilmişti. Statik analiz araçları hiçbir uyarı vermedi çünkü ortada ne bir kilit ifadesi ne de bariz bir hata vardı; sorun yalnızca çalışma zamanında, belirli bir kullanım deseninde ortaya çıkıyordu.

dotnet-diagnostics-mcp ile yapılan canlı bir CPU örneklemesi, örneklerin %15,8'inin Monitor.Enter_Slowpath içinde geçtiğini gösterdi — kod tabanında hiç lock ifadesi olmamasına rağmen. Call-tree, kilidi tetikleyen çağıranların BufferPool.Rent ve Return sarmalayıcıları olduğunu ortaya çıkardı; yani kilit, iş mantığında değil doğrudan ArrayPool'un kendi içinde alınıyordu.

dotnet-assembly-mcp ile System.Private.CoreLib içindeki gerçek SharedArrayPool<T> implementasyonu decompile edilerek incelendi: Return metodu, aynı thread'e özel hızlı yol başarısız olduğunda çekirdek başına düşen bir stack'e Monitor.Enter/Exit ile push yapıyordu. Uygulama kodunda tek bir thread buffer kiralıyor, N farklı consumer thread'i ise bu buffer'ları geri döndürüyordu — thread-local hızlı yolu her seferinde atlayan ve paylaşılan kilide düşen tam da bu desendi.

Bu vaka, mühendisler için önemli bir dersi gösteriyor: ArrayPool<T>.Shared'ı 'kirala bir thread'de, döndür başka bir thread'de' şeklinde kullanmak, framework'ün optimize ettiği thread-local hızlı yolu sistematik olarak devre dışı bırakıyor ve gizli bir monitor çekişmesine yol açıyor. Statik analiz bunu asla yakalayamaz; çözüm ekstra kilit eklemek değil, kiralama ve döndürme işlemlerini aynı thread'de tutmak ya da özel bir havuzlama stratejisine geri dönmekti.