Kod tabanına karşı yaklaşımlar eşit derecede yavaşlıyor


11

Optimizasyon çabalarımızla düzenli bir şekilde yavaşlayan orta boyutlu bir C ++ kod tabanı (10Mloc) üzerinde çalışıyoruz .

Bu kod tabanı, onları çalıştırmak için birleştirdiğimiz bir dizi kütüphanedir. Bu kütüphanelerin nasıl iletişim kurduğuna dair genel çerçeve geliştirildiğinde, performans üzerinde biraz duruldu ve daha sonra, daha fazla bölüm eklendiğinde, genel çerçeve fazla değişmedi. Optimizasyon gerektiğinde ve donanımımız geliştikçe yapıldı. Bu, pahalı erken kararı ancak daha sonra belirgin hale getirdi. Şimdi, kod tabanının büyük bölümlerinin yeniden yazılmasını gerektirecekleri için daha fazla optimizasyonun çok daha pahalı olduğu bir noktadayız. Prensipte kodun çok daha hızlı çalışabilmesi gerektiğini bildiğimiz için kendimizi istenmeyen bir yerel asgari seviyeye yaklaşırken buluyoruz.

Kolay optimizasyon fırsatlarıyla kolayca karıştırılmayan, küresel olarak optimum performans gösteren bir çözüme doğru bir kod tabanının gelişimini neyin üstleneceğine karar vermeye yardımcı olan başarılı metodolojiler var mı?

DÜZENLE

Şu anda nasıl profil oluşturduğumuzu cevaplamak için:

Gerçekten bu kodun nasıl kullanılabileceğine dair iki farklı senaryo var, her ikisi de utanç verici bir şekilde paralel. Profilleme, hem büyük bir girdi örneği üzerinde ortalama duvar saati süresi hem de daha ayrıntılı çalışmalarla (talimat maliyetleri, şube yanlış tahminleri ve önbellek sorunları) yapılır. Sadece son derece homojen makinelerimizde (birkaç bin özdeş makineden oluşan bir küme) çalıştığımız için bu iyi çalışıyor. Genellikle tüm makinelerimizi meşgul ettiğimiz için çoğu zaman daha hızlı çalışır, ek yeni şeylere bakabileceğimiz anlamına gelir. Mesele şu ki, yeni girdi varyasyonları ortaya çıktığında, diğer kullanım durumları için en belirgin mikro verimsizlikleri kaldırdığımızdan, muhtemelen "en iyi çalışan" senaryoların sayısını daralttığımız için geç gelen bir ceza alabilirler.


10
10Mloc aslında büyük bir proje
BЈовић

1
Sayıldığı gibi 10 milyon loc (SI öneki) sloc. Burada "orta boy" olarak adlandırdım çünkü burada neyin "büyük" olduğu hakkında hiçbir fikrim yok.
Benjamin Bannier

5
eminim 10 milyon en azından her yerde büyük ve büyük olasılıkla büyük yerlerde.
Ryathal

1
Harika, teşekkürler @honk 10M LOC için, neredeyse donanım düzeyinde çok düşük bir optimizasyon yapıyormuşsunuz gibi geliyor mu? Geleneksel OOP (AOS "yapı dizisi") önbelleklerde korkunç derecede verimsizdir, sınıflarınızı SOA (dizilerin yapısı) olarak yeniden düzenlemeyi denediniz mi, böylece kodunuzun üzerinde çalıştığı veri noktaları bellekte tutarlı mı? Bu kadar makineyle iletişim tıkanıklıkları veya senkronizasyon yeme süresi ile mi karşılaşıyorsunuz? Son soru, yüksek miktarda veri akışı ile mi ilgileniyorsunuz yoksa bu çoğunlukla veri setlerinizdeki karmaşık işlemlerle mi ilgili?
Patrick Hughes

1
Bu kadar koda sahip olduğunuzda, bahsettiğim yerel olmayan türün büyük potansiyel hızlanmaları olduğu için mükemmelden hayatınıza bahis yapma şansı. Binlerce iş parçacığı / işlem varsa hiçbir fark yaratmaz. Bazı rastgele duraklamalar ya onları sizin için parmaklayacak ya da bana yanlış olduğunu kanıtlayacak.
Mike Dunlavey

Yanıtlar:


9

Bu soruna genel amaçlı bir yaklaşım bilmiyorum, ancak geçmişte benim için biraz ilgili iki yaklaşım iyi çalıştı: daha iyi terimlerin olmaması nedeniyle, onlara demetleme ve yatay optimizasyon dedim .

Demetleme yaklaşımı, çok sayıda kısa, hızlı işlemi, sonuçta aynı sonucu veren tek, daha yavaş çalışan, son derece uzmanlaşmış bir işlemle değiştirme girişimidir.

Misal: Görsel kural düzenleyicimizin özellikle yavaş bir işleminin profilini çıkardıktan sonra "düşük asılı meyve" bulmadık: yürütme süresinin% 2'sinden fazlasını alan tek bir işlem yoktu, ancak işlem bir bütün olarak durgun hissetti. Ancak, editörün sunucuya çok sayıda küçük istek gönderdiğini keşfettik. Editör ayrı ayrı yanıtları hızlı bir şekilde işlese de, istek / yanıt etkileşimlerinin sayısı çarpımsal bir etkiye sahipti, bu nedenle işlemin toplam süresi birkaç saniye idi. Bu uzun süren işlem sırasında editörün etkileşimlerini dikkatlice katalogladıktan sonra, sunucu arayüzüne yeni bir komut ekledik. Ek komut, kısa işlemlerin bir alt kümesini gerçekleştirmek için gereken verileri kabul ettiğinden, döndürülecek son veri kümesini anlamak için veri bağımlılıklarını araştırdı ve sunucuya tek bir seferde tüm tek tek küçük işlemleri tamamlamak için gereken bilgileri içeren bir yanıt sağladı. Bu, kodumuzdaki işlem süresini azaltmadı, ancak birden çok pahalı istemci-sunucu gidiş-dönüş yolculuğunun kaldırılması nedeniyle çok önemli bir gecikme süresi azalttı.

Yatay optimizasyon, yürütme ortamınızın belirli bir özelliğini kullanarak sisteminizin birden çok bileşeni arasında ince bir şekilde dağıtılan "yavaşlığı" ortadan kaldırdığınızda ilgili bir tekniktir.

Misal: Uzun süren bir işlemin profilini oluşturduktan sonra, uygulama etki alanı sınırı boyunca çok fazla çağrı yaptığımızı keşfettik (bu .NET'e oldukça özeldir). Çağrıların hiçbirini ortadan kaldıramıyorduk ve onları bir araya getiremedik: sistemimizin çok farklı bölümlerinden farklı zamanlarda geliyorlardı ve istedikleri şeyler önceki taleplerden geri dönen sonuçlara bağlıydı. Her çağrı, nispeten az miktarda verinin serileştirilmesini ve serileştirilmesini gerektiriyordu. Yine, bireysel çağrıların süresi kısa, ancak sayıca çok büyüktü. Serileştirmeyi neredeyse tamamen engelleyen bir şema tasarladık, bunun yerine uygulama etki alanı sınırı boyunca bir işaretçi geçirerek değiştirdik. Bu büyük bir kazançtı, çünkü tamamen ilgisiz sınıflardan gelen birçok talep, tek bir uygulama sonucunda anında çok daha hızlı hale geldi.yatay çözüm.


Deneyiminizi paylaştığınız için teşekkürler, bunlar akılda tutulması gereken yararlı optimizasyonlardır. Ayrıca, sorunlu parçaları farklı bir yere kaldırdıkları için, gelecekte kontrol etmek çok daha iyi olacaktır. Bir anlamda, en başta olması gerekenleri, şimdi sadece sert verilerle yerine koydular.
Benjamin Bannier

3

Bu, pahalı erken kararı ancak daha sonra belirgin hale getirdi. Şimdi, kod tabanının büyük bölümlerinin yeniden yazılmasını gerektirecekleri için daha fazla optimizasyonun çok daha pahalı olduğu bir noktadayız.

Bu yeniden yazmaya başladığınızda, farklı şeyler yapmanız gerekir.

İlk. Ve en önemlisi. "Optimizasyonu" durdurun. "Optimizasyon" hiç önemli değil. Gördüğünüz gibi, sadece toptan yeniden yazma önemlidir.

Bu nedenle.

İkinci. Her veri yapısı ve algoritma seçiminin etkilerini anlayın.

Üçüncü. Gerçek veri yapısı ve algoritma seçimini "geç bağlama" meselesi haline getirin. Arayüzün arkasında kullanılan çeşitli uygulamalardan herhangi birine sahip olabilen tasarım arayüzleri.

Veri yapısında veya algoritmada toptan bir değişiklik yapmanıza izin veren bir dizi arabiriminiz varsa, şimdi yaptığınız (yeniden yazma) çok, çok daha az acı verici olmalıdır.


1
Cevabınız için teşekkürler. Hala optimizasyona ihtiyacımız olsa da (ki bu benim için 1. ve 2. altına düşüyor) 3'ün arkasındaki organize düşünceyi gerçekten çok seviyorum. Karşılaştığımız sorunlar. Tutarlı bir dile yerleştirdiğiniz için teşekkür ederiz.
Benjamin Bannier

Aslında optimize etmeniz gerekmez. Doğru veri yapısına sahip olduğunuzda, optimizasyonun çaba kaybı olduğu gösterilecektir. Profil oluşturma, yanlış veri yapısına ve yanlış algoritmaya sahip olduğunuzu gösterir. Performans farkı ile dalga geçilmesi ++ve +=1alakasız ve neredeyse ölçülemez olacaktır. Bu kadar şey son .
S.Lott

1
Tüm kötü algoritmalar saf akıl yürütme ile bulunamaz. Arada bir kişinin oturması ve profil oluşturması gerekir. İlk tahminin doğru olup olmadığını öğrenmenin tek yolu budur. Gerçek maliyeti tahmin etmenin tek yolu budur (BigO + const).
Benjamin Bannier

Profil oluşturma kötü algoritmaları ortaya çıkaracaktır. Tamamen doğru. Bu hala "optimizasyon" değil. Bu hala tasarım değişikliği yaptığım temel bir tasarımın düzeltilmesidir. Optimizasyon (ince ayar, ince ayar vb.) Profil oluşturma için nadiren görülebilir.
S.Lott

3

Güzel bir pratik hile, birim test takımınızı bir performans test paketi olarak kullanmaktır .

Aşağıdaki yaklaşım benim kod tabanlarımda iyi çalıştı:

  1. Birim testi kapsamınızın iyi olduğundan emin olun (bunu zaten yaptınız, değil mi?)
  2. Test çalışan çerçeve testinizin her bir testte çalışma zamanı rapor ettiğinden emin olun . Bu önemlidir, çünkü yavaş performansın nerede olduğunu bulmak istersiniz .
  3. Bir test yavaş çalışıyorsa , bunu bu alana dalmak ve optimizasyonu hedeflemek için kullanın . Bir performans sorunu belirlemeden önce optimizasyonun erken yapılması düşünülebilir, bu nedenle bu yaklaşımla ilgili en iyi şey, öncelikle düşük performansla ilgili somut kanıtlar elde etmenizdir. Gerekirse, kök sorunun nerede olduğunu tanımlayabilmeniz için testi farklı yönleri karşılaştıran daha küçük testlere bölün.
  4. Bir test son derece hızlı çalışıyorsa, genellikle iyi bir testtir, ancak testi farklı parametrelerle bir döngüde çalıştırmayı düşünebilirsiniz. Bu, onu daha iyi bir performans testine dönüştürür ve ayrıca parametre boşluğunun test kapsamını artırır.
  5. Performansı özel olarak hedefleyen birkaç ekstra test yazın, örneğin uçtan uca işlem süreleri veya 1000 kural uygulamasını tamamlama süresi. İşlevsel olmayan özel performans gereksinimleriniz varsa (örn. <300 ms yanıt süresi), çok uzun sürerse testin başarısız olmasını sağlayın.

Tüm bunları yapmaya devam ederseniz, zaman içinde kod tabanınızın ortalama performansı organik olarak iyileşmelidir.

Ayrıca, ortalama test sürelerini izlemek ve ortalama performansta zaman içinde performans çizelgeleri ve spot regresyonları çizmek mümkün olacaktır. Ben bunu hiç rahatsız etmedim çünkü değiştirdiğinizde ve yeni testler eklediğinizde olduğu gibi karşılaştırmanızı sağlamak biraz zor, ama performans sizin için yeterince önemliyse ilginç bir egzersiz olabilir.


zeki - howevre tekniğini seviyorum, bu mikro optimizasyon ve yerel bir minimum seviyeye çıkacak
ulaşmanıza

@jasonk - kesinlikle haklısın. Bazen belirli bir mimari değişikliğin neden haklı olduğunu göstermeniz için gereken kanıtları sunabileceğimi eklememize rağmen .....
mikera

1

@Dasblinkenlight'ın yanıtı, özellikle büyük kod tabanlarında (tecrübelerime göre) çok yaygın bir soruna işaret ediyor. Ciddi performans sorunları olabilir, ancak bunlar yerel değildir . Bir profil oluşturursanız, dikkat çekmek için tek başına yeterli zaman alan hiçbir rutin yoktur. (Callees'i içeren kapsayıcı zaman yüzdesine baktığınızı varsayarsak, "zaman" ile bile uğraşmayın.)

Aslında, bu durumda, asıl sorun profilleme ile değil, şanslı içgörü ile bulundu.

Bu konuyu ayrıntılı olarak ve nasıl ele alacağınızı gösteren kısa bir PDF slayt gösterisi olan bir vaka çalışmam var . Temel nokta, kod olabileceğinden çok daha yavaş olduğu için (tanım gereği) kaldırılabilecek bir şey yapmak için fazla zaman harcanması anlamına gelir.

Programın durumunun rastgele zaman örneklerine bakacak olsaydınız , sürenin yüzdesi nedeniyle çıkarılabilir etkinliği yaptığını görürsünüz . Çıkarılabilir etkinliğin bir işlevle, hatta birçok işlevle sınırlı olmaması mümkündür. Bu şekilde yerelleştirilmez.

Bu bir "sıcak nokta" değil.

Gördüğünüz şeyle ilgili yaptığınız bir açıklama, bu büyük bir zaman yüzdesi doğru. Bu, keşfetmeyi kolaylaştırır, ancak düzeltmenin kolay olup olmadığı, ne kadar yeniden yazma gerektirdiğine bağlıdır.

(Bu yaklaşımla ilgili sık sık yapılan bir eleştiri vardır, örneklem sayısı istatistiksel geçerlilik için çok azdır. PDF'nin 13. slaytında yanıtlanmıştır. Kısaca - evet, potansiyel tasarrufların “ölçümü” konusunda yüksek belirsizlik vardır, ancak 1) bu potansiyel tasarrufların beklenen değeri esasen etkilenmez ve 2) $ x $ potansiyel tasarrufları 1 $ / (1-x) $ hızlanma oranına çevrildiğinde, yüksek (faydalı) faktörlere doğru eğilir.)


Cevabınız için teşekkürler. İstatistiksel örneklemeye inanmıyoruz ve valgrind ile enstrümantasyon kullanıyoruz. Bu bize yaptığımız çoğu şeyin hem "öz" hem de "kapsayıcı" maliyetine ilişkin iyi tahminler verir.
Benjamin Bannier

@honk: Doğru. Ancak ne yazık ki, enstrümantasyon hala performans sorunlarının yerelleştirilmiş olduğu fikrini taşıyor ve bu nedenle rutinlerde vb. Harcanan zamanın bir kısmını ölçerek bulunabilir. Kesinlikle valgrind vb. .
Mike Dunlavey
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.