Modern C ++ size ücretsiz performans sağlayabilir mi?


205

Bazen C ++ 11 / 14'ün yalnızca C ++ 98 kodunu derlerken bile performans artışı sağlayabileceği iddia edilir. Gerekçe genellikle hareket semantiği çizgileri üzerindedir, çünkü bazı durumlarda rvalue kurucuları otomatik olarak üretilir veya şimdi STL'nin bir parçasıdır. Şimdi bu vakaların daha önce RVO veya benzer derleyici optimizasyonları tarafından gerçekten ele alınıp alınmadığını merak ediyorum.

Benim sorum o zaman bana bir değişiklik olmadan yeni dil özelliklerini destekleyen bir derleyici kullanarak daha hızlı çalışan bir C ++ 98 kod parçası örneği verebilir eğer. Kopya seçimlerini yapmak için standart bir derleyici gerektirmediğini ve sadece bu nedenle hareket semantiğinin hız getirebileceğini anlıyorum, ancak daha az patolojik bir durum görmek istiyorum.

DÜZENLEME: Açıkça söylemek gerekirse, yeni derleyicilerin eski derleyicilerden daha hızlı olup olmadığını sormuyorum, bunun yerine derleyici bayraklarına -std = c ++ 14 eklemenin daha hızlı çalışacağını (kopyalardan kaçının, ancak anlambilimin yanı sıra başka herhangi bir şey de olabilir, ben de ilgilenirim)


3
Bir kopya oluşturucu kullanarak yeni bir nesne oluştururken kopya elizyonu ve dönüş değeri optimizasyonunun gerçekleştirildiğini unutmayın. Bununla birlikte, bir kopya atama işlecinde kopya elizyonu yoktur (derleyici geçici olmayan önceden oluşturulmuş bir nesneyle ne yapacağını bilmediğinden nasıl olabilir). Bu nedenle, bu durumda, bir taşıma atama operatörü kullanma olanağı vererek C ++ 11/14 büyük kazanır. Sorunuz hakkında olsa da, bir C ++ 11/14 derleyicisi tarafından derlenmişse C ++ 98 kodunun daha hızlı olması gerektiğini düşünmüyorum, derleyici daha yeni olduğu için belki daha hızlıdır.
vsoftco

27
Ayrıca C ++ 98 ile tam uyumlu olsanız bile standart kütüphaneyi kullanan kod potansiyel olarak daha hızlıdır, çünkü C ++ 11 / 14'te temel kütüphane mümkünse dahili olarak hareket semantiği kullanır. Bu nedenle, C ++ 98 ve C ++ 11 / 14'te aynı görünen kod, ikinci durumda (muhtemelen) daha hızlı olacaktır, vektörler, listeler vb. Gibi standart kütüphane nesnelerini her kullandığınızda ve anlambilimi değiştirdiğinizde fark yaratır.
vsoftco

1
@vsoftco, Bu, bahsettiğim bir durum, ancak bir örnekle gelemedi: Kopyalama yapıcısını tanımlamak zorunda kaldığımda hatırladığım kadarıyla, hareket yapıcı otomatik olarak oluşturulmaz, bu da bizi bırakır bence RVO'nun her zaman işe yaradığı çok basit sınıflar. Bir istisna, değer oluşturucularının kütüphane uygulayıcısı tarafından oluşturulduğu STL kaplarıyla birlikte bir şey olabilir (yani, hareketleri kullanması için koddaki hiçbir şeyi değiştirmek zorunda kalmayacağım).
Alarge

kopya oluşturucuya sahip olmamak için sınıfların basit olması gerekmez. C ++ değer semantiği üzerinde gelişir ve kopya yapıcı, atama operatörü, yıkıcı vb. İstisna olmalıdır.
sp2danny

1
@ Eric Bağlantı için teşekkür ederim, ilginçti. Bununla birlikte, hızlı bir şekilde inceledikten sonra, içindeki hız avantajları çoğunlukla std::moveyapıcıları ekleme ve taşıma (mevcut kodda değişiklik yapılmasını gerektirecek) gibi görünüyor. Sorumla gerçekten ilgili tek şey, herhangi bir örnekle desteklenmeyen "Sadece yeniden derleyerek hızlı hız avantajları elde edersiniz" cümlesiydi (sorumda yaptığım gibi aynı slaytta STL'den bahsediyor, ancak belirli bir şey yok) ). Bazı örnekler istiyordum. Slaytları yanlış okuyorsam bana haber ver.
Alarge

Yanıtlar:


221

C ++ 11 derleyicisini yeniden derlemenin, C ++ 11'in uygulama kalitesi ile neredeyse ilgisiz olan sınırsız performans artışlarına neden olabileceği 5 genel kategorinin farkındayım. Bunlar hareket semantiğinin çeşitleridir.

std::vector yeniden tahsis

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

Her seferinde foo'ın tampon 03 C ++ yeniden tahsis edilmektedir her kopyalanan vectoriçinde bar.

C ++ 11'de bunun yerine bar::datatemelde ücretsiz olan s'yi taşır .

Bu durumda, bu stdkonteyner içindeki optimizasyonlara dayanır vector. Aşağıdaki her durumda, stdkapsayıcıların kullanımı, movederleyicinizi yükselttiğinizde C ++ 11'de "otomatik olarak" etkin semantiğe sahip C ++ nesneleri oldukları içindir . Bir stdkap içeren onu engellemeyen nesneler de otomatik geliştirilmiş moveyapıcıları devralır .

NRVO hatası

NRVO (adlandırılmış dönüş değeri optimizasyonu) başarısız olduğunda, C ++ 03'te kopyaya geri döner, C ++ 11'de tekrar harekete geçer. NRVO'nun arızaları kolaydır:

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.push_back(i);
  return v;
}

ya da:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

Üç değerimiz var - dönüş değeri ve işlev içinde iki farklı değer. Elision, fonksiyon içindeki değerlerin dönüş değeriyle 'birleştirilmesine' izin verir, ancak birbiriyle birleştirilmesine izin verir. Her ikisi de birbirleriyle birleşmeden dönüş değeri ile birleştirilemez.

Temel sorun, NRVO elüsyonunun kırılgan olması ve returnsitenin yakınında olmayan değişikliklere sahip kodun , herhangi bir tanı yayılmadan aniden büyük performans düşüşlerine sahip olabilmesidir. Çoğu NRVO başarısızlık durumunda C ++ 11 a ile move, C ++ 03 ise bir kopya ile sonlanır.

İşlev bağımsız değişkenini döndürme

Burada seçim yapmak da imkansız:

std::set<int> func(std::set<int> in){
  return in;
}

C ++ 11 bu ucuz: C ++ 03 kopyasını önlemek için hiçbir yolu yoktur. Parametrelerin ve dönüş değerinin ömrü ve konumu, arama kodu tarafından yönetildiği için işlevlere ilişkin bağımsız değişkenler döndürülemez.

Ancak, C ++ 11 birinden diğerine geçebilir. (Daha az oyuncak örneğinde, için bir şey yapılabilir set).

push_back veya insert

Son olarak kaplara eleme gerçekleşmez: ancak C ++ 11, kopyaları kaydeden rvalue move insert operatörlerini aşırı yükler.

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );

C ++ 03'te bir geçici whateveroluşturulur, daha sonra vektöre kopyalanır v. std::stringHer biri aynı veriye sahip 2 tampon ayrılır ve biri atılır.

C ++ 11'de geçici whateveroluşturulur. whatever&& push_backAşırı sonra movevektör içine geçici s v. Bir std::stringtampon tahsis edilir ve vektöre taşınır. Boş std::stringbir atılır.

Görev

@ Jarod42'nin aşağıdaki cevabından çalındı.

Seçim atama ile gerçekleşemez, ancak geçiş yapılabilir.

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

burada some_functionbir adayı elide olarak döndürür, ancak doğrudan bir nesne inşa etmek için kullanılmadığından, seçilemez. C ++ 03'te, yukarıdaki geçici kopyaya kopyalanır some_value. C ++ 11'de, some_valuetemelde ücretsiz olan içine taşınır .


Yukarıdakilerin tam etkisi için, hareket yapıcılarını ve atamayı sizin için sentezleyen bir derleyiciye ihtiyacınız vardır.

MSVC 2013, hareket yapıcılarını stdkaplarda uygular , ancak hareket yapıcılarını türleriniz üzerinde sentezlemez.

Bu nedenle, std::vectors ve benzerlerini içeren türler MSVC2013'te bu tür iyileştirmeler elde etmez, ancak MSVC2015'te almaya başlayacaktır.

clang ve gcc uzun zamandan beri örtük hareket yapıcıları uygulamaktadır. Intel'in 2013 derleyicisi, geçerseniz örtük nesil oluşturucuları destekleyecektir -Qoption,cpp,--gen_move_operations(MSVC2013 ile çapraz uyumlu olma çabasıyla bunu varsayılan olarak yapmazlar).


1
@alarge evet. Ancak bir hareket yapıcısının bir kopya kurucudan çok daha verimli olması için, genellikle kaynakları kopyalamak yerine taşımak gerekir. Kendi hareket kurucularınızı yazmadan (ve yalnızca bir C ++ 03 programını yeniden derlemeden), stdkütüphane kaplarının tümü move"ücretsiz" kurucuları ve (engellemediyseniz) söz konusu nesneleri kullanan ( ve söz konusu nesneler) birçok durumda serbest hareket inşaatı yapmaya başlayacaktır. Bu durumların çoğu C ++ 03'teki seçimler kapsamındadır: hepsi değil.
Yakk - Adam Nevraumont

5
Bu kötü bir iyileştirici uygulaması, o zaman, döndürülen farklı adlandırılmış nesnelerin çakışan ömrü olmadığı için, RVO teorik olarak hala mümkündür.
Ben Voigt

2
@alarge Örtüşen yaşam süreleri olan iki nesnenin üçüncüye dönüştürülebildiği, ancak birbirinin yerine geçemeyeceği gibi, seçimin başarısız olduğu yerler var. Daha sonra C ++ 11'de taşıma gereklidir ve C ++ 03'e kopyalayın (if gibi yok sayılıyor). Seçim pratikte kırılgandır. stdYukarıdaki kapların kullanımı çoğunlukla, C ++ 03'i derlerken C ++ 11'de 'ücretsiz' olarak aldığınız kopya türüne aşırı derecede hareket etmek için ucuz olmalarıdır. Bu vector::resizebir istisnadır: moveC ++ 11'de kullanır .
Yakk - Adam Nevraumont

27
Yalnızca hareket semantiği olan 1 genel kategori ve bunun 5 özel durumu görüyorum.
Johannes Schaub - litb

3
@sebro Anlıyorum, "programların pek çok 1000baytlık tahsisi ayırmamasına neden oluyor ve bunun yerine işaretçileri hareket ettiriyor" diye düşünmüyorsunuz. Zamanlanmış sonuçlar istiyorsunuz. Mikrobenkmarklar, performans iyileştirmelerinin temelde daha az yaptığınız kanıtlardan daha fazla kanıtı değildir. Çok çeşitli endüstrilerdeki 100 gerçek dünya uygulamasından kısa olanı, gerçek dünyadaki görevlerin profillenmesi gerçekten kanıt değildir. "Ücretsiz performans" hakkında belirsiz iddialarda bulundum ve C ++ 03 ve C ++ 11 kapsamındaki program davranışlarındaki farklılıklar hakkında onlara özel gerçekler yaptım.
Yakk - Adam Nevraumont

46

şuna benzer bir şey varsa:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

C ++ 03'te bir kopyasını alırken C ++ 11'de bir taşıma ataması var. bu durumda ücretsiz optimizasyona sahip olursunuz.


4
@Yakk: Kopyada seçim nasıl yapılır?
Jarod42

2
@ Jarod42 Ayrıca, sol taraf zaten oluşturulmuş olduğundan ve bir derleyicinin kaynakları sağdan çaldıktan sonra "eski" verilerle ne yapacağını bilmesi için makul bir yol olmadığından, bir atamada kopya seçiminin mümkün olmadığına inanıyorum el tarafı. Ama belki yanılıyorum, cevabı bir kez ve sonsuza dek öğrenmek isterim. Nesne "yeni" olduğundan ve eski verilerle ne yapılacağına karar vermekte sorun olmadığından, kopya kopya yapısı yapıyı kopyaladığınızda mantıklıdır. Bildiğim kadarıyla, tek istisna şudur: "Ödevler sadece if-kuralına göre
seçilebilir

4
İyi C ++ 03 kodu bu durumda zaten bir hamle yaptıfoo().swap(v);
Ben Voigt

@BenVoigt emin olun, ancak tüm kodlar optimize edilmemiştir ve bunun gerçekleştiği tüm noktalara ulaşmak kolay değildir.
Yakk - Adam Nevraumont

Kopyalama kararı, @BenVoigt'ın dediği gibi bir ödevde çalışabilir. Daha iyi terim RVO'dur (dönüş değeri optimizasyonu) ve yalnızca foo () bu şekilde uygulandığında çalışır.
DrumM
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.