İşlev çağrıları performansı ne kadar etkiler?


13

Özellikle OOP'de kod modülerliği, okunabilirliği ve birlikte çalışabilirliği için işlevselliği yöntemlere veya işlevlere çıkarmak şarttır.

Ancak bu, daha fazla işlev çağrısı yapılacağı anlamına gelir.

Kodumuzu yöntemlere veya işlevlere bölmek modern * dillerdeki performansı nasıl etkiler ?

* En popüler olanlar: C, Java, C ++, C #, Python, JavaScript, Ruby ...



1
Tuzuna değecek her dil uygulaması, onlarca yıldır inlining yapıyor. IOW, genel gider tam 0'dır.
Jörg W Mittag

1
"daha fazla işlev çağrısı yapılacak" çoğu zaman doğru değildir, çünkü bu çağrıların birçoğu ek yüklerini kodunuzu işleyen çeşitli derleyiciler / tercümanlar tarafından optimize edilecektir. Dilinizde bu tür optimizasyonlar yoksa, onu modern olarak düşünmeyebilirim.
Ixrec

2
Performansı nasıl etkileyecek? Hangi dili kullandığınıza ve gerçek kodun yapısına ve muhtemelen hangi derleyicinin hangi sürümünü kullandığınıza ve hatta hangi platformu kullandığınıza bağlı olarak, daha hızlı veya daha yavaş hale getirecek veya değiştirmeyecektir. üzerinde çalışıyor. Aldığınız her cevap, bu belirsizliğin daha fazla kelime ve daha fazla destekleyici kanıtla bir çeşitlemesi olacaktır.
GrandOpener

1
Darbe varsa, sen bir insan, asla o kadar küçüktür ki hiç fark. Endişelenecek çok daha önemli şeyler var. Sekmelerin 5 mi yoksa 7 boşluk mu olması gerektiği gibi.
MetaFight

Yanıtlar:


21

Olabilir. Derleyici "hey, bu işlev sadece birkaç kez çağrılır ve hız için optimizasyon yapmam gerekiyor, bu yüzden bu işlevi satır içi yapacağım". Temel olarak, derleyici işlev çağrısını işlevin gövdesi ile değiştirir. Örneğin, kaynak kodu şöyle görünecektir.

void DoSomething()
{
   a = a + 1;
   DoSomethingElse(a);
}

void DoSomethingElse(int a)
{
   b = a + 3;
}

Derleyici satır içi olmaya karar verir DoSomethingElseve kod

void DoSomething()
{
   a = a + 1;
   b = a + 3;
}

İşlevler satır içine alınmadığında, evet bir işlev çağrısı yapmak için bir performans isabeti vardır. Ancak, o kadar küçük bir hit ki, sadece son derece yüksek performans kodu fonksiyon çağrıları için endişelenecek. Ve bu tür projelerde, kod genellikle montajda yazılır.

İşlev çağrıları (platforma bağlı olarak) tipik olarak birkaç 10'luk talimat içerir ve bu, yığının kaydedilmesini / geri yüklenmesini içerir. Bazı işlev çağrıları bir atlama ve dönüş talimatı içerir.

Ancak işlev çağrısı performansını etkileyebilecek başka şeyler de vardır. Çağrılan işlev, işlemcinin önbelleğine yüklenmeyebilir ve önbellek kaybına neden olur ve bellek denetleyicisini işlevi ana RAM'den yakalamaya zorlar. Bu, performans için büyük bir isabet yaratabilir.

Özetle: işlev çağrıları performansı etkileyebilir veya etkilemeyebilir. Anlatmanın tek yolu kodunuzu profillendirmektir. Yavaş kod noktalarının nerede olduğunu tahmin etmeye çalışmayın, çünkü derleyici ve donanımın kollarında inanılmaz hileler var. Yavaş noktaların yerini almak için kodu profilleyin.


1
Ben gerçekten büyük bir fonksiyon içinde döngüler için oldukça kötü kod oluşturdukları bakım durumlarda modern derleyiciler (gcc, clang) ile gördüm . Döngüyü statik bir işleve çıkarmak, satır içi işlem nedeniyle yardımcı olmadı. Döngüyü, bazı durumlarda önemli (kıyaslama ölçülerinde ölçülebilir) hız geliştirmeleri ile oluşturulan harici bir işleve çıkarmak.
gnasher729

1
Bunu piggy-back ve OP'nin Erken Optimizasyon
Patrick

1
@Patrick Bingo. Optimize edecekseniz, yavaş bölümlerin nerede olduğunu görmek için bir profil oluşturucu kullanın. Tahmin etme. Genellikle yavaş bölümlerin nerede olabileceği hakkında bir fikir edinebilirsiniz, ancak bir profilerle onaylayın.
CHendrix

@ gnasher729 Bu sorunu çözmek için, bir profil oluşturucudan daha fazlasına ihtiyaç duyulacaktır - demonte edilmiş makine kodunu da okumayı öğrenmek gerekir. Erken optimizasyon olsa da, erken öğrenme (en azından yazılım geliştirmede) diye bir şey yoktur .
rwong

Sen belki bir fonksiyonu milyonlarca kez arayarak, bu sorunu var, ancak önemli ölçüde daha büyük bir etkiye sahip olan diğer sorunlar daha olasıdır.
Michael Shaw

5

Bu, derleyicinin veya çalışma zamanının (ve seçeneklerinin) uygulanması meselesidir ve kesin olarak söylenemez.

C ve C ++ içinde, bazı derleyiciler optimizasyon ayarlarına göre çağrıları sıralı hale getirir - bu, https://gcc.godbolt.org/ gibi araçlara bakarken oluşturulan montajı inceleyerek önemsiz bir şekilde görülebilir.

Java gibi diğer diller, çalışma zamanının bir parçası olarak buna sahiptir. Bu JIT'in bir parçasıdır ve bu SO sorusunda ayrıntılandırılmıştır . HotSpot için JVM seçeneklerine patiküler bir bakışla

-XX:InlineSmallCode=n Önceden derlenmiş bir yöntemi yalnızca oluşturulan yerel kod boyutu bundan küçükse satır içi. Varsayılan değer, JVM'nin çalıştığı platforma göre değişir.
-XX:MaxInlineSize=35 Satırılacak bir yöntemin maksimum bayt kodu boyutu.
-XX:FreqInlineSize=n Satır içi olacak sık kullanılan bir yöntemin maksimum bayt kodu boyutu. Varsayılan değer, JVM'nin çalıştığı platforma göre değişir.

Bu yüzden evet, HotSpot JIT derleyicisi belirli ölçütleri karşılayan yöntemleri satır içine alacaktır.

Bunun , her JVM'nin (veya derleyicinin) farklı şeyler yapabileceği ve bir dilin geniş vuruşuyla cevaplamaya çalışmanın neredeyse kesin yanlış olması nedeniyle etkisini belirlemek zordur. Etki, yalnızca uygun çalışma ortamında kodun profillenmesi ve derlenmiş çıktıların incelenmesi ile uygun şekilde belirlenebilir.

Bu, CPython'un satır içi değil, Jython'un (JVM'de çalışan Python) bazı çağrıları satır içi yapmasıyla yanlış yönlendirilmiş bir yaklaşım olarak görülebilir. Aynı şekilde MRI Ruby, JRuby olurken inlining yapmaz ve ruby2c, Ruby'ye bir transpiler olan C ... daha sonra derlenebilen veya derlenen C derleyici seçeneklerine bağlı olabilir.

Diller satır içi değildir. Uygulamalar olabilir .


5

Yanlış yerde performans arıyorsunuz. İşlev çağrılarıyla ilgili sorun, çok pahalıya mal olmaması değil. Başka bir sorun daha var. İşlev çağrıları tamamen ücretsiz olabilir ve yine de bu başka sorunla karşılaşırsınız.

Bir fonksiyonun kredi kartı gibi olması. Kolayca kullanabileceğiniz için, belki de gerekenden daha fazla kullanma eğilimindesiniz. Diyelim ki ihtiyacınız olandan% 20 daha fazla. Daha sonra, tipik büyük yazılım birkaç katman içerir, her katman aşağıdaki katmandaki işlevleri çağırır, böylece 1.2 faktörü katman sayısı ile birleştirilebilir. (Örneğin, beş katman varsa ve her katman 1.2'de bir yavaşlama faktörüne sahipse, bileşik yavaşlama faktörü 1.2 ^ 5 veya 2.5'tir.) Bu, bunu düşünmenin sadece bir yoludur.

Bu, işlev çağrılarından kaçınmanız gerektiği anlamına gelmez. Bunun anlamı, kod çalışırken ve çalışırken, atıkların nasıl bulunacağını ve ortadan kaldırılacağını bilmeniz gerekir. Stackexchange sitelerinde bunun nasıl yapılacağı konusunda çok mükemmel tavsiyeler var. Bu benim katkılarımdan birini veriyor.

EKLENDİ: Küçük örnek. Bir keresinde, bir dizi iş emrini veya "işi" izleyen fabrika katındaki yazılımda bir ekipte çalıştım. JobDone(idJob)Bir işin yapılıp yapılmadığını söyleyebilecek bir işlev vardı. Tüm alt görevleri yapıldığında bir iş yapıldı ve her biri tüm alt işlemleri yapıldığında yapıldı. Bütün bunlar ilişkisel bir veritabanında takip edildi. Başka bir işleve yapılan tek bir çağrı, JobDonesöz konusu diğer işlev olarak adlandırılan tüm bilgileri elde edebilir, işin yapılıp yapılmadığını gördü ve gerisini attı. Sonra insanlar böyle kodu kolayca yazabilir:

while(!JobDone(idJob)){
    ...
}

veya

foreach(idJob in jobs){
    if (JobDone(idJob)){
        ...
    }
}

Konuyu görüyor musunuz? İşlev o kadar "güçlü" ve kolay aradı ki çok fazla çağrıldı. Yani performans problemi fonksiyona giren ve çıkan talimatlar değildi. İşlerin yapılıp yapılmadığını söylemenin daha doğrudan bir yolu olması gerekiyordu. Yine, bu kod masum kodun binlerce satırına gömülmüş olabilir. Önceden düzeltmeye çalışmak herkesin yapmaya çalıştığı şeydir, ancak bu karanlık bir odaya dart atmaya çalışmak gibidir. Bunun yerine ihtiyacınız olan şey onu çalıştırmak ve daha sonra "yavaş kod" ne olduğunu, sadece zaman ayırarak size bildirin. Bunun için rastgele duraklama kullanıyorum .


1

Gerçekten dile ve işleve bağlı olduğunu düşünüyorum. C ve c ++ derleyicileri birçok işlevi satır içine alabilirken, bu Python veya Java için geçerli değildir.

Java için belirli ayrıntıları bilmiyor olsam da (her yöntemin sanal olduğu, ancak belgeleri daha iyi kontrol etmenizi öneririm), Python'da satır içi olmadığından, kuyruk özyineleme optimizasyonu ve işlev çağrıları oldukça pahalı olduğundan eminim.

Python işlevleri temel olarak yürütülebilir nesnelerdir (ve bir nesne örneğini işlev yapmak için call () yöntemini de tanımlayabilirsiniz ). Bu, onları çağırırken oldukça fazla yük olduğu anlamına gelir ...

FAKAT

fonksiyonların içindeki değişkenleri tanımladığınızda, yorumlayıcı bayt kodundaki normal LOAD komutu yerine LOADFAST kullanır ve kodunuzu daha hızlı hale getirir ...

Başka bir şey, çağrılabilir bir nesne tanımladığınızda, notlama gibi kalıpların mümkün olması ve hesaplamanızı çok daha hızlı bir şekilde hızlandırabilmesidir (daha fazla bellek kullanma pahasına). Temelde her zaman bir değiş tokuş. İşlev çağrıları maliyeti de parametrelere bağlıdır, çünkü yığın üzerinde gerçekte ne kadar şey kopyalamanız gerektiğini belirlerler (bu nedenle c / c ++ 'da yapılar gibi büyük parametreleri değere göre işaretçilerle / referansla geçirmek yaygın bir uygulamadır).

Sorunuzun pratikte stackexchange'te tamamen yanıtlanamayacak kadar geniş olduğunu düşünüyorum.

Yapmanızı önerdiğim, bir dil ile başlamak ve işlev çağrılarının o dil tarafından nasıl uygulandığını anlamak için gelişmiş belgeleri incelemek.

Bu süreçte ne kadar şey öğreneceğinize şaşıracaksınız.

Belirli bir sorununuz varsa, ölçümler / profil oluşturma ve hava durumuna karar verme bir işlev oluşturmak veya eşdeğer kodu kopyalamak / yapıştırmak daha iyidir.

daha spesifik bir soru sorarsanız, daha spesifik bir cevap almak daha kolay olur diye düşünüyorum.


Size alıntı: "Bence sorunuz pratikte stackexchange'te tamamen yanıtlanamayacak kadar geniştir." O zaman nasıl daraltabilirim? Performansta işlev çağrısı etkisini temsil eden bazı gerçek verileri görmek isterim. Hangi dili umursamıyorum, dediğim gibi, mümkünse verilerle desteklenen daha ayrıntılı bir açıklama görmeyi merak ediyorum.
dabadaba

Mesele, dile bağlı olmasıdır. C ve C ++ 'da, işlev satır içi ise, etki 0'dır.
Satır içi

1

Bir süre önce Xenon PowerPC'deki doğrudan ve sanal C ++ işlev çağrılarının yükünü ölçtüm .

Söz konusu fonksiyonların tek bir parametresi ve tek bir dönüşü vardı, bu nedenle kayıtlarda parametre geçişi meydana geldi.

Uzun öykü kısa, doğrudan (sanal olmayan) bir işlev çağrısının ek yükü, bir satır içi işlev çağrısına kıyasla yaklaşık 5.5 nanosaniye veya 18 saat döngüsü idi. Sanal işlev çağrısının ek yükü satır içi ile karşılaştırıldığında 13.2 nanosaniye veya 42 saat devirdi.

Bu zamanlamalar muhtemelen farklı işlemci ailelerinde farklıdır. Test kodum burada ; aynı denemeyi donanımınızda da çalıştırabilirsiniz. Gibi yüksek duyarlıklı zamanlayıcı kullanın RDTSC sizin CFastTimer uygulanması için; sistem zamanı () yeterince hassas değildir.

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.