Const olarak argüman iletmek erken optimizasyon mudur?


20

"Erken optimizasyon tüm kötülüklerin köküdür"

Sanırım hepimiz hemfikiriz. Bunu yapmaktan kaçınmak için çok uğraşıyorum.

Ama son zamanlarda Value yerine const Reference ile parametrelerin geçirilmesi uygulamasını merak ediyorum . Önemsiz işlev argümanlarının (yani en ilkel olmayan türlerin) tercihen const referansı ile geçirilmesi gerektiği öğretildi / öğrenildi - okuduğum birkaç kitap bunu "en iyi uygulama" olarak tavsiye ediyor.

Hala yardım edemiyorum ama merak ediyorum: Modern derleyiciler ve yeni dil özellikleri harikalar yaratabilir, bu yüzden öğrendiğim bilgiler çok eski olabilir ve arasında herhangi bir performans farkı varsa aslında profil yapmaktan rahatsız olmadım

void fooByValue(SomeDataStruct data);   

ve

void fooByReference(const SomeDataStruct& data);

Öğrendiğim uygulama - const referanslarını (önemsiz türler için varsayılan olarak) geçirerek - erken optimizasyon mu?


1
Ayrıca bakınız: Çeşitli parametre geçiş stratejilerinin tartışması için C ++ Temel Yönergeleri'nde F.call .
amon


1
@DocBrown Bu sorunun kabul edilen cevabı , burada da uygulanabilecek en az şaşkınlık ilkesini ifade eder (yani const referanslarını kullanmak endüstri standardıdır, vb.). Bununla birlikte, sorunun bir kopya olduğunu kabul etmiyorum: Bahsettiğiniz soru, derleyici optimizasyonuna (genellikle) güvenmek için kötü bir uygulama olup olmadığını soruyor. Bu soru tersini soruyor: Sabit referansları iletmek (erken) bir optimizasyon mu?
CharonX

@CharonX: Burada derleyici optimizasyonuna güvenilebiliyorsa, sorunuzun cevabı açıkça "evet, manuel optimizasyon gerekli değildir, erken" dir. Biri buna güvenemezse (belki de kod için hangi derleyicilerin kullanılacağını önceden bilmediğinizden dolayı), cevap "büyük nesneler için muhtemelen erken değildir" dir. Dolayısıyla, bu iki soru tam anlamıyla eşit olmasa bile, IMHO onları kopyalar olarak birbirine bağlayacak kadar benzer görünüyor.
Doc Brown

1
@DocBrown: Yani, bir dupe ilan etmeden önce, sorunun derleyiciye izin verileceğini ve bunu "optimize edebileceğini" söylediği yere dikkat edin.
Tekilleştirici

Yanıtlar:


50

"Erken optimizasyon" optimizasyonları erken kullanmakla ilgili değildir . Sorun anlaşılmadan önce, çalışma zamanı anlaşılmadan önce optimize etmek ve şüpheli sonuçlar için genellikle kodu daha az okunabilir ve daha az sürdürülebilir hale getirmekle ilgilidir.

Bir nesneyi değere göre geçmek yerine "const &" kullanmak, çalışma zamanı üzerinde iyi anlaşılmış etkileri olan, pratikte hiçbir çaba harcamadan ve okunabilirlik ve sürdürülebilirlik üzerinde herhangi bir kötü etkisi olmayan iyi anlaşılmış bir optimizasyondur. Aslında her ikisini de geliştirir, çünkü bir çağrının iletilen nesneyi değiştirmeyeceğini söyler. Bu nedenle, kodu yazarken "const &" eklenmesi PREMATURE DEĞİLDİR.


2
Cevabınızın "hemen hemen hiç çaba yok" kısmına katılıyorum. Ancak erken optimizasyon, kayda değer, ölçülen bir performans etkisi olmadan önce ve en başta optimizasyonla ilgilidir. Ve çoğu C ++ programcısının (kendimi içeren) kullanmadan önce herhangi bir ölçüm yaptığını const&düşünmüyorum, bu yüzden sorunun oldukça mantıklı olduğunu düşünüyorum.
Doc Brown

1
Herhangi bir tutarsızlığın buna değip değmeyeceğini bilmek için optimizasyon yapmadan önce ölçersiniz. Const & toplam çaba ile yedi karakter yazmak ve diğer avantajları var. Aktarılan değişkeni değiştirmek istemediğinizde, hız artışı olmasa bile avantajlıdır.
gnasher729

3
Ben bir C uzmanı değilim, bu yüzden bir soru: const& foofonksiyonun foo'yu değiştirmeyeceğini, bu nedenle arayanın güvenli olduğunu söylüyor . Ancak kopyalanan bir değer, başka hiçbir iş parçacığının foo değiştiremeyeceğini söyler , bu nedenle callee güvenlidir. Sağ? Bu nedenle, çok iş parçacıklı bir uygulamada cevap, optimizasyona değil doğruluğa bağlıdır.
user949300

1
@DocBrown sonunda const koymak geliştiricinin motivasyonunu sorgulayabilirsiniz &? Sadece gerisini düşünmeden performans için yaptıysa, erken optimizasyon olarak düşünülebilir. Şimdi eğer bir sabit parametre olacağını bildiği için koyarsa, sadece kodunu kendi kendine belgeliyor ve derleyiciye optimize etme fırsatı veriyor, bu daha iyi.
Walfrat

1
@ user949300: Birkaç işlev, argümanlarının eşzamanlı olarak veya kullanılan geri çağrılarla değiştirilmesine izin verir ve açıkça söyler.
Tekilleştirici

16

TL; DR: Const referansı ile geçiş, C ++ 'da hala iyi bir fikirdir. Erken optimizasyon değil.

TL; DR2: Çoğu atasözü bunu yapana kadar mantıklı değil.


Amaç

Bu cevap sadece C ++ Temel Yönergeleri'nde (amon'un yorumunda ilk kez bahsedilmiştir) bağlantılı öğeyi biraz genişletmeye çalışır .

Bu cevap, programcıların çevrelerinde geniş çapta dolaşan çeşitli atasözleri, özellikle de çelişkili sonuçlar veya kanıtlar arasında uzlaşma sorunu nasıl düşünülüp uygulanacağı konusunu ele almaya çalışmamaktadır.


uygulanabilirlik

Bu yanıt yalnızca işlev çağrıları (aynı iş parçasındaki çıkarılamaz iç içe kapsamlar) için geçerlidir.

(Yan not.) Geçilebilir şeyler kapsamdan kaçabiliyorsa (örneğin, dış kapsamı potansiyel olarak aşan bir ömre sahipse), uygulamanın nesne ömrü yönetimi gereksinimini her şeyden önce karşılamak daha önemli hale gelir. Genellikle bu, akıllı işaretçiler gibi ömür boyu yönetim yeteneğine sahip referansların kullanılmasını gerektirir. Alternatif bir yönetici kullanıyor olabilir. Lambda'nın bir tür sökülebilir kapsam olduğunu unutmayın; lambda yakalamaları nesne kapsamına sahip gibi davranır. Bu nedenle, lambda yakalamalarına dikkat edin. Ayrıca lambda'nın kendisinin nasıl geçtiğine dikkat edin - kopya veya referans olarak.


Değere göre ne zaman geçilir?

Değerler için skaler olan iletişim bazında değişkenliği (ortak bir referans) için bir ihtiyaç vardır (bir makine yazmaçdaki uygun ve değeri semantik sahip standart ilkellerini) değeri ile geçmektedir.

Callee'nin bir nesnenin veya toplamın klonlanmasını gerektirdiği durumlarda, callee'nin kopyasının klonlanmış bir nesne ihtiyacını karşıladığı değere göre geçin.


Referans vb. İle ne zaman geçilir?

diğer tüm durumlar için, işaretçiler, referanslar, akıllı işaretçiler, tutamaklar (bkz: tutamak-vücut deyimi), vb.

Bellek ayak izinde yeterince büyük olan şeyler (kümeler, nesneler, diziler, veri yapıları), performans nedenleriyle her zaman referans yoluyla kolaylaştıracak şekilde tasarlanmalıdır. Bu tavsiye yüzlerce bayt veya daha fazla olduğunda kesinlikle geçerlidir. Bu tavsiye onlarca bayt olduğunda sınırdadır.


Olağandışı paradigmalar

Niyetle kopya ağır olan özel amaçlı programlama paradigmaları vardır. Örneğin, dize işleme, serileştirme, ağ iletişimi, yalıtım, üçüncü taraf kitaplıkların sarılması, paylaşılan bellek süreçler arası iletişim, vb. Bu uygulama alanlarında veya programlama paradigmalarında, veriler yapılardan yapılara kopyalanır veya bazen yeniden paketlenir bayt dizileri.


Optimizasyon düşünülmeden önce dil belirtiminin bu yanıtı nasıl etkilediği .

Alt TL; DR Bir referansın yayılması hiçbir kod çağırmamalıdır; const referansı ile geçmek bu kriteri karşılar. Ancak, diğer tüm diller bu kriteri zahmetsizce karşılar.

(Acemi C ++ programcılarına bu bölümü tamamen atlamaları önerilir.)

(Bu bölümün başlangıcı kısmen gnasher729'un cevabından esinlenmiştir. Ancak farklı bir sonuca varılmıştır.)

C ++, kullanıcı tanımlı kopya oluşturuculara ve atama işleçlerine izin verir.

(Bu, hem şaşırtıcı hem de pişman olan cesur bir seçimdi. Dil tasarımında bugünün kabul edilebilir normundan kesinlikle bir sapma.)

C ++ programcısı bir tanımlı tanımlamasa bile, C ++ derleyicisi bu tür yöntemleri dil ilkelerine göre oluşturmalı ve sonra başka kodların çalıştırılması gerekip gerekmediğini belirlemelidir memcpy. Örneğin, bir class/ structbir içeren std::vectoreleman bir kopyası-yapıcı olmayan önemsiz bir atama operatörü olması gerekiyor.

Diğer dillerde, kopya kurucuları ve nesne klonlaması önerilmez (nesnenin dil anlamına göre kesinlikle gerekli ve / veya uygulamanın anlambilimi hariç). Bu diller genellikle, kapsama dayalı sahiplik veya referans sayımı yerine erişilebilirliğe dayalı çöp toplama mekanizmasına sahip olacaktır.

C ++ (veya C) içinde bir başvuru veya işaretçi (sabit başvuru dahil) iletildiğinde, programcı adres değerinin yayılması dışında hiçbir özel kodun (kullanıcı tanımlı veya derleyici tarafından oluşturulan işlevler) yürütülmeyeceğinden emin olur. (başvuru veya işaretçi). Bu, C ++ programcılarının rahat bulduğu bir davranış netliğidir.

Bununla birlikte, arka plan, C ++ dilinin gereksiz bir şekilde karmaşık olmasıdır, öyle ki, bu davranış netliği nükleer serpinti bölgesinde bir vaha (hayatta kalabilen bir yaşam alanı) gibidir.

Daha fazla kutsama (veya hakaret) eklemek için C ++, kullanıcı tanımlı hareket operatörlerini (hareket oluşturucular ve hareket atama operatörleri) iyi performansla kolaylaştırmak için evrensel referanslar (r-değerleri) sunar. Bu, kopyalama ve derin klonlama ihtiyacını azaltarak, nesnelerin bir örnekten diğerine taşınması (aktarılması) için oldukça alakalı bir kullanım durumuna (aktarımına) yarar. Bununla birlikte, diğer dillerde, nesnelerin bu şekilde hareket etmesinden bahsetmek mantıksızdır.


(Konu dışı bölüm) "Hız İstiyor musunuz? Değere Göre Geç!" 2009 dolaylarında yazılmıştır.

Bu makale 2009 yılında yazılmıştır ve C ++ 'da r-değeri için tasarım gerekçesini açıklar. Bu makale önceki bölümdeki sonucum için geçerli bir karşı argüman sunuyor. Ancak, makalenin kod örneği ve performans iddiası uzun zamandır yalanlanmıştır.

Sub-TL; DR C ++ 'da r-değeri anlambiliminin tasarımı, Sortörneğin bir işlev üzerinde şaşırtıcı derecede zarif bir kullanıcı tarafı anlambilimine izin verir . Bu şıklığın diğer dillerde modellenmesi (taklit edilmesi) imkansızdır.

Tüm veri yapısına bir sıralama işlevi uygulanır. Yukarıda belirtildiği gibi, çok fazla kopyalama yapılması yavaş olacaktır. Bir performans optimizasyonu olarak (pratik olarak önemlidir), bir sıralama işlevi C ++ dışında birkaç dilde yıkıcı olacak şekilde tasarlanmıştır. Yıkıcı, hedef veri yapısının sıralama hedefine ulaşmak için değiştirildiği anlamına gelir.

C ++ 'da, kullanıcı iki uygulamadan birini aramayı seçebilir: daha iyi performansa sahip yıkıcı olan veya girişi değiştirmeyen normal bir uygulama. (Şablon kısalık için kullanılmaz.)

/*caller specifically passes in input argument destructively*/
std::vector<T> my_sort(std::vector<T>&& input)
{
    std::vector<T> result(std::move(input)); /* destructive move */
    std::sort(result.begin(), result.end()); /* in-place sorting */
    return result; /* return-value optimization (RVO) */
}

/*caller specifically passes in read-only argument*/ 
std::vector<T> my_sort(const std::vector<T>& input)
{
    /* reuse destructive implementation by letting it work on a clone. */
    /* Several things involved; e.g. expiring temporaries as r-value */
    /* return-value optimization, etc. */
    return my_sort(std::vector<T>(input));  
}

/*caller can select which to call, by selecting r-value*/
std::vector<T> v1 = {...};
std::vector<T> v2 = my_sort(v1); /*non-destructive*/
std::vector<T> v3 = my_sort(std::move(v1)); /*v1 is gutted*/    

Sıralama dışında, bu zarafet özyinelemeli bölümleme yoluyla bir dizide (başlangıçta ayrıştırılmamış) yıkıcı medyan bulma algoritmasının uygulanmasında da yararlıdır.

Ancak, çoğu dilde, dizilere yıkıcı bir sıralama algoritması uygulamak yerine, sınıflandırmaya dengeli bir ikili arama ağacı yaklaşımı uygulayacağını unutmayın. Bu nedenle, bu tekniğin pratik ilgisi göründüğü kadar yüksek değildir.


Derleyici optimizasyonu bu yanıtı nasıl etkiler?

Satır içi (ve ayrıca tüm program optimizasyonu / bağlantı zamanı optimizasyonu) birkaç işlev çağrısı seviyesine uygulandığında, derleyici veri akışını görebilir (bazen ayrıntılı olarak). Bu olduğunda, derleyici, bazıları bellekteki tüm nesnelerin oluşturulmasını ortadan kaldırabilen birçok optimizasyon uygulayabilir. Tipik olarak, bu durum geçerli olduğunda, parametrelerin değere göre mi yoksa sabit referansla mı iletildiği önemli değildir, çünkü derleyici tam olarak analiz edebilir.

Ancak, alt düzey işlev analiz dışında bir şeyi çağırırsa (örneğin, derleme dışındaki farklı bir kitaplıkta bulunan bir şey veya çok karmaşık bir çağrı grafiği), derleyici savunmacı bir şekilde en iyi duruma getirmelidir.

Bir makine kayıt değerinden daha büyük olan nesneler, açık bellek yükleme / saklama talimatları veya saygıdeğer memcpyişleve çağrı ile kopyalanabilir . Bazı platformlarda, derleyici iki bellek konumu arasında hareket etmek için SIMD talimatları üretir, her komut onlarca bayt taşır (16 veya 32).


Ayrıntı düzeyi veya görsel karmaşa üzerine tartışma

C ++ programcıları buna alışkındır, yani bir programcı C ++ 'dan nefret etmediği sürece, kaynak kodda const referansı yazma veya okuma yükü korkunç değildir.

Maliyet-fayda analizleri daha önce birçok kez yapılmış olabilir. Belirtilmesi gereken herhangi bir bilimsel olup olmadığını bilmiyorum. Sanırım çoğu analiz bilimsel değil ya da tekrarlanamaz.

İşte hayal ettiğim şey (kanıt veya güvenilir referanslar olmadan) ...

  • Evet, bu dilde yazılmış yazılımın performansını etkiler.
  • Derleyiciler kodun amacını anlayabiliyorsa, bunu otomatikleştirmek için yeterince akıllı olabilir.
  • Ne yazık ki, değişebilirliği tercih eden dillerde (işlevsel saflığın aksine), derleyici çoğu şeyi mutasyona uğramış olarak sınıflandırır, bu nedenle otomatikliğin sabit olarak çıkarılması çoğu şeyi const olmayan olarak reddeder
  • Zihinsel yük insanlara bağlıdır; Bunu yüksek bir zihinsel yük olarak gören insanlar C ++ 'ı geçerli bir programlama dili olarak reddederdi.

Keşke sadece bir tane seçmek zorunda kalmadan iki cevabı kabul edebileceğim durumlardan biri ... nefes
CharonX

8

DonaldKnuth'un "StructuredProgrammingWithGoToStatements" adlı makalesinde şunları yazdı: "Programcılar, programlarının kritik olmayan bölümlerinin hızını düşünmek veya bunlardan endişe etmek için çok fazla zaman harcıyorlar ve verimlilikteki bu girişimlerin aslında hata ayıklama ve bakım sırasında güçlü bir olumsuz etkisi olduğu düşünülüyor "Zamanın yaklaşık% 97'sini küçük verimlilikleri unutmalıyız: erken optimizasyon tüm kötülüklerin köküdür. Yine de bu kritik% 3'teki fırsatlarımızı kaçırmamalıyız." - Erken Optimizasyon

Bu, programcılara mevcut en yavaş teknikleri kullanmalarını önermez. Program yazarken netliğe odaklanmakla ilgilidir . Genellikle, netlik ve verimlilik bir değiş tokuştur: sadece birini seçmeniz gerekiyorsa, netliği seçin. Ancak her ikisini de kolayca başarabilirseniz, sadece verimliliği önlemek için netliği sakatlamaya gerek yoktur (bir şeyin sabit olduğunu belirtmek gibi).


3
"yalnızca birini seçmeniz gerekiyorsa, netlik seçin." İkincisini seçmek zorunda kalacağınız için ikincisi tercih edilmelidir .
Deduplicator

@Deduplicator Teşekkür ederim. Ancak OP bağlamında programcı seçme özgürlüğüne sahiptir.
Lawrence

Cevabınız bundan biraz daha genel okuyor ...
Deduplicator

@Deduplicator Ah, ama cevabımın içeriği programcının seçtiği (ayrıca) . Seçim programcıya zorlansaydı, seçim yapan "sen" olmaz :). Önerdiğiniz değişikliği düşündüm ve cevabımı buna göre düzenlemenize itiraz etmeyeceğim, ancak netliği için mevcut ifadeleri tercih ediyorum.
Lawrence

7

([Const] [rvalue] referansı) | (değer) ile geçmek arayüzün amacı ve vaatleri ile ilgili olmalıdır. Performansla hiçbir ilgisi yok.

Richy'nin Başparmak Kuralı:

void foo(X x);          // I intend to own the x you gave me, whether by copy, move or direct initialisation on the call stack.     

void foo(X&& x);        // I intend to steal x from you. Do not use it other than to re-assign to it after calling me.

void foo(X const& x);   // I guarantee not to change your x

void foo(X& x);         // I may modify your x and I will leave it in a defined state

3

Teorik olarak, cevap evet olmalı. Ve aslında, bu bazen bir süredir - aslında, sadece bir değeri geçmek yerine const referansı ile geçmek, geçen değerin tek bir sığmayacak kadar büyük olduğu durumlarda bile bir kötümseme olabilir kayıt (ya da insanların değer ne zaman geçip geçmeyeceklerini belirlemek için kullanmaya çalıştıkları diğer buluşsal yöntemlerin çoğu). Yıllar önce, David Abrahams "Hız İstiyor? Değere Göre Geç!" bu davaların bazılarını kapsamaktadır. Artık bulmak kolay değil, ancak bir kopyasını kazabiliyorsanız okumaya değer (IMO).

Bununla birlikte, const referansı ile geçme durumunda, deyimin o kadar iyi kurulduğunu söyleyebilirim ki durum aşağı yukarı tersine çevrilir: türün / / / olacağını bilmedikçe , insanlar const tarafından geçtiğini görmeyi beklerler varsayılan olarak başvurursanız, bu nedenle, aksi takdirde oldukça belirli bir nedeniniz olmadığı sürece bununla birlikte gitmek en iyisidir.charshortintlong

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.