Derleyiciler neden her şeyi sıraya koymuyor? [kapalı]


13

Bazen derleyiciler satır içi işlev çağrıları. Bu, çağrılan işlevin kodunu çağrı işlevine taşırlar. Bu, işleri biraz daha hızlı hale getirir, çünkü çağrı yığınını açıp kapatmaya gerek yoktur.

Benim sorum şu, neden derleyiciler her şeyi satır içi yapmıyor? Yürütülebilir dosyayı oldukça hızlı hale getireceğini varsayıyorum.

Düşünebilmemin tek nedeni çok daha büyük bir yürütülebilir dosyadır, ancak bugünlerde yüzlerce GB bellekle gerçekten önemli mi? Geliştirilmiş performans buna değmez mi?

Derleyicilerin sadece tüm işlev çağrılarını satır içi yapmasının başka bir nedeni var mı?


18
Senin hakkında IDK, ama yüzlerce GB'lık hafızam yok.
Ampt

2
Isn't the improved performance worth it?Bir döngüyü 100 kez çalıştıracak ve bazı ciddi sayıları kıracak bir yöntem için, 2 veya 3 argümanının CPU kayıtlarına taşınması yükü hiçbir şey değildir.
Doval

5
Aşırı genelsiniz, "derleyiciler" "tüm derleyiciler" anlamına mı geliyor ve "her şey" gerçekten "her şey" anlamına mı geliyor? O zaman cevap basittir, satır içi yapamayacağınız durumlar vardır. Özyineleme akla geliyor.
Otávio Décio

17
Önbellek konumu, küçük işlev çağrısı yükünden çok daha önemlidir.
SK-logic

3
Yüzlerce GFLOPS işlem gücü ile performans gelişimi gerçekten önemli mi?
mouviciel

Yanıtlar:


22

İlk satır içi etkisinin önemli bir etkisinin, çağrı sitesinde daha fazla optimizasyon yapılmasına izin vermesidir.

Sorunuz için: satır içi yapmak zor hatta imkansız olan şeyler vardır:

  • dinamik bağlantılı kütüphaneler

  • dinamik olarak belirlenmiş işlevler (işlev göndericileri aracılığıyla çağrılan dinamik gönderme)

  • özyinelemeli fonksiyonlar (kuyruk özyineleme olabilir)

  • koduna sahip olmadığınız işlevler (ancak bağlantı süresi optimizasyonu bazıları için buna izin verir)

O zaman inlining sadece yararlı etkilere sahip değildir:

  • daha büyük yürütülebilir dosya daha fazla disk yeri ve daha büyük yükleme süresi anlamına gelir

  • daha büyük yürütülebilir dosya önbellek basıncının artması anlamına gelir (basit alıcılar gibi yeterince küçük işlevlerin satır içine alınmasının yürütülebilir boyutu ve önbellek basıncını azaltabileceğini unutmayın)

Ve son olarak, yürütülmesi önemsiz olmayan bir zaman alan işlevler için, kazanç sadece acıya değmez.


3
bazı özyinelemeli çağrılar satır içine alınabilir (kuyruk çağrıları), ancak isteğe bağlı olarak açık bir yığın eklerseniz hepsi yinelemeye dönüştürülebilir
cırcır ucube

@ ratchetfreak, ayrıca kuyruk olmayan bazı özyinelemeli çağrı içine bir kuyruk dönüştürebilirsiniz. Ama bu benim için "zor" olanın alanında (özellikle ortak özyinelemeli işlevleriniz varsa veya dönüşü simüle etmek için nereye atlayacağınızı dinamik olarak belirlemeniz gerektiğinde), ancak bu imkansız değil (sadece bir devam çerçevesi oluşturdunuz ve mevcut olduğunu düşünürsek kolaylaşır).
AProgrammer

11

Büyük bir sınırlama, çalışma zamanı polimorfizmidir. Yazarken dinamik bir gönderme varsa foo.bar(), yöntem çağrısını satır içine almak imkansızdır. Bu, derleyicilerin neden her şeyi satır içi yapmadıklarını açıklar.

Yinelemeli çağrılar da kolayca satır içine alınamaz.

Çapraz modül satır içi yapmanın teknik nedenlerle gerçekleştirilmesi de zordur (ex için artımlı yeniden derleme imkansız olacaktır)

Ancak, derleyiciler birçok şeyi satır içi yapar.


3
Sanal bir gönderiyle satır içi yapmak çok zordur, ancak imkansız değildir. Bazı C ++ derleyicileri bunu belirli koşullar altında yapabilir.
bstamour

2
... ve bazı JIT derleyicileri (sanallaştırma).
Frank

@bstamour Uygun optimizasyonları olan herhangi bir dilin yarı iyi derleyicisi, derleme zamanında dinamik türü bilinebilen bir nesne üzerinde bildirilen sanal bir yönteme statik olarak çağrı gönderir. Bu, devrileştirme aşaması (veya başka bir) satır içi fazından önce meydana gelirse, satır içi yapmayı kolaylaştırabilir. Ancak bu önemsizdir. Demek istediğin başka bir şey var mıydı? Gerçek bir "Sanal dağıtım yoluyla satır içi" nasıl elde edilebileceğini görmüyorum. Yani devirtualise - - satır içi için, bir statik tür bilmelidir inlining araçlarının varlığı orada o kadar olduğunu hiçbir sanal sevk
underscore_d

9

İlk olarak, her zaman satır içi olamazsınız, örneğin, özyinelemeli işlevler her zaman satır içi olmayabilir (ancak factyalnızca yazdırılmasıyla özyinelemeli bir tanım içeren bir program fact(8)satır içi olabilir).

O zaman, satır içi her zaman yararlı olmaz. Derleyici, sonuç kodunun, sıcak parçalarının örneğin L1 komut önbelleğine sığmayacak kadar büyük olması durumunda, satır içi olmayan sürümden (daha kolay L1 önbelleğine sığacak) çok daha yavaş olabilir ... Ayrıca, son işlemciler bir CALLmakine talimatı yürütme konusunda çok hızlıdır (en azından bilinen bir konuma, yani doğrudan çağrı, işaretçi çağrısı değil).

Sonunda, tam satır içi ayar, tüm bir program analizi gerektirir. Bu mümkün olmayabilir (veya çok maliyetli olabilir). GCC tarafından derlenen C veya C ++ ile (ve ayrıca Clang / LLVM ile ) bağlantı zamanı optimizasyonunu etkinleştirmeniz gerekir (örneğin derleyerek ve bağlayarak g++ -flto -O2) ve bu çok fazla derleme süresi gerektirir.


1
Kayıt için, LLVM / Clang (ve diğer birkaç derleyici) bağlantı zamanı optimizasyonunu da destekler .
Sen

Bunu biliyorum; LTO önceki yüzyılda vardı (IIRC, en azından bazı MIPS tescilli derleyicilerinde).
Basile Starynkevitch

7

Her ne kadar şaşırtıcı görünse de, her şeyin satır içi olması, yürütme süresini mutlaka azaltmaz. Kodunuzun artan boyutu, CPU'nun tüm kodunuzu aynı anda önbelleğinde tutmasını zorlaştırabilir. Kodunuzdaki bir önbellek kaçırma olasılığı artar ve önbellek kaçışı pahalıdır. Potansiyel olarak eğimli işlevleriniz büyükse bu çok daha kötü hale gelir.

Başlık dosyalarının 'satır içi' olarak işaretlenmiş büyük kod parçalarını alarak, kaynak koduna yerleştirerek zaman zaman fark edilir performans geliştirmeleri yaptım, böylece kod her çağrı sitesinde değil, tek bir yerde. Sonra CPU önbelleği daha iyi kullanılır ve daha iyi derleme zamanı elde edersiniz ...


Bu sadece bir saat önce gönderilen önceki cevapta yapılan ve açıklanan noktaları tekrarlıyor gibi görünüyor
gnat

1
Ne önbellekleri? L1? L2? L3? Hangisi daha önemli?
Peter Mortensen

1

Her şeyi satırlamak, sadece artan disk belleği tüketimi anlamına gelmez, aynı zamanda bu kadar bol olmayan dahili bellek tüketimini de arttırır. Kodun, kod segmentindeki belleğe de bağlı olduğunu unutmayın; bir işlev 10000 yerden çağrılırsa (oldukça büyük bir projede standart kütüphanelerden olanları söyleyin), bu işlevin kodu 10000 kat daha fazla dahili bellek kullanır.

Başka bir neden JIT derleyicileri olabilir; her şey satır içi ise, dinamik olarak derlenecek sıcak noktalar yoktur.


1

Birincisi, satır içi her şeyin çok kötü çalışacağı basit örnekler var. Bu basit C kodunu düşünün:

void f1 (void) { printf ("Hello, world\n"); }
void f2 (void) { f1 (); f1 (); f1 (); f1 (); }
void f3 (void) { f2 (); f2 (); f2 (); f2 (); }
...
void f99 (void) { f98 (); f98 (); f98 (); f98 (); }

Bil bakalım her şeyin size ne yapacağı.

Sonra, satır içi işlemin işleri daha hızlı hale getireceği varsayımına varıyorsunuz. Bazen böyle olur, ama her zaman değil. Bunun bir nedeni, talimat önbelleğine uyan kodun çok daha hızlı çalışmasıdır. 10 yerden bir işlev çağırırsam, her zaman talimat önbelleğindeki kodu çalıştırırım. Satır içi ise, kopyalar her yerdedir ve çok daha yavaş çalışır.

Başka sorunlar da var: Inlining devasa işlevler üretir. Büyük işlevlerin optimize edilmesi çok daha zordur. Derleyici onları satır içine önlemek için işlevleri ayrı bir dosyaya gizleyerek performans kritik kod önemli kazanımlar var. Sonuç olarak, bu işlevler için oluşturulan kod gizlendiklerinde çok daha iyiydi.

BTW. "Yüzlerce GB belleğim" yok. İş bilgisayarımda "yüzlerce GB sabit disk alanı" bile yok. Ve eğer benim uygulama "yüzlerce GBs" burada, sadece belleğe uygulamayı yüklemek için 20 dakika sürer.

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.