C / C ++ ile karşılaştırıldığında Java'nın “tweak” performansı daha mı zor? [kapalı]


11

JVM'nin "büyüsü", bir programcının Java'daki mikro optimizasyonlar üzerindeki etkisini engelliyor mu? Son zamanlarda C ++ 'da bazen veri üyelerinin sıralaması optimizasyonlar (mikrosaniye ortamında verilen) sağlayabilir ve Java'dan performans sıkma söz konusu olduğunda bir programcının elleri bağlı olduğunu varsayalım?

Ben iyi bir algoritma daha büyük hız kazanımları sağlar takdir, ama bir kez doğru algoritma var Java JVM kontrolü nedeniyle tweak zor?

Değilse, insanlar Java'da hangi hileleri kullanabileceğinize dair örnekler verebilirler (basit derleyici bayraklarının yanı sıra).


14
Tüm Java optimizasyonunun arkasındaki temel prensip şudur: JVM muhtemelen bunu zaten yapabileceğinizden daha iyi yaptı. Optimizasyon çoğunlukla mantıklı programlama uygulamalarını izlemeyi ve bir döngüde dizeleri birleştirmek gibi olağan şeylerden kaçınmayı içerir.
Robert Harvey

3
Tüm dillerde mikro-optimizasyon prensibi derleyicinin bunu zaten yapabileceğinizden daha iyi yapmış olmasıdır. Tüm dillerde mikro optimizasyonun diğer prensibi, üzerine daha fazla donanım atmanın programcının mikro optimizasyon zamanından daha ucuz olmasıdır. Programcı ölçekleme problemlerine eğilimlidir (optimal olmayan algoritmalar), ancak mikro optimizasyon zaman kaybıdır. Bazen mikro optimizasyon, üzerine daha fazla donanım atamadığınız gömülü sistemlerde mantıklıdır, ancak Java kullanan Android ve oldukça zayıf bir uygulaması, çoğunun zaten yeterli donanıma sahip olduğunu gösterir.
Jan Hudec

1
"Java performans hileleri" için, çalışmaya değer: Etkili Java , Angelika Langer Bağlantıları - Java Java teorisi ve pratiği ve burada listelenen Hafif Diş dizisi Brian Goetz tarafından performans ve performans ile ilgili makaleler
gnat

2
İpuçları ve püf noktaları konusunda son derece dikkatli olun - JVM, işletim sistemleri ve donanım işlemleri - performans ayarlama metodolojisini öğrenmek ve kendi ortamınız için iyileştirmeler uygulamak en iyisidir :-)
Martijn Verburg

Bazı durumlarda, bir VM derleme zamanında yapılması pratik olmayan çalışma zamanında optimizasyonlar yapabilir. Yönetilen belleği kullanmak performansı artırabilir, ancak genellikle daha yüksek bellek alanına sahiptir. Kullanılmayan bellek uygun olduğunda ASAP yerine serbest bırakılır.
Brian

Yanıtlar:


5

Elbette, mikro optimizasyon düzeyinde JVM, özellikle C ve C ++ ile karşılaştırıldığında çok az kontrole sahip olacağınız bazı şeyler yapacak.

Öte yandan, C ve C ++ ile derleyici davranışlarının çeşitliliği, mikro-optimizasyonları her türlü belirsiz taşınabilir şekilde (derleyici revizyonlarında bile) yapabilme yeteneğiniz üzerinde çok daha olumsuz bir etkiye sahip olacaktır.

Ne tür bir projeyi değiştirdiğinize, hangi ortamları hedeflediğinize vb. Bağlıdır. Ve sonuç olarak, algoritmik / veri yapısı / program tasarımı optimizasyonlarından birkaç büyüklükte daha iyi sonuç aldığınız için gerçekten önemli değil.


Uygulamanızın çekirdekler arasında ölçeklenmediğini fark ettiğinizde çok önemli olabilir
James

@james - özen göstermek ister misiniz?
Telastyn

1
Başlangıç ​​için buraya bakın: mechanical-sympathy.blogspot.co.uk/2011/07/false-sharing.html
James

1
@James, çekirdekler arasında ölçeklendirmenin uygulama diliyle (Python hariç!) Ve uygulama mimarisiyle daha fazla ilgisi yoktur.
James Anderson

29

Mikro optimizasyonlar neredeyse hiç zaman ayırmaya değmez ve neredeyse tüm kolay olanlar derleyiciler ve çalışma zamanları tarafından otomatik olarak yapılır.

Bununla birlikte, C ++ ve Java'nın temelde farklı olduğu önemli bir optimizasyon alanı vardır ve bu da toplu bellek erişimidir. C ++ manuel bellek yönetimine sahiptir, bu da önbellekleri tam olarak kullanmak için uygulamanın veri düzenini ve erişim modellerini optimize edebileceğiniz anlamına gelir. Bu oldukça zordur, üzerinde çalıştığınız donanıma özgüdür (bu nedenle performans kazançları farklı donanımlarda kaybolabilir), ancak doğru yapılırsa, kesinlikle nefes kesici bir performansa yol açabilir. Elbette bunun için her türlü korkunç hata potansiyeli ile ödeme yaparsınız.

Java gibi çöp toplanmış bir dilde bu tür optimizasyonlar kodda yapılamaz. Bazıları çalışma zamanı tarafından yapılabilir (otomatik olarak veya yapılandırma yoluyla, aşağıya bakın) ve bazıları mümkün değildir (bellek yönetimi hatalarından korunmak için ödediğiniz fiyat).

Değilse, insanlar Java'da hangi hileleri kullanabileceğinize dair örnekler verebilirler (basit derleyici bayraklarının yanı sıra).

Java derleyicisi neredeyse hiç optimizasyon yapmadığından derleyici bayrakları Java ile ilgisizdir; çalışma zamanı yapar.

Gerçekten de Java çalışma zamanlarında , özellikle çöp toplayıcıyla ilgili olarak değiştirilebilecek çok sayıda parametre vardır. Bu seçenekler hakkında "basit" bir şey yoktur - varsayılanlar çoğu uygulama için iyidir ve daha iyi performans elde etmek için seçeneklerin tam olarak ne yaptığını ve uygulamanızın nasıl davrandığını anlamanız gerekir.


1
+1: temelde cevabımda yazdıklarım, belki daha iyi formülasyon.
Klaim

1
+1: Çok iyi noktalar, çok kısa bir şekilde açıkladı: "Bu oldukça zor ... ama doğru yapılırsa, kesinlikle nefes kesici bir performansa yol açabilir. Tabii ki bunun için her türlü korkunç hata potansiyeli ile ödeme yaparsınız. ."
Giorgio

1
@MartinBa: Bellek yönetimini optimize etmek için daha fazla ödersiniz. Bellek yönetimini optimize etmeye çalışmazsanız, C ++ bellek yönetimi o kadar da zor değildir (tamamen STL ile kaçının veya RAII kullanarak nispeten kolaylaştırın). Tabii ki, C ++ 'da RAII uygulamak, Java'da hiçbir şey yapmamaktan daha fazla kod satırı alır (yani, Java bunu sizin için halleder).
Brian

3
@Martin Ba: Temel olarak evet. Sarkan işaretçiler, arabellek taşmaları, başlatılmamış işaretçiler, işaretçi aritmetiğindeki hatalar, manuel bellek yönetimi olmadan var olmayan her şey. Ve bellek erişimi optimize hemen hemen yapmak gerektiren bir çok manuel bellek yönetiminin.
Michael Borgwardt

1
Java'da yapabileceğiniz birkaç şey var. Birincisi, nesnelerin bellek konumlarını en üst düzeye çıkaran nesne havuzlamasıdır (C ++ 'dan farklı olarak bellek yerini garanti edebilir).
RokL

5

[...] (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 Pixelnesne 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, Pixelnesneden kaçınırsanız, bir bayt dizisi kullandıysanız ve bir Imagenesneyi 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 intyerine Integer) ve benzeri toplu arayüzleri modelini oluşturmaya başlamak Imagearayüz değil Pixelarayü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 intbitişik temsili garanti eder. Bir dizi Integerdeğ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 Integerbellekte 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 bileIntegersbellekte birbirinin yanındaydı, sadece Integer4 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 Booleandaha büyüktür boolean(ancak tekdüze yansıma yararları ve finalher 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 - Integervs. 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, floatvb 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 intve gerçekten küçük olan şeyler için genellikle büyük Integerolduğ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 intsbir milyon rasgele VS. Integersve (tekrar tekrar bunu yapmak için Integersbir 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 Accountsadece 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.


1
Yığındaki kötü performansın aslında kritik alanlarda en yüksek performansı ezmek için iyi bir şansı olabileceğini düşünürsek, birinin kolayca iyi performansa sahip olma avantajını tamamen göz ardı edebileceğini düşünmüyorum. Orijinal yapılardan birini içeren tüm (veya neredeyse tüm) değerlere aynı anda erişildiğinde, bir dizi diziyi dizilerin bir yapısına dönüştürme hilesi bir şekilde parçalanır. BTW: Gördüğüm kadarıyla çok sayıda eski yazı var ve kendi iyi cevabınızı, bazen de iyi cevabı ekliyorsunuz ;-)
Deduplicator

1
@Deduplicator Umarım çok fazla çarparak insanları rahatsız etmem! Bu biraz ufacık biraz ranty var - belki biraz geliştirmeliyim. SoA ve AoS genellikle benim için zor bir süreçtir (sıralı veya rastgele erişim). Nadiren hangisini kullanmam gerektiğini biliyorum, çünkü benim durumumda sıklıkla sıralı ve rastgele erişimin bir karışımı var. Sıklıkla öğrendiğim değerli ders, veri gösterimi ile oynamak için yeterli alan bırakan arayüzler tasarlamaktır - mümkünse büyük dönüşüm algoritmalarına sahip tür bulkier arayüzler (bazen buraya rastgele erişilen ufacık bitlerle mümkün değildir).

1
Sadece farkettim çünkü işler gerçekten yavaş. Ve her biriyle zamanımı aldım.
Tekilleştirici

Gerçekten neden user204677gittiğini merak ediyorum . Harika bir cevap.
oligofren

3

Bir yanda mikro optimizasyon, diğer yanda iyi algoritma seçimi arasında bir orta alan var.

Sabit faktörlü hızlanmaların alanıdır ve büyüklük dereceleri verebilir.
Bunu yapmanın yolu, ilk 30%, sonra kalanların% 20'si, daha sonra bunun% 50'si gibi yürütme süresinin tüm bölümlerini kesmektir.

Bunu küçük demo tarzı programlarda görmüyorsunuz. Gördüğünüz yer, çağrı yığınının tipik olarak çok sayıda katman derin olduğu çok sayıda sınıf veri yapısına sahip büyük ciddi programlarda. Hızlandırma fırsatlarını bulmanın iyi bir yolu , programın durumundan rastgele zaman örneklerini incelemektir .

Genellikle hızlanma aşağıdaki gibi şeylerden oluşur:

  • neweski nesneleri bir araya getirerek ve yeniden kullanarak çağrıları en aza indirgemek ,

  • aslında gerekli olmaktan ziyade, genellik adına orada yapılan şeyleri kabul ederek,

  • aynı big-O davranışına sahip ancak gerçekte kullanılan erişim kalıplarından yararlanan farklı toplama sınıflarını kullanarak veri yapısını gözden geçirmek,

  • işlevi yeniden çağırmak yerine işlev çağrıları tarafından elde edilen verileri kaydetme, (Programcıların daha kısa adlara sahip işlevlerin daha hızlı çalıştığını varsaymak doğal ve eğlenceli bir eğilimdir.)

  • gereksiz veri yapıları arasında bildirim olaylarıyla tamamen tutarlı tutmaya çalışmak yerine belirli bir tutarsızlığı tolere etmek,

  • vesaire vesaire.

Ama elbette, bunların hiçbiri, ilk önce örnek alınarak sorun olduğu gösterilmeden yapılmamalıdır.


2

Java (bildiğim kadarıyla), bellekteki değişken konumlar üzerinde hiçbir kontrol sağlamaz, böylece yanlış paylaşım ve değişkenlerin hizalanması gibi şeylerden kaçınmak için daha zor zamanınız olur (kullanılmayan üyelerle bir sınıfı doldurabilirsiniz). Faydalanabileceğinizi düşünemediğim başka bir şey gibi talimatlar mmpause, ancak bunlar CPU'ya özgüdür ve eğer ihtiyacınız olduğunu düşünüyorsanız Java kullanmak için dil olmayabilir.

Orada var olan Güvensiz değil, aynı zamanda C / C ++ 'tehlikesi ile size C / C ++ esnekliğini verir sınıfı.

JVM'nin kodunuz için oluşturduğu montaj koduna bakmanıza yardımcı olabilir

Bu tür ayrıntılara bakan bir Java uygulaması hakkında bilgi edinmek için LMAX tarafından yayınlanan Disruptor koduna bakın


2

Bu soruya cevap vermek çok zor çünkü dil uygulamalarına bağlı.

Genel olarak bu günlerde bu tür "mikro optimizasyonlar" için çok az yer var. Bunun ana nedeni derleyicilerin derleme sırasında bu optimizasyonlardan faydalanmasıdır. Örneğin semantiklerinin aynı olduğu durumlarda artış öncesi ve artış sonrası operatörleri arasında performans farkı yoktur. Başka bir örnek, örneğin for(int i=0; i<vec.size(); i++),size()her yineleme sırasında üye işlevi, döngüden önce vektörün boyutunu elde etmek ve daha sonra bu tek değişkenle karşılaştırmak ve böylece yineleme başına bir çağrıdan kaçınmak daha iyi olacaktır. Ancak, bir derleyicinin bu aptal durumu algılayacağı ve sonucu önbelleğe alacağı durumlar vardır. Bununla birlikte, bu sadece işlevin hiçbir yan etkisi olmadığında ve derleyici, döngü sırasında vektör boyutunun sabit kaldığından emin olabilir, bu yüzden sadece oldukça önemsiz durumlar için geçerlidir.


İkinci durumda ise, derleyicinin öngörülebilir gelecekte onu optimize edebileceğini düşünmüyorum. Vec.size () yöntemini optimize etmenin güvenli olduğunu saptamak, vektör / kayıp döngü içinde değişmezse, bunun durma problemi nedeniyle kararsız olduğuna inanıyorum.
Yalan Ryan

@LieRyan Sonuç el ile "önbelleğe alınmış" ve size () çağrılmışsa derleyicinin tam olarak aynı ikili dosyayı oluşturduğu çok sayıda (basit) vaka gördüm. Bazı kod yazdım ve bu davranış, programın çalışma biçimine oldukça bağlı olduğu ortaya çıkıyor. Derleyicinin, döngü sırasında vektör boyutunun değişme olasılığının olmadığını garanti edebileceği durumlar vardır ve daha sonra, söz konusu gibi durma problemine çok benzer şekilde garanti edemeyeceği durumlar vardır. Şimdilik
iddiamı

2
@Lie Ryan: Genel durumda karar verilemeyen birçok şey, spesifik ancak yaygın durumlar için mükemmel bir şekilde karar verilebilir ve burada gerçekten ihtiyacınız olan her şey budur.
Michael Borgwardt

@LieRyan constBu vektörde sadece yöntemleri çağırırsanız , pek çok optimize edici derleyicinin bunu anlayacağından eminim.
K.Steff

C #, ve ben de Java okudum düşünüyorum, eğer önbellek boyutu yoksa derleyici dizi sınırları dışında gidip gitmediğini görmek için kontrolleri kaldırabilir bilir ve önbellek boyutu yaparsanız kontrolleri yapmak zorunda önbelleğe alarak tasarruf ettiğinizden genellikle daha pahalıya mal olur. Optimize edicilerin altını çizmeye çalışmak nadiren iyi bir plan.
Kate Gregory

1

insanlar Java'da hangi hileleri kullanabileceğinize dair örnekler verebilirler (basit derleyici bayrakları dışında).

Algoritmaların geliştirilmesinden başka, bellek hiyerarşisini ve işlemcinin bunu nasıl kullandığını dikkate aldığınızdan emin olun . Söz konusu dilin veri türlerine ve nesnelerine nasıl bellek ayırdığını anladıktan sonra, bellek erişim gecikmelerini azaltmanın büyük faydaları vardır.

1000x1000 inçlik bir diziye erişmek için Java örneği

Aşağıdaki örnek kodu göz önünde bulundurun - aynı bellek alanına (1000x1000 inçlik bir dizi), ancak farklı bir sırayla erişir. Mac mini'imde (Core i7, 2,7 GHz) çıkış aşağıdaki gibidir, bu da diziyi satırlar halinde geçirmenin performansı iki kattan fazla artırdığını gösterir (her biri ortalama 100 turdan fazla).

Processing columns by rows*** took 4 ms (avg)
Processing rows by columns*** took 10 ms (avg) 

Bunun nedeni, dizinin ardışık sütunların (yani int değerlerinin) belleğe bitişik yerleştirileceği, ancak ardışık satırların olmayacağı şekilde depolanmasıdır. İşlemcinin verileri gerçekten kullanabilmesi için, önbelleklerine aktarılması gerekir. Bellek aktarımı, önbellek hattı adı verilen bir bayt bloğudur - önbellek hattını doğrudan bellekten yüklemek gecikmeleri beraberinde getirir ve böylece bir programın performansını düşürür.

Core i7 (kumlu köprü) için bir önbellek satırı 64 bayt tutar, böylece her bellek erişimi 64 bayt alır. İlk test, belleğe öngörülebilir bir sırada eriştiğinden, işlemci gerçekte program tarafından tüketilmeden önce verileri getirir. Genel olarak, bu, bellek erişimlerinde daha az gecikme ile sonuçlanır ve böylece performansı artırır.

Örnek kod:

  package test;

  import java.lang.*;

  public class PerfTest {
    public static void main(String[] args) {
      int[][] numbers = new int[1000][1000];
      long startTime;
      long stopTime;
      long elapsedAvg;
      int tries;
      int maxTries = 100;

      // process columns by rows 
      System.out.print("Processing columns by rows");
      for(tries = 0, elapsedAvg = 0; tries < maxTries; tries++) {
       startTime = System.currentTimeMillis();
       for(int r = 0; r < 1000; r++) {
         for(int c = 0; c < 1000; c++) {
           int v = numbers[r][c]; 
         }
       }
       stopTime = System.currentTimeMillis();
       elapsedAvg += ((stopTime - startTime) - elapsedAvg) / (tries + 1);
      }

      System.out.format("*** took %d ms (avg)\n", elapsedAvg);     

      // process rows by columns
      System.out.print("Processing rows by columns");
      for(tries = 0, elapsedAvg = 0; tries < maxTries; tries++) {
       startTime = System.currentTimeMillis();
       for(int c = 0; c < 1000; c++) {
         for(int r = 0; r < 1000; r++) {
           int v = numbers[r][c]; 
         }
       }
       stopTime = System.currentTimeMillis();
       elapsedAvg += ((stopTime - startTime) - elapsedAvg) / (tries + 1);
      }

      System.out.format("*** took %d ms (avg)\n", elapsedAvg);     
    }
  }

1

JVM müdahale edebilir ve sıklıkla müdahale edebilir ve JIT derleyicisi sürümler arasında önemli ölçüde değişebilir Java'da hiper iş parçacığı dostu veya en yeni Intel işlemcilerin SIMD koleksiyonu gibi dil sınırlamaları nedeniyle bazı mikro optimizasyonlar imkansızdır.

Disruptor yazarlarından birinin konuyla ilgili oldukça bilgilendirici bir blogun okunması önerilir:

Mikro optimizasyonlar istiyorsanız, Java'yı neden rahatsız ettiğiniz her zaman sorulmalıdır, yerel bir kütüphaneye geçmek için JNA veya JNI kullanmak gibi bir işlevin hızlandırılması için birçok alternatif yöntem vardır.

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.