C ++ standart kütüphane uygulamalarını karşılaştıran / kıyaslayan belgeler var mı? [kapalı]


16

(Bu kendi başına oyun programlama değil, ama eğer tarihte bize her büyük oyunun bu şeyler hakkında endişe duyduğunu söylese bile, SO'da bunu erken yapmamam söylenecek olsaydı, eminim.)

Farklı C ++ standart kitaplığı uygulamaları arasındaki performans farklarını ve özellikle bellek kullanımını özetleyen bir belge var mı? Bazı uygulamaların ayrıntıları NDA tarafından korunmaktadır, ancak STLport ile libstdc ++ ve libc ++ ve MSVC / Dinkumware (EASTL?) Arasında bir karşılaştırma bile son derece yararlı görünmektedir.

Özellikle şu soruların cevaplarını arıyorum:

  • Standart kaplarla ne kadar bellek ek yükü ilişkilidir?
  • Hangi konteynerler (varsa), yalnızca dinamik ayırmalar bildirilerek yapılır?
  • Std :: string yazarken kopyalama yapar mı? Kısa dize optimizasyonu? Halatlar?
  • Std :: deque bir halka tamponu mu kullanıyor yoksa bok mu?

Ben dequeher zaman bir vektör ile STL uygulanan bir izlenim altında idi.
Tetrad

@Tetrad: Birkaç hafta öncesine kadar ben de öyleydim, ama sonra genellikle ip benzeri bir yapı tarafından uygulandığını okudum - ve bu STLport'taki gibi görünüyor.

STL, uygulanan çeşitli veri yapıları (sıralı ve ilişkilendirici), algoritmalar ve yardımcı sınıflar hakkında bilgi bulmak için kullanılabilecek açık bir çalışma taslağına sahiptir . Ancak, bellek ek yükünün tanımlanmış olan özelliklerden ziyade uygulamaya özel olduğu görülmektedir.
Thomas Russell

3
@Duck: Oyun geliştirme, düzenli olarak yüksek seviyeli C ++ özelliklerini kullanan ancak düşük bellek olmayan sanal bellek sistemlerinde çalıştığı için bellek ayırmalarını titizlikle izlemesi gereken farkında olduğum tek yerdir. SO'daki her bir cevap "erken optimize etmeyin, STL iyidir, kullanın!" - Şimdiye kadar cevapların% 50'si - ve yine de Maik'in testi std :: map'i kullanmak isteyen oyunlar ve Tetrad'ın ortak std :: deque uygulamaları hakkındaki kafa karışıklığı ve mayın gibi açıkça önemli bir endişesini gösteriyor.

2
@Joe Wreschnig Kapatmak için oy vermek istemiyorum çünkü bunun sonucuyla ilgileniyorum. : p
Komünist Ördek

Yanıtlar:


6

Böyle bir karşılaştırma tablosu bulamazsanız, alternatif, söz konusu STL sınıflarına kendi ayırıcı enjekte etmek ve bazı günlükler eklemektir.

Test ettiğim uygulama (VC 8.0) sadece bir dize / vektör / deque bildirerek bellek ayırma kullanmaz, ancak bunun için liste ve harita yapar. 3 karakter eklemek bir ayırmayı tetiklemediğinden, dizenin kısa dize optimizasyonu vardır. Çıktı kodun altına eklenir.

// basic allocator implementation used from here
// http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4079

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <map>

template <class T> class my_allocator;

// specialize for void:
template <> 
class my_allocator<void> 
{
public:
    typedef void*       pointer;
    typedef const void* const_pointer;
    // reference to void members are impossible.
    typedef void value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };
};

#define LOG_ALLOC_SIZE(call, size)      std::cout << "  " << call << "  " << std::setw(2) << size << " byte" << std::endl

template <class T> 
class my_allocator 
{
public:
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };

    my_allocator() throw() : alloc() {}
    my_allocator(const my_allocator&b) throw() : alloc(b.alloc) {}

    template <class U> my_allocator(const my_allocator<U>&b) throw() : alloc(b.alloc) {}
    ~my_allocator() throw() {}

    pointer       address(reference x) const                    { return alloc.address(x); }
    const_pointer address(const_reference x) const              { return alloc.address(x); }

    pointer allocate(size_type s, 
               my_allocator<void>::const_pointer hint = 0)      { LOG_ALLOC_SIZE("my_allocator::allocate  ", s * sizeof(T)); return alloc.allocate(s, hint); }
    void deallocate(pointer p, size_type n)                     { LOG_ALLOC_SIZE("my_allocator::deallocate", n * sizeof(T)); alloc.deallocate(p, n); }

    size_type max_size() const throw()                          { return alloc.max_size(); }

    void construct(pointer p, const T& val)                     { alloc.construct(p, val); }
    void destroy(pointer p)                                     { alloc.destroy(p); }

    std::allocator<T> alloc;
};

int main(int argc, char *argv[])
{

    {
        typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > my_string;

        std::cout << "===============================================" << std::endl;
        std::cout << "my_string ctor start" << std::endl;
        my_string test;
        std::cout << "my_string ctor end" << std::endl;
        std::cout << "my_string add 3 chars" << std::endl;
        test = "abc";
        std::cout << "my_string add a huge number of chars chars" << std::endl;
        test += "d df uodfug ondusgp idugnösndögs ifdögsdoiug ösodifugnösdiuödofu odsugöodiu niu od unoudö n nodsu nosfdi un abc";
        std::cout << "my_string copy" << std::endl;
        my_string copy = test;
        std::cout << "my_string copy on write test" << std::endl;
        copy[3] = 'X';
        std::cout << "my_string dtors start" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "vector ctor start" << std::endl;
        std::vector<int, my_allocator<int> > v;
        std::cout << "vector ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            v.push_back(i);
        }
        std::cout << "vector dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "deque ctor start" << std::endl;
        std::deque<int, my_allocator<int> > d;
        std::cout << "deque ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "deque insert start" << std::endl;
            d.push_back(i);
            std::cout << "deque insert end" << std::endl;
        }
        std::cout << "deque dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "list ctor start" << std::endl;
        std::list<int, my_allocator<int> > l;
        std::cout << "list ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "list insert start" << std::endl;
            l.push_back(i);
            std::cout << "list insert end" << std::endl;
        }
        std::cout << "list dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "map ctor start" << std::endl;
        std::map<int, float, std::less<int>, my_allocator<std::pair<const int, float> > > m;
        std::cout << "map ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "map insert start" << std::endl;
            std::pair<int, float> a(i, (float)i);
            m.insert(a);
            std::cout << "map insert end" << std::endl;
        }
        std::cout << "map dtor starts" << std::endl;
    }

    return 0;
}

Şimdiye kadar VC8 ve STLPort 5.2 test edildi, karşılaştırma (teste dahil edildi: dize, vektör, deque, liste, harita)

                    Allocation on declare   Overhead List Node      Overhead Map Node

VC8                 map, list               8 Byte                  16 Byte
STLPort 5.2 (VC8)   deque                   8 Byte                  16 Byte
Paulhodge's EASTL   (none)                  8 Byte                  16 Byte

VC8 çıkış dizesi / vektör / deque / list / map:

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    128 byte
my_string copy
  my_allocator::allocate    128 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  128 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    12 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate  12 byte
  my_allocator::allocate    24 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  24 byte

===============================================
deque ctor start
deque ctor end
deque insert start
  my_allocator::allocate    32 byte
  my_allocator::allocate    16 byte
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
  my_allocator::allocate    16 byte
deque insert end
deque dtor starts
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
  my_allocator::allocate    12 byte
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
  my_allocator::allocate    24 byte
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

STLPort 5.2. VC8 ile derlenmiş çıktı

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::deallocate   0 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
deque ctor start
  my_allocator::allocate    32 byte
  my_allocator::allocate    128 byte
deque ctor end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque dtor starts
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

EASTL sonuçları, deque mevcut değil

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
  my_allocator::allocate     9 byte
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
  my_allocator::deallocate   9 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

Bu, temel tahsislerin ayrıntılarını elde etmek için yararlıdır, ancak maalesef genel gider ve beklenen önbellek performansı hakkında hiçbir şey söylemez.

@ Doğru, tüm sorularınızı tek bir cevapla ele almak zor. "Tepegöz" ile tam olarak ne demek istediğinizden emin değilim. Tepeden tırnağa bellek tüketimi demek istediğinizi düşündüm.
Maik Semder

"Tepegöz" derken, yapıların ve ilgili tüm yineleyicilerin boş örneklerinin büyüklüğünü ve daha karmaşık olanların ayırmayı nasıl ele aldığını kastediyorum - örneğin std :: list dahili olarak bir seferde birden fazla düğümü tahsis ediyor mu, yoksa her düğüm için temel tahsis bedelini ödemek, vb?

1
Soru çok fazla değil "Lütfen bu karşılaştırmayı yapın" olarak "bu karşılaştırma için bir kaynaktır" - SO'yu "korumak" için iyi bir yer olduğunu düşünmüyorum. Belki de bir Google sitesine veya wiki'ye veya başka bir şeye atmaya başlamalısınız.

1
@ İyi şimdi burada: p Başka bir siteye taşımakla çok ilgilenmiyorum, sadece sonuçlarla ilgileniyordum.
Maik Semder

8

std::stringyazma üzerine kopya yapmaz. CoW eskiden bir optimizasyondu, ancak birden fazla iş parçacığı resme girer girmez bir karamsarlığın ötesinde - kodu büyük faktörlerle yavaşlatabilir. O kadar kötü ki, C ++ 0x Standard onu aktif olarak bir uygulama stratejisi olarak yasaklıyor. Sadece bu değil, std::stringdeğişebilir yineleyiciler ve karakter referansları ile izin verilebilirliği, std::stringneredeyse her işlem için "yazma" anlamına gelir .

Kısa dize optimizasyonu yaklaşık 6 karakter, inanıyorum, ya da o bölgede bir şey. Halatlara izin verilmez - işlev std::stringiçin bitişik belleği saklamalıdır c_str(). Teknik olarak, aynı sınıfta hem bitişik bir ipi hem de bir ipi koruyabilirsiniz, ancak hiç kimse bunu yapmadı. Dahası, ipler hakkında bildiklerimden, onları manipüle etmek için iplik açısından güvenli hale getirmek inanılmaz derecede yavaş olurdu - belki de CoW'den daha kötü veya daha kötü olurdu.

Modern STL'lerde bildirilen hiçbir kapsayıcı bellek ayırma işlemi yapmaz. Liste ve harita gibi düğüm tabanlı kaplar bunu yapmak için kullanıldı, ancak şimdi gömülü bir son optimizasyona sahipler ve buna ihtiyaç duymuyorlar. Boş bir kapla takas yaptığınız yerde "swaptimization" adı verilen bir optimizasyon yapmak yaygındır. Düşünmek:

std::vector<std::string> MahFunction();
int main() {
    std::vector<std::string> MahVariable;
    MahFunction().swap(MahVariable);
}

Elbette, C ++ 0x'de bu gereksizdir, ancak C ++ 03'te daha sonra bu yaygın olarak kullanıldığında, MahVariable bildirime bellek ayırırsa etkinliği azaltır. Bunun vector, MSVC9 STL gibi , elemanların kopyalanması ihtiyacını ortadan kaldıran konteynerlerin daha hızlı yeniden tahsis edilmesi için kullanıldığını biliyorum .

dequebağlantısız bağlantı listesi olarak adlandırılan bir şey kullanır. Temel olarak, genellikle düğüm içinde sabit boyutlu dizilerin bir listesidir. Bu nedenle, çoğu kullanım için, her iki veri yapısının - bitişik erişim ve itfa edilmiş O (1) çıkarılmasının ve hem ön hem de arkaya ekleyebilme ve daha iyi yineleyici geçersiz kılma avantajlarını korur vector. dequealgoritmik karmaşıklığı ve yineleyici geçersiz kılma garantileri nedeniyle hiçbir zaman vektör tarafından uygulanamaz.

Ne kadar bellek ek yükü ilişkilidir? Dürüst olmak gerekirse, bu sorulması biraz değersiz bir soru. STL kapları verimli olacak şekilde tasarlanmıştır ve işlevlerini çoğaltırsanız, daha kötü performans gösteren bir şeyle karşılaşırsınız veya aynı noktada tekrar karşılaşırsınız. Temel veri yapılarını bilerek, kullandıkları, verdikleri veya aldıkları bellek yükünü bilebilirsiniz ve sadece küçük dize optimizasyonu gibi iyi bir nedenden ötürü bundan daha fazlası olacaktır.


"C ++ 0x Standardının aktif olarak bir uygulama stratejisi olarak yasaklanması o kadar kötü ki." Ve bunu yasaklıyorlar çünkü önceki uygulamalar onu kullandı veya kullanmaya çalıştı. Görünüşe göre herkesin en iyi şekilde uygulanmış en yeni STL'yi her zaman kullandığı bir dünyada yaşıyorsunuz. Bu cevap hiç yardımcı olmuyor.

Ayrıca std :: deque'nin bitişik olarak saklanan bir depolamayı engellediğini düşündüğünüz özellikleri de merak ediyorum - yineleyiciler, yalnızca başlangıçta / uçta kaldırmadan sonra, ortada değil, bir vektörle kolayca yapılabilen herhangi bir eklemeden sonra geçerlidir. Dairesel bir tampon kullanarak tüm algoritmik garantileri karşılıyor gibi görünüyor - amortize O (1) uçlarda ekleme ve silme, ortadaki O (n) silme.

3
@Joe: Bence CoW 90'ların sonlarından beri kötü bir şey olarak kaydedildi. Özellikle CString kullanılan dize uygulamaları vardır, ancak bu std::string, zamanın yaptığı anlamına gelmez . Bunun için en son ve en büyük STL uygulamalarını kullanmanız gerekmez. msdn.microsoft.com/en-us/library/22a9t119.aspx "Önden bir öğe eklenirse , tüm referanslar geçerliliğini korur" der. Dairesel bir arabellekle nasıl uygulamak istediğinizden emin değilsiniz, çünkü dolduğunda yeniden boyutlandırmanız gerekecek.
DeadMG


Kesinlikle COW'u bir uygulama tekniği olarak savunmayacağım, ama aynı zamanda yazılımın zayıf olarak tanımlanmasından çok sonra kötü teknikler kullanılarak uygulanmaya devam ettiği konusunda da naif değilim. Örneğin, Maik'in yukarıdaki testi, deklarasyona tahsis edilen modern bir stdlib'i ortaya koymaktadır. Deque referans geçerliliği işaretçisi için teşekkürler. (Nitpick için, bir vektör yineleyici geçersiz kılma ve algoritmik karmaşıklık ile ilgili tüm garantileri karşılayabilir; bu gereklilik de değildir.) Bir şey varsa, bunu sorumun istediği gibi bir belgeye daha fazla ihtiyaç olarak görüyorum.

2

Soru çok fazla değil "Lütfen bu karşılaştırmayı yap", "bu karşılaştırma için bir kaynak nerede"

Bu soru gerçekten buysa (en kuşkusuz olduğu değil size 4 soruların sona erdi gerçek soru metninde, söylediklerini hiçbiri Bir kaynak bulabileceği bir yerde soran edildi), sonra Cevap çok basit:

Bir tane yok.

C ++ programcılarının çoğunluğunun standart kütüphane yapılarının yükü, önbellek performansı ( zaten derleyiciye oldukça bağımlı) veya bu tür şeyler hakkında çok fazla şeyleri olması gerekmez . Bahsetmemek gerekirse, genellikle standart kütüphane uygulamanızı seçemezsiniz; derleyicinizle birlikte gelenleri kullanırsınız. Bu yüzden hoş olmayan şeyler yapsa bile, alternatif seçenekleri sınırlıdır.

Elbette bu tür şeyleri önemseyen programcılar var. Ama hepsi uzun zaman önce standart kütüphaneyi kullanarak yemin ettiler.

Yani umursamayan bir grup programcı var. Ve kullanıp kullanmadıklarını önemseyen başka bir programcı grubu, ama kullanmadığı için umursamıyorlar. Kimse umursamadığı için, bu tür şeyler hakkında gerçek bir bilgi yoktur. Burada ve orada resmi olmayan bilgi yamaları vardır (Effective C ++, std :: string uygulamaları ve aralarındaki büyük farklılıklar hakkında bir bölüme sahiptir), ancak kapsamlı bir şey değildir. Ve kesinlikle hiçbir şey güncel kalmadı.


Spekülatif cevap. Muhtemelen doğru için +1, kanıtlamanın hiçbir yolu olmadan -1.
yavaşladı

Geçmişte çok iyi ve ayrıntılı karşılaştırmalar gördüm, ama hepsi modası geçmiş. Hareketin başlamasından önceki her şey, günümüzde hemen hemen önemsizdir.
Peter
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.