C ++ 11'in 'otomatik' kullanımı performansı artırabilir mi?


230

autoC ++ 11 türünün neden doğruluk ve sürdürülebilirliği geliştirdiğini görebiliyorum. Ben de performansı artırabilir okudum ( Herb Sutter tarafından Neredeyse Her Zaman Otomatik ), ama iyi bir açıklama özledim.

  • autoPerformansı nasıl geliştirebilirim?
  • Herkes bir örnek verebilir mi?

5
Örneğin, gadget'tan widget'a kadar, yanlışlıkla örtük dönüşümlerden kaçınmaktan bahseden otlarutter.com/2013/06/13/… konusuna bakın . Bu yaygın bir sorun değil.
Jonathan Wakely

42
Bir performans iyileştirmesi olarak “kasıtsız olarak kötüleşmeyi daha az olası kılıyor” mu kabul ediyorsunuz?
5gon12eder

1
Belki gelecekte kod temizleme performansı, belki
Croll

Kısa bir cevaba ihtiyacımız var: Hayır, iyiyseniz. 'Noobish' hatalarını önleyebilir. C ++, sonuçta yapmayanları öldüren bir öğrenme eğrisine sahiptir.
Alec Teal

Yanıtlar:


309

autosessiz örtük dönüşümlerden kaçınarak performansa yardımcı olabilir . Zorlayıcı bulduğum bir örnek şudur.

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}

Hatayı gördün mü? Burada, haritadaki her öğeyi const referansı ile zarif bir şekilde aldığımızı ve niyetimizi netleştirmek için yeni aralık ifadesini kullandığımızı düşünüyoruz, ancak aslında her öğeyi kopyalıyoruz . Çünkü std::map<Key, Val>::value_typeöyle std::pair<const Key, Val>değil std::pair<Key, Val>. Böylece, (örtük olarak) sahip olduğumuz zaman:

std::pair<Key, Val> const& item = *iter;

Varolan bir nesneye başvuru yapmak ve bunu ona bırakmak yerine bir tür dönüştürme yapmak zorundayız. Kullanılabilir bir örtük dönüştürme olduğu sürece farklı türde bir nesneye (veya geçici) bir const referansı almanıza izin verilir, örneğin:

int const& i = 2.0; // perfectly OK

Tip dönüşüm bir dönüştürebilirsiniz aynı nedenle izin verilen bir örtülü dönüşüm const Keybir etmek Key, ama biz bunun için izin vermek için yeni tip bir geçici inşa etmek gerekir. Böylece, döngümüz etkili bir şekilde yapar:

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(Tabii ki, aslında bir __tmpnesne yok, sadece örnekleme için var, gerçekte isimsiz geçici itemyaşamı boyunca bağlı ).

Sadece şuna değiştiriliyor:

for (auto const& item : m) {
    // do stuff
}

bize bir ton kopya kaydetti - şimdi referans verilen tür başlatıcı türüyle eşleşiyor, bu yüzden geçici veya dönüşüm gerekli değil, sadece doğrudan bir referans yapabiliriz.


19
@Barry Derleyiciye an std::pair<const Key, Val> const &olarak davranmaya çalışmaktan şikayet etmek yerine niçin mutlu kopyalar çıkaracağını açıklayabilir misiniz std::pair<Key, Val> const &? C ++ 11'de yeni olan, nasıl aralıklı olacağından ve buna nasıl autogirdiğinden emin değil .
Agop

@Barry Açıklama için teşekkürler. Bu eksik olduğum parça - bir nedenden dolayı, geçici bir şeye sürekli başvuru yapamayacağınızı düşündüm. Ama elbette yapabilirsiniz - kapsamının sonunda var olmak sona erecek.
Agop

@barry Seni alıyorum, ama sorun şu ki, autoperformansı artıran tüm nedenleri kapsayan bir cevap yok . Bu yüzden aşağıdaki kendi kelimelerimle yazacağım.
Yakk - Adam Nevraumont

38
Hala bunun " autoperformansı geliştirdiğinin " kanıtı olduğunu düşünmüyorum . " autoPerformansı yok eden programcı hatalarının önlenmesine yardımcı olan" bir örnek . İkisi arasında ince ama önemli bir ayrım olduğunu belirtiyorum. Yine de +1.
Yörüngedeki Hafiflik Yarışları

70

Çünkü autobaşlatılıyor ifade deduces türü, alan hiçbir tür dönüştürmesi yoktur. Geçici algoritmalar ile birleştiğinde, bu, kendiniz bir tür oluşturmanızdan daha doğrudan bir hesaplama yapabileceğiniz anlamına gelir - özellikle türünü adlandıramadığınız ifadelerle uğraşırken!

Tipik bir örnek aşağıdakileri kullanarak (ab) 'den gelir std::function:

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);

İle cmp2ve cmp3, bir inşa eğer oysa karşılaştırma çağrıyı satır içine alabilirsiniz tüm algoritma std::functionnesnesi, sadece çağrı satır içine yerleştirilmiş olamaz, ama aynı zamanda fonksiyon sargının tip sildim iç polimorfik arama geçmek zorunda.

Bu temanın başka bir varyantı diyebilirsiniz:

auto && f = MakeAThing();

Bu her zaman bir işlevdir, işlev çağrısı ifadesinin değerine bağlıdır ve hiçbir zaman ek nesne oluşturmaz. Döndürülen değerin türünü bilmiyorsanız, böyle bir şeyle yeni bir nesne (belki de geçici olarak) oluşturmak zorunda kalabilirsiniz T && f = MakeAThing(). (Üstelik, auto &&dönüş tipi hareketli olmadığında ve dönüş değeri bir öndeğer olduğunda bile çalışır.)


Yani bu "tipi silmekten kaçının" sebebidir auto. Diğer varyantınız "kazara kopyalardan kaçınmak" tır, ancak bezeme gerekir; neden autooraya yazmanız gerektiğinde size hız veriyor? (Sanırım cevap "türü yanlış anlıyorsunuz ve sessizce dönüşüyor") Bu da Barry'nin cevabını daha az açıklanmış bir örnek haline getiriyor, değil mi? Yani, iki temel durum vardır: tür silinmesini önlemek için otomatik ve yanlışlıkla her ikisi de bir çalışma süresi maliyeti olan sessiz tür hatalarını önlemek için otomatik.
Yakk - Adam Nevraumont

2
"çağrı sadece inline olamaz" - neden o zaman? Veri ilgili uzmanlık eğer akış analizi sonra prensip şey önler çağrı devirtualized ediliyor anlamına mı std::bind, std::functionve std::stable_partitiontüm inlined edilmiştir? Ya da sadece pratikte hiçbir C ++ derleyicisi karmaşayı çözmek için yeterince agresif bir şekilde sıraya girmeyecek mi?
Steve Jessop

@SteveJessop: Çoğunlukla ikincisi - kurucudan geçtikten sonra std::function, özellikle küçük işlevli optimizasyonlarla gerçek çağrıda görmek çok karmaşık olacaktır (böylece aslında sanallaştırma istemezsiniz). Tabii prensipte her şey sanki ...
Kerrek SB

41

İki kategori var.

autotipi silmeyi önleyebilir. Adsız türler (lambdalar gibi) ve neredeyse adsız türler (şeylerin sonucu std::bindveya diğer ifade şablonu gibi) vardır.

Olmadan auto, verileri silmek gibi bir şeye yazmak zorunda kalırsınız std::function. Tip silme maliyeti vardır.

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};

task1tip silme yükü vardır - olası bir yığın ayırma, satır içi zorluk ve sanal işlev tablosu çağırma yükü. task2yok. Lambdaslar , tip silmeden saklamak için otomatik veya başka türden kesinti biçimlerine ihtiyaç duyarlar ; diğer türler o kadar karmaşık olabilir ki sadece pratikte ihtiyaç duyarlar.

İkincisi, türleri yanlış anlayabilirsiniz. Bazı durumlarda, yanlış tür mükemmel bir şekilde çalışır, ancak bir kopyaya neden olur.

Foo const& f = expression();

expression()döner Bar const&veya eğer , Barhatta Bar&nereden Fooinşa edilebilir derleyecektir Bar. Bir geçici Fooyaratılacak, sonra bağlanacak fve ömrü sona erene kadar uzatılacak f.

Programcı, burada Bar const& fbir kopya oluşturmayı amaçlamış olabilir ve olmayabilir, ancak bir kopyasını da dikkate almadan yapabilirsiniz.

En yaygın örnek türüdür *std::map<A,B>::const_iteratorolduğunu std::pair<A const, B> const&değil std::pair<A,B> const&, ancak hata sessizce maliyet performansı o hataların bir kategoridir. Bir ' std::pair<A, B>den a std::pair<const A, B>. (Haritadaki anahtar sabittir, çünkü haritayı düzenlemek kötü bir fikirdir)

Hem @Barry hem de @KerrekSB ilk önce bu iki ilkeyi cevaplarında gösterdiler. Bu, basitçe, iki konuyu tek bir cevapta, örnek odaklı olmaktan ziyade problemi hedefleyen ifadelerle vurgulamaya yönelik bir girişimdir.


9

Mevcut üç cevaplar kullanarak örnek vermek autoyardımcı olur “daha az olası istemeden için pessimize yapar” etkili olarak "performansını artırmak" yapma.

Madalyonun bir flip tarafı var. autoTemel nesneyi döndürmeyen işleçleri olan nesnelerle kullanmak yanlış (hala derlenebilir ve çalıştırılabilir) kodla sonuçlanabilir. Örneğin, bu soruauto Eigen kütüphanesini kullanarak farklı (yanlış) sonuçların nasıl kullanıldığını sorar , yani aşağıdaki satırlar

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;

farklı çıktı ile sonuçlandı. Kuşkusuz, bu çoğunlukla Eigens tembel değerlendirmesinden kaynaklanmaktadır, ancak bu kod (kütüphane) kullanıcısı için şeffaftır / olmalıdır.

Performans burada büyük ölçüde etkilenmese de, autokasıtsız kötümserlikten kaçınmak için kullanmak erken optimizasyon veya en azından yanlış olarak sınıflandırılabilir;).


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.