[...] (mikrosaniye ortamında verildi) [...]
Milyonlarca ila milyarlarca şeyi döngüye sokarsak mikro saniye eklenir. C ++ 'dan kişisel bir vtune / mikro optimizasyon oturumu (algoritmik iyileştirme yok):
T-Rex (12.3 million facets):
Initial Time: 32.2372797 seconds
Multithreading: 7.4896073 seconds
4.9201039 seconds
4.6946372 seconds
3.261677 seconds
2.6988536 seconds
SIMD: 1.7831 seconds
4-valence patch optimization: 1.25007 seconds
0.978046 seconds
0.970057 seconds
0.911041 seconds
"Çok iş parçacıklı", "SIMD" (derleyiciyi yenmek için el yazısı) ve 4 değerlikli yama optimizasyonu dışında her şey mikro düzeyde bellek optimizasyonlarıydı. Ayrıca, 32 saniyenin ilk zamanlarından başlayan orijinal kod zaten biraz optimize edildi (teorik olarak en uygun algoritmik karmaşıklık) ve bu son bir oturum. Bu son oturumdan çok önce orijinal sürümün işlenmesi 5 dakika sürdü.
Bellek verimliliğini optimize etmek, genellikle tek iş parçacıklı bağlamda birkaç kez büyüklük sırasına ve daha çok iş parçacıklı bağlamlarda daha fazlasına yardımcı olabilir (verimli bir bellek temsilcisinin faydaları genellikle karışımdaki birden çok iş parçacığı ile çoğalır).
Mikro Optimizasyonun Önemi Hakkında
Mikro optimizasyonların zaman kaybı olduğu fikrinden biraz tedirgin oluyorum. İyi bir genel tavsiye olduğuna katılıyorum, ancak herkes bunu ölçümlerden ziyade önsezilere ve batıl inançlara dayanarak yapmıyor. Doğru yapıldığında, mutlaka mikro bir etki yaratmaz. Intel'in kendi Embree'sini (raytracing kernel) alıp yalnızca yazdıkları basit skaler BVH'yi test edersek (üstel olarak yenilmesi daha zor olan ray paketi değil) ve daha sonra bu veri yapısının performansını yenmeye çalışırsak, kod yıllarca profilleme ve ayarlama için kullanılan bir gazi için bile alçakgönüllülük deneyimi. Ve hepsi uygulanan mikro optimizasyonlar nedeniyle. Işınlama konusunda çalışan endüstriyel profesyoneller gördüğümde, çözümleri saniyede yüz milyondan fazla ışın işleyebilir.
Sadece algoritmik bir odak ile bir BVH'nin basit bir şekilde uygulanmasının ve herhangi bir optimize edici derleyiciye (Intel'in kendi ICC'sine) karşı saniyede yüz milyondan fazla birincil ışın kesişiminin elde edilmesinin bir yolu yoktur. Basit olanı, saniyede milyon ışın bile almaz. Saniyede birkaç milyon ışın bile almak için profesyonel kalitede çözümler gerekir. Saniyede yüz milyondan fazla ışının elde edilmesi Intel düzeyinde mikro optimizasyon gerektirir.
Algoritmalar
Mikro-optimizasyonun, dakikalar, saniyeler veya saatler ya da dakikalar arasında performans önemli olmadığı sürece önemli olmadığını düşünüyorum. Kabarcık sıralaması gibi korkunç bir algoritma alır ve bunu örnek olarak bir kütle girişi üzerinde kullanır ve daha sonra birleştirme sıralamasının temel bir uygulamasıyla karşılaştırırsak, ilkinin işlenmesi aylar alabilir, ikincisi belki 12 dakika, sonuç olarak ikinci dereceden vs lineeritmik karmaşıklık.
Aylar ve dakikalar arasındaki fark büyük olasılıkla, performansı kritik alanlarda çalışmayanlar bile çoğu insanın, aylar beklemek için kullanıcıların beklemesini gerektiriyorsa, yürütme süresinin kabul edilemez olduğunu düşünecektir.
Bu arada, mikro için optimize edilmemiş, doğrudan birleştirme türünü hızlı sıralama ile karşılaştırırsak (bu, birleştirme sıralamasında hiç algoritmik olarak üstün değildir ve yalnızca referans konum için mikro düzeyde iyileştirmeler sunar), mikro optimize edilmiş hızlı sıralama tamamlanabilir 12 dakika yerine 15 saniye. Kullanıcıların 12 dakika bekletilmesi mükemmel bir şekilde kabul edilebilir (kahve arası).
Sanırım bu fark, örneğin 12 dakika ve 15 saniye arasındaki çoğu insan için ihmal edilebilir ve bu yüzden mikro optimizasyon genellikle işe yaramaz olarak kabul edilir, çünkü bu genellikle dakikalar ve saniyeler arasındaki farktır, dakikalar ve aylar değil. İşe yaramaz olduğunu düşündüğüm diğer bir neden, genellikle önemli olmayan alanlara uygulanmasıdır: döngüsel ve kritik olmayan bazı şüpheli% 1 fark veren küçük bir alan (çok iyi bir gürültü olabilir). Ancak bu tür zaman farklarını önemseyen ve ölçmek ve doğru yapmak isteyen insanlar için, en azından bellek hiyerarşisinin temel kavramlarına (özellikle sayfa hataları ve önbellek özledikleri ile ilgili üst düzeylere) dikkat etmeye değer olduğunu düşünüyorum. .
Java İyi Mikro Optimizasyonlara Bolca Yer Bırakıyor
Vay canına, özür dilerim - bu tür bir rantla:
JVM'nin "büyüsü", bir programcının Java'daki mikro optimizasyonlar üzerindeki etkisini engelliyor mu?
Biraz ama doğru yaparsanız insanların düşünebileceği kadar değil. Örneğin, görüntü işleme yapıyorsanız, el yazısı SIMD, çoklu okuma ve bellek optimizasyonları (erişim kalıpları ve hatta görüntü işleme algoritmasına bağlı olarak temsil etme) içeren yerel kodda, 32- saniyede yüz milyonlarca pikseli bit RGBA piksel (8 bit renk kanalları) ve bazen saniyede milyarlarca.
Bir Pixel
nesne yaptıysanız, Java'da herhangi bir yere yakın olmak imkansızdır (bu tek başına bir pikselin boyutunu 64 bitte 4 bayttan 16'ya şişirir).
Ancak, Pixel
nesneden kaçınırsanız, bir bayt dizisi kullandıysanız ve bir Image
nesneyi modellediyseniz, çok daha yakınlaşabilirsiniz . Düz eski veri dizilerini kullanmaya başlarsanız Java hala oldukça yetkin. Ben Java önce bu tür şeyleri denedim ve oldukça etkilendim sağlanan normalden daha 4 kat daha büyük olduğu her yerde küçük ufacık bir demet oluşturmak kalmamasıdır nesneleri (eski: kullanım int
yerine Integer
) ve benzeri toplu arayüzleri modelini oluşturmaya başlamak Image
arayüz değil Pixel
arayüz. Hatta nesneler değil ( float
örneğin, büyük diziler) düz eski veriler üzerinde döngü yapıyorsanız Java C ++ performans rakip olabilir söylemek girişim olacaktır Float
.
Belki de bellek boyutlarından daha da önemlisi, bir dizi int
bitişik temsili garanti eder. Bir dizi Integer
değil. Birden fazla öğenin (ör: 16 ints
) tek bir önbellek hattına sığabileceği ve etkili bellek erişim kalıplarıyla tahliye edilmeden önce potansiyel olarak birbirine erişilebileceği anlamına geldiğinden, bitişiklik genellikle referans yerellik için gereklidir . Bu arada, tek bir Integer
bellekte bir yerde mahsur olabilir, ancak çevreleyen bellek önemsizdir, sadece bellek bölgesinin 16 tamsayı yerine tahliye edilmeden önce tek bir tamsayı kullanmak için bir önbellek hattına yüklenmesi gerekir. Şaşırtıcı derecede şanslı ve çevremiz olsa bileIntegers
bellekte birbirinin yanındaydı, sadece Integer
4 kat daha büyük olmanın bir sonucu olarak tahliye edilmeden önce erişilebilen bir önbellek satırına 4 sığdırabiliriz ve bu en iyi senaryoda.
Aynı bellek mimarisi / hiyerarşisi altında birleştiğimiz için orada pek çok mikro optimizasyon var. Bellek erişim kalıpları, hangi dili kullanırsanız kullanın, döngü döşemesi / engelleme gibi kavramlar genellikle C veya C ++ 'da çok daha sık uygulanabilir, ancak Java kadar yararlanır.
Son zamanlarda C ++ bazen veri üyelerinin sipariş optimizasyonu sağlayabilir okumak [...]
Veri üyelerinin sırası genellikle Java'da önemli değildir, ancak bu çoğunlukla iyi bir şeydir. C ve C ++ 'da, veri üyelerinin sırasını korumak ABI nedenleriyle genellikle önemlidir, bu nedenle derleyiciler bununla uğraşmaz. Orada çalışan insan geliştiriciler, doldurma belleğini boşa harcamamak için veri üyelerini azalan düzende (en büyükten en küçüğe) düzenlemek gibi şeylere dikkat etmelidir. Görünüşe göre JIT, dolguları en aza indirirken uygun hizalamayı sağlamak için üyeleri anında sizin için yeniden sıralayabilir, bu durumda, ortalama C ve C ++ programcılarının genellikle zayıf bir şekilde yapabileceği ve belleği boşa harcayabileceği bir şeyi otomatik hale getirir ( ki bu sadece hafızayı boşa harcamakla kalmaz, aynı zamanda AoS yapıları arasındaki adımı gereksiz yere artırarak ve daha fazla önbellek kaybına neden olarak hız kaybeder). O' Dolguyu en aza indirgemek için alanları yeniden düzenlemek için çok robotik bir şeydir, bu yüzden ideal olarak insanlar bununla ilgilenmez. Alan düzenlemesinin bir insanın optimum düzenlemeyi bilmesini gerektiren bir şekilde önemli olabileceği tek zaman, nesnenin 64 bayttan büyük olması ve alanları erişim modeline (en uygun dolguya değil) dayalı olarak düzenlememizdir - bu durumda daha insani bir çaba olabilir (bazıları derleyicinin yazılımla ne yapacağını bilmeden muhtemelen tahmin edemeyeceği bilgiler olan kritik yolların anlaşılmasını gerektirir).
Değilse, insanlar Java'da hangi hileleri kullanabileceğinize dair örnekler verebilirler (basit derleyici bayraklarının yanı sıra).
Java ve C ++ arasında optimize edici bir zihniyet açısından benim için en büyük fark, C ++ 'ın performans açısından kritik bir senaryoda nesneleri Java'dan biraz (ufacık) daha fazla kullanmanıza izin verebilmesidir. Örneğin, C ++ herhangi bir ek yükü olmayan bir sınıfa (her yerde kıyaslamalı) bir tamsayıyı sarabilir. Java, nesne başına meta veri işaretçi stili + hizalama dolgu yüküne sahip olmalıdır, bu yüzden Boolean
daha büyüktür boolean
(ancak tekdüze yansıma yararları ve final
her bir UDT için işaretlenmemiş herhangi bir işlevi geçersiz kılma yeteneği sağlar ).
C ++ 'da, homojen olmayan alanlardaki bellek düzenlerinin sürekliliğini kontrol etmek biraz daha kolaydır (örn: mekansal konum genellikle kaybolur (veya en azından kontrol kaybolur). GC üzerinden nesne tahsis ederken Java.
... ancak çoğu zaman en yüksek performanslı çözümler genellikle bunları yine de böler ve bitişik düz eski veri dizileri üzerinde bir SoA erişim deseni kullanır. Bu nedenle, en yüksek performansa ihtiyaç duyan alanlar için, Java ve C ++ arasındaki bellek düzenini optimize etme stratejileri genellikle aynıdır ve genellikle sıcak / küçük gibi şeyler yapabilen toplama tarzı arabirimler lehine bu ufacık nesne yönelimli arayüzleri yıkmanızı sağlar Soğuk alan bölme, SoA temsilcileri, vb. Homojen olmayan AoSoA temsilcileri Java'da imkansız görünüyor (sadece ham bir bayt dizisi veya bunun gibi bir şey kullanmadıysanız), ancak bunlar her ikisi desıralı ve rasgele erişim modellerinin hızlı olması gerekirken aynı zamanda sıcak alanlar için alan türlerinin bir karışımına sahip olunmalıdır. Bana göre, en yüksek performansa ulaşıyorsanız, bu ikisi arasındaki optimizasyon stratejisindeki (genel düzeyde) farkın büyük kısmı tartışmalıdır.
Farklılıklar sadece "iyi" performansa ulaşıyorsanız biraz daha fazla değişir - Integer
vs. gibi küçük nesnelerle çok fazla şey yapamamak int
, özellikle jeneriklerle etkileşime girme şekliyle bir PITA'nın biraz daha fazlası olabilir. . Java ile merkezi bir optimizasyon hedef olarak sadece yapım bir jenerik veri yapısına biraz daha zor olduğunu için çalışmalar int
, float
vb olanlar daha büyük ve pahalı UDT'leri kaçınarak, ama genellikle en performansa kritik alanlar kendi veri yapılarını elde sarılan gereksinim duyacaktır zaten çok özel bir amaç için ayarlanmış, bu yüzden sadece iyi performans için çabalayan ancak en yüksek performans için değil kod için can sıkıcı.
Nesne Yükü
Java nesnesinin ek yükünün (ilk veri döngüsünden sonra meta veriler ve uzamsal konum kaybı ve geçici geçici konum kaybı ) milyonlarca kişi tarafından milyonlarca tarafından depolanan int
ve gerçekten küçük olan şeyler için genellikle büyük Integer
olduğunu unutmayın. büyük ölçüde bitişiktir ve çok sıkı döngülerle erişilir. Bu konu hakkında çok fazla duyarlılık var gibi görünüyor, bu yüzden görüntüler gibi büyük nesneler için yükü, sadece tek bir piksel gibi çok küçük nesneler için nesne yükü hakkında endişelenmek istemediğinizi açıklığa kavuşturmak zorundayım.
Herkes bu bölümü hakkında şüpheli hissederse, ben bir milyon rasgele özetliyor arasında bir kriter yapma öneririm ints
bir milyon rasgele VS. Integers
ve (tekrar tekrar bunu yapmak için Integers
bir ilk GC döngüsünden sonra bellekte değişiklik yapacağım).
Ultimate Trick: Optimize Etmek İçin Oda Bırakan Arayüz Tasarımları
Yani, küçük nesneler üzerinde ağır bir yük işleyen bir yerle uğraşıyorsanız gördüğüm gibi nihai Java hilesi (örneğin: a Pixel
, 4-vektör, 4x4 matris, a Particle
, muhtemelen Account
sadece birkaç küçük varsa alanlar) bu ufacık şeyler için nesneler kullanmaktan kaçınmak ve düz eski verilerin dizilerini (muhtemelen birlikte zincirlenmiş) kullanmaktır. Gibi daha sonra toplama arayüzleri haline nesnelerin Image
, ParticleSystem
, Accounts
, Bireysel olanları endeksi ulaşılabilir vb matrisleri veya vektörlerin, koleksiyonu, örneğin Bu aynı zamanda C ve C nihai tasarım hileler ++ biridir çünkü bu bile temel nesne yükü olmadan ve ayrık bellek, arayüzü tek bir parçacık düzeyinde modellemek en verimli çözümleri önler.