Modern derleyicilerde işlev çağrısı maliyetleri ne zaman önemlidir?


95

Ben dindar biriyim ve günah işlememek için çaba sarf ediyorum. Bu yüzden Küçük Yazma eğilimindeyim ( ondan daha küçük , Robert C. Martin'i özetlemek için) İşlevleri, Temiz Kod İncilinin sipariş ettiği çeşitli emirlere uymak için kullanır . Bazı şeyler kontrol ederken Ama indi bu yazı bu görüşü okumak aşağıda:

Bir yöntem çağrısının ücretinin, dile bağlı olarak önemli olabileceğini unutmayın. Okunabilir kod yazmak ile performans kodu yazmak arasında hemen hemen her zaman bir sapma vardır.

Zengin performans gösteren modern derleyiciler endüstrisi göz önüne alındığında, bu alıntı hangi şartlar altında bugünlerde hala geçerlidir?

Bu benim tek sorum. Ve bu uzun veya küçük işlevler yazmam gerekip gerekmediği ile ilgili değil. Sadece görüşlerinizin, tutumumu değiştirmeme katkıda bulunmayacağına ve beni küfürlerin cezbedilmesine karşı koyamayacağımı vurguluyorum .


11
Okunabilir ve bakımı yapılabilir bir kod yazın. Yalnızca yığın taşması ile ilgili bir sorunla karşılaştığınızda, spproach'ınızı yeniden düşünebilirsiniz
Fabio

33
Burada genel bir cevap imkansızdır. Çok fazla farklı dil spesifikasyonu uygulayan çok fazla farklı derleyici var. Ve sonra JIT derlenmiş diller, dinamik olarak yorumlanmış diller vb. Var. Ancak, yerel C veya C ++ kodunu modern bir derleyici ile derliyorsanız, bir işlev çağrısının maliyetleri konusunda endişelenmenize gerek yoktur. Optimize edici, uygun olduğunda bunları satır içi yapar. Bir mikro-optimizasyon meraklısı olarak, nadiren nadiren ben ya da kıyaslama ölçütlerimin uyuşmadığı kararlar veren kararlar alırım.
Cody Gray

6
Kişisel deneyimlerden bahseden, kod bakımından yetenek açısından oldukça modern olan özel bir dilde kod yazıyorum, ancak fonksiyon çağrıları döngü için tipik bile olsa hız için optimize edilmek zorunda olduğu noktaya kadar saçma: pahalı for(Integer index = 0, size = someList.size(); index < size; index++)değil for(Integer index = 0; index < someList.size(); index++). Derleyicinizin son birkaç yıl içinde yapılmış olması, mutlaka profillemeden vazgeçmeniz anlamına gelmez.
phyrfox

5
Sadece mantıklı olan, her seferinde döngü boyunca çağırmak yerine, loop dışındaki bazı List.size () değerlerini elde etmek. Bu, özellikle okuyucuların ve yazarların yineleme sırasında çakışmaya çalışabilecekleri bir senkronizasyon sorunu olması durumunda geçerlidir; bu durumda listeyi yineleme sırasında değişikliklere karşı korumak isteyebilirsiniz.
Craig

8
Küçük işlevleri çok uzak tutmaya dikkat edin, monolitik bir mega fonksiyonun yaptığı kadar etkili bir şekilde kodu gizleyebilir. Bana inanmıyorsanız, ioccc.org kazananlarının bazılarına göz atın: Bazıları her şeyi tek bir kod olarak kodlar main(), bazıları ise her şeyi küçük 50 küçük fonksiyona böler ve hepsi tamamen okunmaz olur. İşin püf noktası, her zaman olduğu gibi, iyi bir denge sağlamak .
cmaster

Yanıtlar:


148

Etki alanınıza bağlı.

Düşük güçlü mikrodenetleyici için kod yazıyorsanız, yöntem çağrısı maliyeti önemli olabilir. Ancak normal bir web sitesi veya uygulama oluşturuyorsanız, diğer yöntemlere kıyasla yöntem çağrısı maliyeti ihmal edilebilir. Bu durumda, yöntem çağrıları gibi mikro optimizasyonlar yerine her zaman doğru algoritmalara ve veri yapılarına odaklanmaya değer olacaktır.

Ve ayrıca sizin için yöntemleri sıralayan bir derleyici sorusu var. Çoğu derleyici, mümkün olan yerlerde işlevlerin satır içi olmasına yetecek kadar akıllıdır.

Ve son olarak, altın performans kuralı var: HER ZAMAN PROFİL İLK. Varsayımlara dayalı olarak "optimize edilmiş" kod yazmayın. Eğer uygun değilseniz, her iki vakayı da yazın ve hangisinin daha iyi olduğunu görün.


13
Ve örneğin HotSpot derleyicisi , bir şekilde mümkün olmadığında bile inlining olan Spekülatif İnlinleme işlemini gerçekleştirir .
Jörg W Mittag

49
Aslında, bir web uygulamasında, tüm kod muhtemelen DB erişimi ve ağ trafiği ile ilgili olarak önemsiz ...
AnoE

72
Aslında, optimizasyonun ne anlama geldiğini çok az bilen çok eski bir derleyiciye sahip gömülü ve çok düşük güçlüyüm ve işlevsellik olsa bile, optimizasyon için bakılacak ilk yer olmadığına inanıyor. Bu niş alanda bile, kod kalitesi bu durumda ilk sırada yer almaktadır.
Tim

2
@Mehrdad Bu durumda bile, kodda optimize etmek için daha uygun bir şey olmasa şaşırırdım. Kodları profillerken, işlevlerin çağırdığı işlevlerden çok daha ağır şeyler görüyorum ve optimizasyon aramanın uygun olduğu yer burası. Bazı geliştiriciler bir ya da iki geliştirilmemiş LOC için çıldırıyor, ancak SW'yi profilden çıkardığınızda tasarımın bundan daha önemli olduğunu, en azından kodun en büyük kısmı için farkediyorsunuz. Darboğazı bulduğunuzda, onu optimize etmeyi deneyebilirsiniz ve aramaları önlemek için büyük işlevler yazmak gibi düşük seviyeli keyfi optimizasyondan çok daha fazla etkiye sahip olacaktır.
Tim

8
İyi cevap! Son noktanız ilk önce olmalıdır: Her zaman nereye optimize edeceğinize karar vermeden önce profil yapın .
CJ Dennis

56

İşlev çağrısı ek yükü tamamen dile ve hangi düzeyde iyileştirdiğinize bağlıdır.

Çok düşük bir seviyede, işlev çağrıları ve hatta sanal yöntem çağrıları, dal yanlışlanmasına ya da CPU önbellek özlüyorsa yol açarsa maliyetli olabilir. Bir montajcı yazdıysanız , bir arama çevresindeki kayıtları kaydetmek ve geri yüklemek için birkaç ek yönergeye ihtiyacınız olduğunu da bileceksiniz. “Yeterince akıllı” bir derleyicinin bu ek yükten kaçınmak için doğru işlevleri sıralayabileceği doğru değildir, çünkü derleyiciler dilin semantiği ile sınırlıdır (özellikle arayüz yöntemi gönderme veya dinamik olarak yüklenmiş kütüphaneler gibi özellikler etrafında).

Yüksek düzeyde, Perl, Python, Ruby gibi diller, işlev çağrısı başına çok fazla defter tutma yaparak bunları nispeten maliyetli hale getirir. Bu meta programlama ile daha da kötüleşiyor. Bir kez sadece bir çok sıcak döngüden fonksiyon çağrıları kaldırarak bir Python yazılımı 3x hızlandırdı. Performans açısından kritik kodda, yardımcı yardımcı işlevler dikkat çekici bir etkiye sahip olabilir.

Ancak, yazılımın büyük bir çoğunluğu, işlev çağrısının genel giderini görebilmeniz için performans açısından çok kritik değildir. Her durumda, temiz, basit kod yazmak ödedi:

  • Kodunuz performans açısından kritik değilse, bakımı kolaylaştırır. Performans açısından kritik yazılımlarda bile, kodun çoğunluğu “etkin nokta” olmayacaktır.

  • Kodunuz performans açısından kritikse, basit kod, kodun anlaşılmasını ve optimizasyon için nokta fırsatlarını kolaylaştırır. En büyük kazançlar genellikle satır içi işlevler gibi mikro optimizasyonlardan değil, algoritmik gelişmelerden gelir. Veya farklı ifadeler: aynı şeyi daha hızlı yapmayın. Daha az yapmanın bir yolunu bulun.

“Basit kodun” “bin küçük fonksiyona çarpan” anlamına gelmediğine dikkat edin. Her fonksiyon aynı zamanda bir miktar bilişsel ek yükü de beraberinde getirir - daha soyut kod hakkında düşünmek daha zordur . Bir noktada, bu küçük işlevler o kadar az şey yapabilir ki, onları kullanmamak kodunuzu kolaylaştıracaktır.


16
Gerçekten akıllı bir DBA bana bir keresinde “Canı yanana kadar normalleş, sonra yapamayana kadar denormalile” dedi. Bana öyle geliyor ki, "Acıyana kadar yöntemleri çıkart, sonra da satmayacak olana kadar ayıkla."
RubberDuck

1
Bilişsel ek yüke ek olarak, hata ayıklayıcı bilgilerinde sembolik ek yük vardır ve genellikle son ikili dosyalarda ek yük kaçınılmazdır.
Frank Hileman

Akıllı derleyicilerle ilgili olarak - bunu her zaman değil, yapabilirler. Örneğin, jvm, alışılmadık yol için çok ucuz / ücretsiz tuzağa sahip çalışma zamanı profilini temel alan veya satır içi polimorfik işlevi olan şeyleri satır içi olarak verebilir, verilen yöntem / arabirimin yalnızca bir uygulaması vardır ve ardından yeni alt sınıf dinamik olarak yüklendiğinde bu çağrıyı düzgün şekilde polimorfik hale getirir. Çalışma süresi. Fakat evet, böyle şeylerin mümkün olmadığı birçok dil vardır ve genel olarak uygun maliyetli veya mümkün olmadığı durumlarda jvm'de bile birçok durum vardır.
Artur Biesiadowski

19

Neredeyse performans kodunu ayarlama ile ilgili atasözleri Amdahl yasasının özel durumlarıdır . Amdahl yasasının kısa, esprili ifadesi

Programınızın tek parça çalışma zamanı% 5'ini alır ve şimdi alır, böylece bu parça optimize Eğer sıfır çalışma zamanının yüzde bir bütün olarak programın sadece% 5 daha hızlı olacaktır.

(Çalışma süresinin yüzde sıfırına kadar işleri optimize etmek tamamen mümkündür: Büyük, karmaşık bir programı optimize etmek için oturduğunuzda, çalışma zamanının en azından bir kısmını hiç yapması gerekmeyen şeyler için harcadığını görmeniz oldukça muhtemeldir. .)

Bu nedenle insanlar normalde işlev çağrısı ücretlerinden endişe etmemelerini söyler: ne kadar pahalı olursa olsun, normal olarak bir bütün olarak program, çalışma süresinin küçük bir kısmını harcama ek yüküne harcar, bu yüzden onları hızlandırmak pek yardımcı olmaz .

Ancak, tüm işlev çağrılarını daha hızlı yapan bir numara varsa , o numara buna değecektir. Derleyici geliştiricileri, "prologlar" ve "epiloglar" fonksiyonlarını optimize etmek için çok fazla zaman harcıyorlar, çünkü bu , her biri için küçük bir parça olsa bile, bu derleyici ile derlenen tüm programlara fayda sağlıyor .

Ve, bir program inanmak için nedenlerimiz varsa edilir sadece işlev arama yapmak onun zamanında bir sürü harcama, o zaman bu işlev çağrılarının bazı gereksiz olup olmadığı düşünmeye başlamalıdır. Bunu ne zaman yapmanız gerektiğini bilmek için bazı kurallar şunlardır:

  • Bir işlevin çağırma başına çalışma zamanı bir milisaniyeden azsa, ancak bu işleve yüz binlerce kez denirse, büyük olasılıkla satır içi olmalıdır.

  • Programın bir profili binlerce işlev gösteriyorsa ve bunların hiçbiri çalışma zamanının% 0.1'inden fazlasını almıyorsa, işlev çağrısı ek yükü büyük olasılıkla toplamda önemlidir.

  • Lazanya koduna sahipseniz , bir sonraki katmana göndermenin ötesinde herhangi bir işi yapmayan pek çok soyutlama katmanı varsa ve bu katmanların tümü sanal yöntem çağrılarıyla uygulanırsa, CPU'nun boşa harcanması ihtimali de vardır. dolaylı dallı boru hattı tezgahlarında çok fazla zaman. Ne yazık ki, bunun tek çare, genellikle çok zor olan bazı katmanlardan kurtulmaktır.


7
Sadece iç içe döngüler içinde yapılan pahalı şeylere dikkat edin. Bir işlevi optimize ettim ve 10 kat daha hızlı çalışan bir kod aldım. Bu, profilerin suçluyu işaret etmesinden sonraydı. (O, n (3) 'den küçük (n ^ 6)' ye kadar olan döngüler halinde tekrar tekrar çağrıldı.)
Loren Pechtel

“Maalesef, bunun tek çare, genellikle çok zor olan bazı katmanlardan kurtulmak.” - bu sizin dil derleyicinize ve / veya sanal makine teknolojisine bağlıdır. Derleyicinin satır içi yapmasını kolaylaştırmak için kodu değiştirebilirseniz (örneğin final, Java'da uygulanabilir durumlarda sınıfları ve yöntemleri kullanarak veya virtualC # veya C ++' da olmayan yöntemleri kullanarak ), dolaylı olarak derleyici / çalışma zamanı ve Büyük yeniden yapılandırma olmadan bir kazanç göreceğim. @JorgWMittag'ın işaret ettiği gibi, JVM, optimizasyonun kanıtlanamadığı durumlarda bile satır içi olabilir ...
Jules

... geçerli, bu yüzden katmanlamaya rağmen yine de kodunuzda yapıyor olabilir.
Jules

O JIT derleyicileri doğru olsa @Jules olabilir spekülatif optimizasyonu yapmak, bu tür optimizasyonlar anlamına gelmez edilir tekdüze uyguladı. Spesifik olarak Java ile ilgili deneyimim, geliştirici kültürünün, son derece derin arama yığınlarına yol açan katmanların üst üste yığılmış katmanları desteklemesidir. Anekdotally, bu birçok Java uygulamasının halsiz, şişirilmiş hissine katkıda bulunuyor. Bu tür çok katmanlı mimari, katmanların teknik olarak inline edilebilir olup olmadığına bakılmaksızın, JIT çalışma zamanına karşı çalışır. JIT, yapısal sorunları otomatik olarak çözebilecek sihirli bir mermi değil.
amon

@ amon "lazanya kodu" ile olan deneyimim, derin iç içe geçmiş nesne hiyerarşileri ve COM'un moda olduğu 1990'lı yıllara dayanan çok sayıda C ++ uygulamasından geliyor. C ++ derleyicileri, bu gibi programlardaki soyutlama cezalarını ortadan kaldırmak için oldukça kahramanca çaba harcıyor ve hala dolaylı dallı boru hattı tezgahlarında (ve I-cache özlümlerinde bir başka önemli yığın) duvar saati çalışma zamanının önemli bir kısmını harcadıklarını görebilirsiniz. .
zwol

17

Bu alıntıya meydan okuyacağım:

Okunabilir kod yazmak ile performans kodu yazmak arasında hemen hemen her zaman bir sapma vardır.

Bu gerçekten yanıltıcı bir ifade ve potansiyel olarak tehlikeli bir tutumdur. Bir takas yapmanız gereken bazı özel durumlar vardır , ancak genel olarak iki faktör bağımsızdır.

Gerekli bir tradeoff örneği, daha karmaşık fakat daha performansa karşı basit bir algoritmaya sahip olmanızdır. Ayrılabilir bir uygulama, bağlantılı bir liste uygulamasından açıkça daha karmaşıktır, ancak arama daha yavaş olacaktır, bu nedenle performans için basitlik (bu da okunabilirliği olan bir faktördür) ticareti yapmanız gerekebilir.

İşlev çağrısı ek yükü ile ilgili olarak, özyinelemeli bir algoritmayı yinelemeye dönüştürmek, algoritmaya ve dile bağlı olarak önemli bir fayda sağlayabilir. Ancak bu yine çok özel bir senaryodur ve genel olarak işlev çağrılarının ek yükü ihmal edilebilir veya en aza indirilecektir.

(Python gibi bazı dinamik dillerde önemli bir yöntem çağrısı ek yükü vardır. Ancak, performans bir sorun haline gelirse, muhtemelen Python'u ilk başta kullanmamalısınız.)

Okunabilir kod için çoğu ilke - tutarlı biçimlendirme, anlamlı tanımlayıcı adları, uygun ve yararlı yorumlar vb. Performans üzerinde hiçbir etkisi yoktur. Ve bazıları - karakter dizileri yerine enums kullanmak gibi - performans avantajları da vardır.


5

İşlev çağrısı ek yükü çoğu durumda önemsizdir.

Bununla birlikte, satır içi koddan daha büyük kazanç, satır içi işlemden sonra yeni kodu en iyi duruma getirmektir .

Örneğin, sürekli bir argüman olan bir işlevi çağırırsanız, en iyileştirici şimdi aramanın içine girmeden önce bu argümanı katlayabilecektir. Argüman bir fonksiyon işaretçisi (veya lambda) ise, optimizer şimdi bu lambdaya yapılan çağrıları da sıralayabilir.

Bu, sanal fonksiyonların ve fonksiyon işaretçilerinin çekici olmamasının büyük bir nedenidir çünkü asıl fonksiyon imleci çağrı sitesine sürekli katlanmıyorsa, onları satır içi tutamazsınız.


5

Performansın, programınız için önemli olduğunu ve gerçekten çok ve çok sayıda görüşme olduğunu varsayarsak, maliyet, aradığı arama türüne bağlı olarak hala olabilir veya olmayabilir.

Çağrılan işlev küçükse ve derleyici satır içi yapabiliyorsa, maliyet esasen sıfır olacaktır. Modern derleyiciler / dil uygulamaları, yararlı olduğunda satır içi işlevlerini en üst düzeye çıkarmak için tasarlanmış JIT, link-time optimizasyonları ve / veya modül sistemlerine sahiptir.

OTOH, çağrıları işlevlendirmek için açık olmayan bir maliyet var: sadece varoluşları çağrıdan önce ve sonra derleyici optimizasyonlarını engelleyebilir.

Derleyici, çağrılan fonksiyonun ne yaptığını düşünemezse (örn. Sanal / dinamik gönderim veya dinamik bir kütüphanedeki bir fonksiyon), fonksiyonun herhangi bir yan etkiye sahip olabileceğini varsaymak zorunda kalabilir - bir istisna atarak, değiştir küresel devlet veya işaretçilerin gördüğü hafızayı değiştirin. Derleyicinin hafızayı yedeklemek için geçici değerleri kaydetmesi ve çağrıdan sonra bunları tekrar okuması gerekebilir. Çağrının etrafındaki talimatları yeniden sıralayamayacağından, döngüler vektörleştiremez veya gereksiz hesaplamayı döngülerin dışına çıkarabilir.

Örneğin, her döngü yinelemesinde gereksiz yere bir işlev çağırın:

for(int i=0; i < /* gasp! */ strlen(s); i++) x ^= s[i];

Derleyici bunun saf bir işlev olduğunu bilir ve onu döngüden çıkarır (bu örnek gibi korkunç bir durumda, yanlışlıkla O (n ^ 2) algoritmasını O (n) olması için bile düzeltir):

for(int i=0, end=strlen(s); i < end; i++) x ^= s[i];

Ve daha sonra geniş / SIMD komutlarını kullanarak bir seferde 4/8/16 elemanlarını işlemek için döngüyü yeniden bile yazabilirsiniz.

Çağrı ile aynı belleğe işaret eden bir küresel değişkeni erişecek o - çağrı hiçbir şey yapmaz ve kendisi süper ucuz olsa bile döngü içinde bazı opak koduna çağrı eklersek, derleyici en kötüsünü düşünmek zorunda sdeğişikliği içeriği ( constişlevinde olsa bile const, optimizasyonu imkansız hale getiren başka hiçbir yerde olmayabilir ):

for(int i=0; i < strlen(s); i++) {
    x ^= s[i];
    do_nothing();
}

3

Bu eski makale sorunuzu cevaplayabilir:

Guy Lewis Steele, Jr. .. "Zararlı Olarak Görülen Pahalı Prosedür Çağrısı" Mitini veya Prosedür Çağrısı Uygulamalarını Borçlandırmak veya Lambda: The Ultimate GOTO ". MIT AI Lab. AI Lab Notu AIM-443. Ekim 1977.

Soyut, Özet:

Folklor, GOTO ifadelerinin "ucuz" olduğunu, prosedür çağrıları "pahalı" olduğunu belirtir. Bu efsane, büyük ölçüde, zayıf tasarlanmış dil uygulamalarının bir sonucudur. Bu efsanenin tarihsel gelişimi düşünülmektedir. Hem bu teoriyi harap eden teorik fikirler hem de mevcut bir uygulama tartışıldı. Kısıtlı olmayan prosedür çağrılarının kullanımının büyük bir şık özgürlüğe izin verdiği gösterilmiştir. Özellikle, herhangi bir akış şeması ilave değişkenler getirilmeden "yapılandırılmış" bir program olarak yazılabilir. GOTO ifadesi ve prosedür çağrısı ile ilgili zorluk, soyut programlama kavramları ve somut dil yapıları arasındaki bir çatışma olarak tanımlanır.


12
Eskiden "İşlev çağrısı masraflarının hala modern derleyicilerde önemli olup olmadığı" sorusuna cevap vereceğinden şüpheliyim .
Cody Gray,

6
@CodyGray Derleyici teknolojisinin 1977'den beri gelişmiş olması gerektiğini düşünüyorum. Yani eğer 1977'de işlev çağrıları ucuza yapılabiliyorsa, şimdi yapabilmeliyiz. Dolayısıyla yanıt hayır. Elbette bu, inline işlevi gibi şeyleri yapabilen iyi bir dil uygulaması kullandığınızı varsayar.
Alex Vong

4
@AlexVong 1977'de yapılan derleyici optimizasyonları, taş çağındaki emtia fiyatları trendine dayanmak gibidir. Her şey çok değişti. Örneğin, çarpma işlemi daha ucuz bir işlem olarak bellek erişimiyle değiştirilirdi. Şu anda, büyük bir faktör tarafından daha pahalı. Sanal yöntem çağrıları, eskisinden çok daha pahalıdır (bellek erişimi ve şube yanlış tahminleri), ancak çoğu zaman onlar en iyi duruma getirilebilir ve sanal yöntem çağrısı bile satır içi olabilir (Java her zaman yapar), bu nedenle maliyet Tamamen sıfır. 1977'de böyle bir şey yoktu.
maaartinus 10:17

3
Diğerlerinin de belirttiği gibi, sadece eski araştırmayı geçersiz kılan derleyici teknolojisindeki değişiklikler değil. Mikro mimariler büyük ölçüde değişmeden kalırken derleyiciler gelişmeye devam etmiş olsaydı, makalenin sonuçları yine de geçerli olurdu. Ama bu olmadı. Bir şey varsa, mikro mimariler derleyicilerden daha fazla değişti. Hızlı olan şeyler şimdi yavaş, göreceli olarak konuşuyor.
Cody Gray

2
@AlexVong Kağıdın eski olmasını sağlayan CPU değişikliklerinde daha kesin olmak gerekirse: 1977'de, ana hafıza erişimi tek bir CPU döngüsü idi. Bugün, L1 (!) Önbelleğine basit bir erişim bile 3 ila 4 döngü gecikmeye sahiptir. Şimdi, işlev çağrıları bellek erişiminde (yığın çerçevesinin oluşturulması, dönüş adresinin kaydedilmesi, yerel değişkenler için kayıtların kaydedilmesi) oldukça ağırdır; Eğer fonksiyonunuz sadece argümanlarını yeniden düzenler ve belki de bir aramaya geçmek için başka bir sabit argüman eklerse, bu neredeyse% 100 ek yük demektir.
cmaster

3
  • C ++, argümanları kopyalayan fonksiyon çağrıları tasarlamaya dikkat edin, varsayılan değer "değerine göre geçer". Kayıt kayıtları ve diğer yığın çerçeve ile ilgili şeyler nedeniyle, çağrı ek yükü, bir nesnenin istenmeyen (ve potansiyel olarak çok pahalı) bir kopyasıyla boğulabilir.

  • Yüksek faktörlü koddan vazgeçmeden önce araştırmanız gereken yığın kare ile ilgili optimizasyonlar vardır.

  • Yavaş bir programla uğraşmak zorunda kaldığım zamanların çoğunda algoritmik değişiklikler yapmanın iç hat fonksiyon çağrılarından çok daha fazla hız artışı sağladığını gördüm. Örneğin: Başka bir mühendis, harita haritası yapısını dolduran ayrıştırıcıyı yeniden düzenledi. Bunun bir parçası olarak, bir haritadan mantıksal olarak ilişkilendirilmiş bir dizeye önbelleğe alınmış bir dizini çıkardı. Bu, iyi bir kod sağlamlığı hamlesiydi, ancak programı, saklanan dizini kullanmak yerine gelecekteki tüm erişimler için karma arama gerçekleştirme nedeniyle 100 yavaşlama faktörü nedeniyle kullanılamaz hale getirdi. Profil oluşturma, çoğu zaman karma işlevinde harcandığını gösterdi.


4
İlk tavsiye biraz eski. C ++ 11'den beri hareket etmek mümkündü. Özellikle, bağımsız değişkenlerini dahili olarak değiştirmesi gereken işlevler için değere göre bir argüman alma ve yerinde değiştirme en etkili seçenek olabilir.
MSalters

@ MSalters: "Özellikle" "ayrıca" bir şeyle yanlış anladığını düşünüyorum. Kopyaları veya referansları verme kararı C ++ 11'den önce vardı (bildiğinizi biliyor muydunuz).
phresnel 12:17

@ phresnel: Sanırım doğru anladım. Bahsettiğim özel durum, arayanda geçici olarak oluşturduğunuz, onu bir argümana taşıdığınız ve ardından arayana değiştirdiğiniz durumdur. Bu, C ++ 11'den önce mümkün değildi, çünkü C ++ 03 geçici olmayanlara
derinlikli

@ MSalters: O zaman ilk okuduktan sonra yorumunuzu yanlış anladım. Bana öyle geliyordu ki, C ++ 11'den önce, değere göre iletmek, birinin geçirilen değeri değiştirmek isterse yapacağı bir şey değildi.
phresnel

“Hareket etme” nin ortaya çıkışı, fonksiyonda dış mekana göre daha elverişli bir şekilde yapılandırılmış ve referans olarak aktarılan nesnelerin geri dönüşünde en önemli şekilde yardımcı olur. Bundan önce bir fonksiyondan bir nesneyi döndürmek bir kopya çağırdı, genellikle pahalı bir hamle. Bu fonksiyon argümanları ile ilgilenmez. “Tasarım” kelimesini yoruma dikkatlice koydum, çünkü açıkça derleyiciye fonksiyon argümanlarına (& & sentaks) geçmek için izin vermelidir. Bunu yapmanın değerli olduğu yerleri belirlemek için kopya kurucuları 'silme' alışkanlığı kazandım.
user2543191

3

Diğerlerinin dediği gibi, önce programınızın performansını ölçmelisiniz ve muhtemelen uygulamada bir fark bulamayacaksınız.

Yine de, kavramsal bir düzeyde, sorunuzla ilgili birkaç şeyi açıklığa kavuşturacağımı düşündüm. İlk önce şunu sorarsınız:

Modern derleyicilerde işlev çağrısı maliyetleri hala önemli mi?

"Function" ve "compilers" anahtar kelimelerine dikkat edin. Teklifiniz farklıdır:

Bir yöntem çağrısının ücretinin, dile bağlı olarak önemli olabileceğini unutmayın.

Bu, nesneye yönelik anlamda yöntemlerden bahsediyor .

"İşlev" ve "yöntem" çoğu zaman birbirinin yerine kullanılırken, maliyetlerine (sorduğunuz) ve derlemeye gelince (verdiğiniz bağlam) olduğu gibi farklılıklar vardır.

Özellikle, dinamik gönderime karşı statik gönderimi bilmemiz gerekir . Şimdilik optimizasyonları görmezden geleceğim.

C gibi bir dilde, genellikle diyoruz fonksiyonları ile statik sevk . Örneğin:

int foo(int x) {
  return x + 1;
}

int bar(int y) {
  return foo(y);
}

int main() {
  return bar(42);
}

Derleyici çağrıyı gördüğünde, foo(y)bu fooismin hangi fonksiyondan bahsettiğini bilir , böylece çıkış programı doğrudan fooucuza atlar . Yani ne statik sevk anlamına gelir.

Alternatiftir dinamik sevk derleyici, gelmez çağrılan hangi işlevi biliyoruz. Örnek olarak, bazı Haskell kodu (C eşdeğeri karışık olduğu için!):

foo x = x + 1

bar f x = f x

main = print (bar foo 42)

Burada barişlev, fherhangi bir şey olabilecek argümanını çağırıyor . Dolayısıyla derleyici sadece barhızlı bir atlama komutunu derleyemez, çünkü nereye atlayacağını bilmiyor. Bunun yerine, oluşturduğumuz kod, hangi işlevi işaret ettiğini bulmak ve sonra ona atlamak için uygun bargörmeyecektir f. Yani ne dinamik bir gönderme anlamına gelir.

Bu örneklerin her ikisi de işlevler içindir . Dinamik olarak gönderilen işlevin belirli bir stili olarak düşünülebilecek yöntemlerden bahsettiniz . Örneğin, işte bazı Python:

class A:
  def __init__(self, x):
    self.x = x

  def foo(self):
    return self.x + 1

def bar(y):
  return y.foo()

z = A(42)
bar(z)

y.foo()O değerini yukarıya bakıyor beri çağrı, dinamik sevkini kullanır foomülkiyet ynesnesi, ve bulduğu her ne aradığını; ysınıfın olacağını Aveya Asınıfın bir fooyöntem içerdiğini bilmiyor , bu yüzden doğrudan atlayamayız.

Tamam, temel fikir bu. Derleme veya yorumlamadan bağımsız olarak , statik gönderimin dinamik gönderimden daha hızlı olduğunu unutmayın ; Her şey eşit. Değişikliklerden herhangi bir şekilde ek ücret alınır.

Peki bu, modernleştirici derleyicileri nasıl etkiliyor?

Unutulmaması gereken ilk şey, statik gönderimin daha yoğun bir şekilde optimize edilebileceği: hangi fonksiyona atladığımızı bildiğimizde, satır içi gibi şeyler yapabilir. Dinamik gönderimde, çalışma zamanına kadar zıpladığımızı bilmiyoruz, bu yüzden yapabileceğimiz fazla bir optimizasyon yok.

İkinci olarak, bazı diller için de mümkündür sonucuna bazı dinamik gönderir atlama sona erecek nerede ve dolayısıyla statik sevk içine onları optimize. Bu, satır içi vb. Diğer optimizasyonları yapmamızı sağlar.

Yukarıdaki Python örneğinde, bu tür bir çıkarım oldukça ümitsizdir, çünkü Python, diğer kodların sınıfları ve özellikleri geçersiz kılmasına izin verir;

Dilimiz daha fazla kısıtlama getirmemize izin veriyorsa, örneğin bir ek açıklama kullanarak ysınıfa sınırlama yaparak A, bu bilgiyi hedef işlevi ortaya çıkarmak için kullanabiliriz. Alt sınıflara sahip dillerde (bu, neredeyse tüm sınıfların dilidir!) Aslında yeterli değildir, çünkü yaslında farklı (alt) bir sınıfa sahip olabilir, bu yüzden finaltam olarak hangi fonksiyonun çağrılacağını bilmek için Java'nın ek açıklamaları gibi ek bilgilere ihtiyaç duyarız .

Haskell OO dil değildir, ancak değerini anlaması finlining ile bar(edildiği statik dağıtılacak) mainikame fooiçin y. Hedef yana fooin mainbu işlevler küçük olduğu için statik olarak bilinir, çağrı statik olarak gönderilir olur ve muhtemelen de satır içi alacak ve tamamen uzak optimize (derleyici onları satır içi olasılığı daha yüksektir, biz genel olarak bu konuda sayılmaz rağmen ).

Dolayısıyla maliyet aşağıya düşer:

  • Dil aramanızı statik mi yoksa dinamik olarak mı gönderiyor?
  • İkincisi ise, dil, uygulamanın başka bilgileri kullanarak (örneğin, türler, sınıflar, ek açıklamalar, satır içi, vb.) Hedefi hedeflemesine izin veriyor mu?
  • Statik gönderme (çıkarım veya başka şekilde) ne kadar agresif bir şekilde optimize edilebilir?

"Çok dinamik" bir dil kullanıyorsanız, çok fazla dinamik gönderim ve derleyicinin kullanabileceği birkaç garanti varsa, o zaman her çağrı bir ücrete tabidir. "Çok statik" bir dil kullanıyorsanız, olgun bir derleyici çok hızlı bir kod üretecektir. Arasındaysanız, kodlama stilinize ve uygulamanın ne kadar akıllı olduğuna bağlı olabilir.


Haskell örneğinde olduğu gibi bir kapatma (veya bazı fonksiyon göstergeleri ) çağırmanın dinamik gönderim olduğu konusunda hemfikir değilim . Dinamik gönderim , bu kapatmayı elde etmek için bazı hesaplamaları (örneğin, bazı dengesiz kullanarak ) içerir , dolaylı aramalardan daha maliyetlidir. Aksi takdirde, güzel cevap.
Basile Starynkevitch

2

Evet, kaçırılmış bir dal tahmini, modern donanım için on yıl öncesine göre daha maliyetlidir, ancak derleyiciler bunu optimize etmede çok daha akıllı hale gelmiştir.

Örnek olarak, Java'yı düşünün. İlk bakışta, genel telefon çağrısı işlevi bu dilde özellikle baskın olmalıdır:

  • JavaBean sözleşmesi nedeniyle küçük işlevler yaygındır
  • işlevler varsayılandan sanal olarak ve genellikle
  • derleme birimi sınıftır; çalışma zamanı, önceden monomorfik yöntemleri geçersiz kılan alt sınıflar da dahil olmak üzere herhangi bir zamanda yeni sınıfların yüklenmesini destekler

Bu uygulamalar tarafından dehşete düşürülen, ortalama C programcısı, Java'nın C'den daha az bir büyüklük sırası olması gerektiğini tahmin ederdi ve 20 yıl önce haklı olurdu. Bununla birlikte, modern ölçütler, idiomatik Java kodunu, eşdeğer C kodunun yüzde birkaçına yerleştirir. Bu nasıl mümkün olabilir?

Bunun bir nedeni, modern JVM'lerin satır içi işlevinin elbette mesele olması. Spekülatif satır içi kullanarak bunu yapar:

  1. Yeni yüklenen kod optimizasyon olmadan yürütülür. Bu aşamada, her arama sitesi için, JVM hangi yöntemlerin gerçekten çağrıldığını izler.
  2. Kod, performans etkin noktası olarak tanımlandıktan sonra, çalışma zamanı, en olası yürütme yolunu tanımlamak için bu istatistikleri kullanır ve spekülatif optimizasyonun geçerli olmaması durumunda koşullu bir şubeyle önceden ekleyen bir tane işaret eder.

İşte kod:

int x = point.getX();

için yazılır

if (point.class != Point) GOTO interpreter;
x = point.x;

Ve elbette, çalışma zamanı, bu tür kontrolünü nokta atanmadığı sürece yukarı taşıyacak kadar akıllıdır veya tür arama koduna biliniyorsa, onu ayırın.

Özet olarak, eğer Java bile otomatik yöntem satır içimini yönetebiliyorsa, derleyicinin otomatik satırlamayı desteklememesinin doğal bir nedeni yoktur ve bunu yapmak için her neden vardır, çünkü satır içi modern işlemciler için oldukça yararlıdır. Bu nedenle, optimizasyon stratejilerinin bu en temelinden habersiz herhangi bir modern ana akım derleyiciyi hayal bile edemiyorum ve aksi ispat edilmedikçe, bunu yapabilecek bir derleyiciyi varsayacağım.


4
“Bir derleyicinin otomatik iç döşemeyi desteklememesinin doğal bir nedeni yok” - var. Kendi kendini değiştiren kod (bir işletim sisteminin güvenlik nedeniyle engelleyebileceği) ve otomatik profil destekli tam program optimizasyonu yapma yeteneği anlamına gelen JIT derlemesi hakkında konuştunuz. Dinamik bağlantıya izin veren bir dil için bir AOT derleyicisi herhangi bir çağrıyı sapkınlaştırmak ve satır içi yapmak için yeterli bilgiyi bilmiyor. OTOH: Bir AOT derleyicisinin yapabileceği her şeyi optimize etmek için zamanı var, bir JIT derleyicisinin sadece sıcak noktalardaki ucuz optimizasyonlara odaklanacak zamanı var. Çoğu durumda, bu, JIT'yi hafif bir dezavantajda bırakır.
amon

2
Bana Google Chrome'un çalışmasını önleyen bir işletim sistemi söyle "çünkü güvenlik" (V8 çalışma zamanında JavaScript'i yerel kodla derliyor). Ayrıca, AOT satır içi isteyen oldukça doğal bir nedeni (o dile göre belirlenmez, ama mimari Eğer derleyici için seçin) değildir ve dinamik bağlama derleme birimlerinde AOT inlining inhibe yapar iken, bu satır içi uygulaması inhibe etmez içinde derleme çoğu görüşmenin yapıldığı birimler. Aslında, yararlı satır içi ekleme, Java'dan daha az aşırı dinamik bağlantı kullanan bir dilde tartışmasız daha kolaydır.
meriton

4
Özellikle, iOS ayrıcalıklı olmayan uygulamalar için JIT’i önler. Chrome veya Firefox, Apple tarafından sağlanan web görünümünü kendi motorları yerine kullanmak zorunda. AOT vs. JIT’nin dil düzeyinde bir seçim değil, uygulama düzeyinde olduğu halde iyi bir nokta.
amon

Ayrıca, Windows 10 S ve video oyun konsolu işletim sistemleri de üçüncü taraf JIT motorlarını engelleme eğilimindedir.
Damian Yerrick 11:17

2

Bir yöntem çağrısının ücretinin, dile bağlı olarak önemli olabileceğini unutmayın. Okunabilir kod yazmak ile performans kodu yazmak arasında hemen hemen her zaman bir sapma vardır.

Bu, ne yazık ki, son derece bağlıdır:

  • varsa JIT de dahil olmak üzere, derleyici araç zinciri,
  • alan adı.

Her şeyden önce, performans optimizasyonunun ilk yasası önce profildir . Yazılım bölümünün performansının tüm yığının performansı ile ilgisi olmadığı birçok alan var : veritabanı çağrıları, ağ işlemleri, işletim sistemi işlemleri, ...

Bu, yazılımın performansının tamamen alakasız olduğu anlamına gelir; gecikmeyi arttırmasa bile, yazılımı optimize etmek, enerji tasarrufu ve donanım tasarrufu (veya mobil uygulamalar için pil tasarrufu) ile sonuçlanabilir.

Bununla birlikte, bunlar tipik olarak göze çarpan OLMAMALIDIR ve sıklıkla algoritmik iyileştirmeler büyük bir farkla mikro optimizasyonlardan geçer.

Bu yüzden, optimizasyondan önce, neyi optimize ettiğinizi ve buna değip değmeyeceğini anlamanız gerekir.


Şimdi, saf yazılım performansıyla ilgili olarak, araç zincirleri arasında büyük farklılıklar gösterir.

Bir fonksiyon çağrısının iki bedeli vardır:

  • çalışma süresi maliyeti
  • Derleme süresi maliyeti.

Çalışma süresi maliyeti oldukça açık; Bir fonksiyon çağrısı yapmak için belli miktarda iş yapılması gerekir. Örneğin, x86'daki C kullanarak, bir işlev çağrısı (1) kayıtları yığına dökmek, (2) kayıtları argümanlara itmek, aramayı gerçekleştirmek ve ardından kayıtları (3) yığından geri yüklemek isteyecektir. Bkz gerekli çalışmayı görmek için kuralları çağıran bu özet .

Bu kayıt dökümü / restorasyonu önemsiz miktarda sürmektedir (onlarca CPU döngüsü).

Bu maliyetin, işlevi yerine getirmenin fiili maliyetine kıyasla önemsiz olması beklenir, ancak burada bazı desenler tersine üretkendir: alıcılar, basit bir koşulla korunan işlevler, vb ...

Tercümanların yanı sıra , bir programcı derleyicisinin veya JIT'inin gereksiz fonksiyon çağrıları için optimize edilmesini umacaktır; Her ne kadar bu umut bazen meyve vermeyebilir. Çünkü optimize ediciler büyü değildir.

Bir eniyileyici, bir işlev çağrısının önemsiz olduğunu algılayabilir ve aramayı satır içi yapar : temel olarak, işlevin gövdesini çağrı sitesinde kopyalayıp yapıştırarak. Bu her zaman iyi bir optimizasyon değildir (şişkinliğe neden olabilir) ancak genel olarak faydalıdır, çünkü satır içi bağlamı ortaya çıkarır ve bağlam daha fazla optimizasyona olanak tanır.

Tipik bir örnek:

void func(condition: boolean) {
    if (condition) {
        doLotsOfWork();
    }
}

void call() { func(false); }

Eğer funcinlined, o iyileştirici şube almamış olduğunu fark ve optimize eder calliçin void call() {}.

Bu anlamda, işlev çağrıları, iyileştiriciden (gizlenmemişse) bilgileri gizleyerek belirli optimizasyonları engelleyebilir. Sanal işlev çağrıları özellikle suçludur, çünkü devirtualization (sonuçta hangi fonksiyonun çalışma zamanında çağrıldığını kanıtlamak) her zaman kolay değildir.


Sonuç olarak, benim tavsiyem, ilk önce açıkça yazmak , erken algoritmik kötümserlikten kaçınmak (kübik karmaşıklık veya hızlıca daha kötü ısırmalar) ve daha sonra sadece neyin optimize edilmesi gerektiğini optimize etmektir.


1

"Bir yöntem çağrısının ücretinin, dile bağlı olarak, önemli olabileceğini unutmayın. Okunabilir kod yazmak ile performans kodu yazmak arasında hemen hemen her zaman bir değişim vardır."

Zengin performans gösteren modern derleyiciler endüstrisi göz önüne alındığında, bu alıntı hangi koşullar altında hala geçerli?

Sadece asla söylemediğim gibi konuşacağım. Alıntı sadece orada atmak için umursamaz olacağına inanıyorum.

Tabii ki gerçekleri tam olarak konuşmuyorum, ama bu kadar doğru olmayı umursamıyorum. Matrix filminde olduğu gibi, 1 veya 2 mi yoksa 3 mü 3 olduğunu unuttum. Sanırım büyük kavunlu seksi bir İtalyan aktris olan filmdi. kahin hanım Keanu Reeves'e “Sadece duymak istediğini söyledim” ya da bu etkiden bahseden şey, şimdi yapmak istediğim şey.

Programcıların bunu duymasına gerek yok. Profilciler ellerinde deneyimliyse ve teklifler derleyicileri için biraz geçerliyse, bunu zaten bilecekler ve profil çıktısını anlamalarını ve belirli yaprak çağrılarının neden sıcak noktalar olduğunu ölçmeleri halinde bunu doğru şekilde öğreneceklerdir. Eğer deneyimli değiller ve kodlarını hiç profillendirmemişlerse, duymaları gereken en son şey budur, umuduyla sıcak noktaları belirlemeden önce her şeyi satırlama noktasına kod yazma biçiminden ödün vermeden baştan başa çıkmaları gerekir. daha performanslı olmak.

Neyse, daha doğru bir cevap için, buna bağlı. Tekne yükü koşullarından bazıları zaten hassas cevaplar arasında listelenmiştir. Sadece bir dili seçmekle ilgili olası koşullar, sanal aramalarda dinamik gönderim yapmak zorunda olan ve ne zaman en iyi duruma getirilebileceği ve hangi derleyicilerin ve hatta bağlayıcıların altında olacağı ve zaten denemeye çalışarak ayrıntılı bir yanıt vermeyi garanti edecek olan C ++ gibi zaten zaten büyüktür. Koşulları mümkün olan her dilde ele almak ve orada derlemek. Ama üstüne ekleyeceğim, "kimin umrunda?" Çünkü performans açısından kritik alanlarda ışın izleme gibi çalışmak bile, en baştan başlayacağım en son şey, herhangi bir ölçüm yapmadan önce elle yapılan yöntemlerdir.

Bazı insanların ölçümden önce herhangi bir mikro-optimizasyon yapmamanız gerektiğini önerme konusunda aşırı çaba sarf ettiklerini düşünüyorum. Eğer referansın yeri için optimizasyon bir mikro optimizasyon olarak sayılıyorsa, o zaman çoğu zaman bu tür optimizasyonları başlangıçta veri odaklı bir tasarım zihniyetiyle uygulamaya başlıyorum. Çünkü aksi takdirde, yıllar boyunca bu alanlarda çalıştıktan sonra büyük bölümleri yeniden yazmak zorunda kalacağımı biliyorum. Önbellek isabetleri için veri temsilini optimize etmek, lineer için ikinci dereceden bir zaman gibi konuşmadığımız sürece, algoritmik iyileştirmelerle aynı performans performanslarına sahip olabilir.

Ancak, ölçümler öncesinde satır içi başlatmaya başlamak için asla iyi bir neden görmedim, özellikle de profilciler satır içi satırdan neyin fayda sağlayabileceğini ortaya koymakta ustalar; çizgisiz işlev çağrısı, sıcak kod için icache için referans yerinin iyileştirilmesi ve bazen en iyi duruma getiricilerin ortak uygulama yolu için daha iyi bir iş yapmasına bile izin veren nadir bir durumdur).

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.