Std :: vector düz dizilerden çok daha yavaş mı?


212

Hep std::vectorbir dizi olarak uygulanan genel bilgelik olduğunu düşündüm , falan falan filan. Bugün aşağı inip test ettim ve öyle görünmüyor:

İşte bazı test sonuçları:

UseArray completed in 2.619 seconds
UseVector completed in 9.284 seconds
UseVectorPushBack completed in 14.669 seconds
The whole thing completed in 26.591 seconds

Bu 3-4 kat daha yavaş! Gerçekten haklı değil " vectorbirkaç nanosec için daha yavaş olabilir" yorum.

Ve kullandığım kod:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector()
{
    TestTimer t("UseVector");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPushBack");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels;
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        free(pixels);
    }
}

int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

Yanlış mı yapıyorum? Yoksa bu performans efsanesini mi yaktım?

Visual Studio 2005'te Release modunu kullanıyorum .


Olarak Visual C ++ , #define _SECURE_SCL 0azaltan UseVectoryarı yarıya (4 saniye için aşağı getirerek). Bu gerçekten çok büyük, IMO.


23
Hata ayıklama modundayken vektörün bazı sürümleri, dizinin sonu ve bunun gibi şeylerin ötesine erişip erişmediğinizi kontrol etmek için ek talimatlar ekler. Gerçek zamanlamaları elde etmek için sürüm modunda oluşturmalı ve optimizasyonları açmalısınız.
Martin York

40
İnternet üzerinden duyduğunuz iddialara inanmak yerine ölçmeniz iyi bir şey.
P

51
Vektör olan bir dizi olarak uygulanan. Bu "geleneksel bilgelik" değil, gerçek bu. Bunun vectorgenel amaçlı yeniden boyutlandırılabilir bir dizi olduğunu keşfettiniz . Tebrikler. Tüm genel amaçlı araçlarda olduğu gibi, optimalin altında olduğu özel durumlar bulmak mümkündür. Geleneksel bilgelik etmektir yüzden mi başlatmak bir ile vectorve gerekirse alternatifler düşünün.
Dennis Zickefoose

37
lol, "kirli bulaşıkları bir lavaboya atmak" ve "kirli bulaşıkları bir lavaboya atmak ve kırılmadı mı kontrol etmek" hız farkı nedir?
Imre L

9
VC2010'da en azından büyük fark, malloc () öğesinin resize () öğesinden daha hızlı olmasıdır. Bellek ayırmayı zamanlamadan kaldırın, _ITERATOR_DEBUG_LEVEL == 0 ile derleyin ve sonuçlar aynı.
Andreas Magnusson

Yanıtlar:


260

Aşağıdakileri kullanarak:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseArray 2.196 saniyede
tamamlandı
UseVector 4.412 saniyede
tamamlandı UseVectorPushBack 8.017 saniyede tamamlandı Her şey 14.626 saniyede tamamlandı

Yani dizi vektörden iki kat daha hızlıdır.

Ancak koda daha ayrıntılı baktıktan sonra bu beklenir; vektörde iki kez ve dizide sadece bir kez koşarken. Not: resize()vektörü yaptığınızda, sadece belleği ayırmakla kalmaz, aynı zamanda vektörde de çalışır ve her üyede yapıcıyı çağırırsınız.

Kod, vektörün her bir nesneyi yalnızca bir kez başlatması için hafifçe yeniden düzenlenmesi:

 std::vector<Pixel>  pixels(dimensions * dimensions, Pixel(255,0,0));

Şimdi aynı zamanlamayı tekrar yapıyoruz:

g ++ -O3 Time.cpp -I <MyBoost>
./a.out
UseVector 2.216 saniyede tamamlandı

Vektör şimdi diziden biraz daha kötü performans gösteriyor. IMO bu fark önemsizdir ve testle ilişkili olmayan birçok şeyden kaynaklanabilir.

Ben de UseArrray()ne yapıcı / yıkıcı çağrılmaz olarak bu yöntemde Pixel nesnesini doğru bir şekilde başlatma / yok olmadığını (bu basit sınıf için bir sorun olmayabilir ama biraz daha karmaşık bir şey (işaretçiler veya üyelerle) işaretçilerle) sorunlara neden olur.


48
@ kizzx2: Bunun reserve()yerine kullanmanız gerekir resize(). Bu nesneler için yer ayırır (yani vektörün kapasitesini değiştirir ) ancak nesneleri oluşturmaz (yani vektörün boyutu değişmeden kalır).
James McNellis

25
1 000 000 000 dizi erişimi yapıyorsunuz. Zaman farkı 0.333 saniyedir. Veya dizi erişimi başına 0.000000000333 farkı. Dizi erişimi başına 0,7 talimat boru hattı aşaması olan benimki gibi 2,33 GHz İşlemci varsayarsak. Yani vektör, erişim başına bir ek talimat kullanıyor gibi görünüyor.
Martin York

3
@James McNellis: Sadece yerini alamaz resize()ile reserve()bu kendi boyutuna ilişkin vektörün iç fikri ayarlanmaz nedeniyle teknik olarak "sonuna geçmiş yazma" olan kendi elemanlarına, yani sonradan yazma ile UB üretecek. Pratikte her STL uygulaması bu konuda "kendi başına davranacaktır" olsa da, vektörün boyutunu nasıl yeniden senkronize edersiniz? Vektörü doldurduktan resize() sonra aramayı denerseniz , büyük olasılıkla varsayılan olarak oluşturulmuş değerlerle tüm bu öğelerin üzerine yazılır!
j_random_hacker

8
@j_random_hacker: Tam olarak söylediğim bu değil mi? reserveBir vektörün kapasitesini değil, sadece kapasitesini değiştirdiğini çok net olarak düşündüm .
James McNellis

7
Tamam, şekil ver. Vektör yöntemlerinde çok fazla istisna ile ilgili rüşvet vardı. /EHscDerleme anahtarlarına eklemek bunu temizledi ve assign()aslında diziyi şimdi atıyor. Yaşasın.
Pavel Minaev

55

Harika bir soru. Buraya, vektör testlerini hızlandıracak basit bir düzeltme bulmayı bekledim. Beklediğim gibi bu işe yaramadı!

Optimizasyon yardımcı olur, ancak yeterli değildir. Optimizasyon açıkken hala UseArray ve UseVector arasında 2 kat performans farkı görüyorum. İlginçtir, UseVector optimizasyon olmadan UseVectorPushBack'ten önemli ölçüde yavaştı.

# g++ -Wall -Wextra -pedantic -o vector vector.cpp
# ./vector
UseArray completed in 20.68 seconds
UseVector completed in 120.509 seconds
UseVectorPushBack completed in 37.654 seconds
The whole thing completed in 178.845 seconds
# g++ -Wall -Wextra -pedantic -O3 -o vector vector.cpp
# ./vector
UseArray completed in 3.09 seconds
UseVector completed in 6.09 seconds
UseVectorPushBack completed in 9.847 seconds
The whole thing completed in 19.028 seconds

Fikir # 1 - Malloc yerine yeni [] kullanın

Nesnelerin inşa edilecek böylece UseArray malloc()için değiştirmeyi denedim new[]. Bireysel alan atamasından bir Pixel örneği atamaya geçiş. Oh, ve iç döngü değişkenini yeniden adlandırmak j.

void UseArray()
{
    TestTimer t("UseArray");

    for(int i = 0; i < 1000; ++i)
    {   
        int dimension = 999;

        // Same speed as malloc().
        Pixel * pixels = new Pixel[dimension * dimension];

        for(int j = 0 ; j < dimension * dimension; ++j)
            pixels[j] = Pixel(255, 0, 0);

        delete[] pixels;
    }
}

Şaşırtıcı bir şekilde (bana göre), bu değişikliklerin hiçbiri herhangi bir fark yaratmadı. new[]Varsayılan olarak tüm Pikselleri oluşturacak değişiklik bile değil . Gcc, kullanıldığında varsayılan oluşturucu çağrılarını optimize edebilir new[], ancak kullanamaz vector.

Fikir # 2 - Tekrarlanan operatör [] çağrılarını kaldırın

Ayrıca üçlü operator[]arama kurtulmak ve referans önbellek çalıştı pixels[j]. Bu aslında UseVector'u yavaşlattı! Hata.

for(int j = 0; j < dimension * dimension; ++j)
{
    // Slower than accessing pixels[j] three times.
    Pixel &pixel = pixels[j];
    pixel.r = 255;
    pixel.g = 0;
    pixel.b = 0;
}

# ./vector 
UseArray completed in 3.226 seconds
UseVector completed in 7.54 seconds
UseVectorPushBack completed in 9.859 seconds
The whole thing completed in 20.626 seconds

Fikir # 3 - Yapıcıları kaldır

Yapıcıları tamamen kaldırmaya ne dersiniz? Daha sonra belki gcc, vektörler oluşturulduğunda tüm nesnelerin yapımını optimize edebilir. Piksel'i şu şekilde değiştirirsek ne olur:

struct Pixel
{
    unsigned char r, g, b;
};

Sonuç: yaklaşık% 10 daha hızlı. Hala bir diziden daha yavaş. Hm.

# ./vector 
UseArray completed in 3.239 seconds
UseVector completed in 5.567 seconds

Fikir # 4 - Döngü dizini yerine yineleyici kullanın

vector<Pixel>::iteratorBir döngü dizini yerine bir kullanmaya ne dersiniz ?

for (std::vector<Pixel>::iterator j = pixels.begin(); j != pixels.end(); ++j)
{
    j->r = 255;
    j->g = 0;
    j->b = 0;
}

Sonuç:

# ./vector 
UseArray completed in 3.264 seconds
UseVector completed in 5.443 seconds

Hayır, farklı değil. En azından yavaş değil. Bunun Pixel&referans kullandığım # 2'ye benzer bir performansa sahip olacağını düşündüm .

Sonuç

Bazı akıllı çerezler, vektör döngüsünü dizi kadar hızlı hale getirmeyi anlasa bile, bu, varsayılan davranışından iyi konuşmaz std::vector. Derleyicinin tüm C ++ ness'i optimize edecek ve STL kaplarını ham diziler kadar hızlı hale getirecek kadar akıllı olması için çok fazla.

Sonuç olarak, derleyici, kullanıldığında varsayılan olmayan yapıcı çağrılarını optimize edemez std::vector. Düz kullanırsanız, new[]onları gayet iyi optimize eder. Ama ile değil std::vector. Buradaki mantranın karşısında uçan yapıcı çağrılarını ortadan kaldırmak için kodunuzu yeniden yazabilseniz bile: "Derleyici sizden daha akıllıdır. STL o kadar hızlıdır. Bu konuda endişelenmeyin."


2
Yine, kodu çalıştırdığınız için teşekkürler. Birisi popüler fikirlere meydan okumaya çalıştığında sebepsiz yere basmak bazen kolaydır.
kizzx2

3
"Derleyici tüm C ++ ness optimize etmek ve STL kapları ham diziler kadar hızlı yapmak için yeterince akıllı olması için." Güzel yorumlar. Bu "derleyici akıllı" sadece bir efsane - C ++ ayrıştırma son derece zor ve derleyici sadece bir makine olduğunu bir teori var.
kizzx2

3
Bilmiyorum. Tabii, dizi testini yavaşlatabildi , ancak vektörü hızlandırmadı . Yukarıda kurguları Pixel'den kaldırdığım ve basit bir yapı haline getirdiğim yerde düzenledim ve hala yavaştı. Bu gibi basit türleri kullanan herkes için kötü bir haber vector<int>.
John Kugelman

2
Keşke cevabınızı gerçekten iki kez değerlendirebilseydim. Düşünemediğim (gerçekten hiçbiri işe yaramadı) denemek için akıllı fikirler!
kizzx2

9
Sadece (delicesine karmaşık, evet) C ++ ayrıştırma karmaşıklığının optimizasyon kalitesi ile ilgisi olmadığını not etmek istedim . İkincisi genellikle ayrıştırma sonucunun çok sayıda defa çok daha düşük seviyeli temsile dönüştürüldüğü aşamalarda gerçekleşir.
Pavel Minaev

44

Bu eski ama popüler bir soru.

Bu noktada, birçok programcı C ++ 11'de çalışacaktır. Ve C ++ 11'de OP'nin kodu yazıldığı gibi UseArrayveya için eşit derecede hızlı çalışır UseVector.

UseVector completed in 3.74482 seconds
UseArray completed in 3.70414 seconds

Temel sorun, yapınız Pixelbaşlatılmamışken, std::vector<T>::resize( size_t, T const&=T() )varsayılan olarak oluşturulmuş Pixelve kopyalanmış olmasıdır . Derleyici, başlatılmamış verileri kopyalamanın istendiğini fark etmedi, bu yüzden aslında kopyayı gerçekleştirdi.

C ++ 11'de std::vector<T>::resizeiki aşırı yükleme vardır. Birincisi std::vector<T>::resize(size_t), diğeri std::vector<T>::resize(size_t, T const&). Bu, resizeikinci bir argüman olmadan çağırdığınızda , yalnızca varsayılan yapıları oluşturur ve derleyici, varsayılan yapının hiçbir şey yapmadığını fark edecek kadar akıllıdır, bu nedenle arabellek geçişini atlar.

(Taşınabilir, yapılandırılabilir ve kopyalanamayan türleri işlemek için eklenen iki aşırı yükleme - başlatılmamış veriler üzerinde çalışırken performans artışı bir avantajdır).

push_backÇözelti ayrıca daha yavaş kalabilmeleri için onu yavaşlatır fencepost denetimi yapar mallocsürümü.

canlı örnek (zamanlayıcıyı da değiştirdim chrono::high_resolution_clock).

Genellikle başlatma gerektiren bir yapınız varsa, ancak arabelleğinizi büyüttükten sonra bunu yapmak istiyorsanız, bunu özel bir std::vectorayırıcıyla yapabileceğinizi unutmayın . Daha normal bir hale getirmek istiyorsanız std::vector, dikkatli bir şekilde kullanmanın allocator_traitsve geçersiz kılmanın bunu başarabileceğine inanıyorum ==, ancak emin değilim.


Ayrıca emplace_backvs nasıl push_backburada görmek ilginç olurdu .
Daniel

1
Sonuçlarınızı tekrarlayamıyorum. Kodunuzu Derleme clang++ -std=c++11 -O3sahiptir UseArray completed in 2.02e-07 secondsve UseVector completed in 1.3026 seconds. Ayrıca UseVectorEmplaceBackyaklaşık bir sürüm ekledim . 2,5 kat daha hızlı UseVectorPushBack.
Daniel

1
@daniel olasılıkları optimize edici dizi sürümü her şeyi kaldırılır. Mikro ölçütlerle her zaman bir risk.
Yakk - Adam Nevraumont

4
evet haklısın, sadece meclise (ya da eksikliğine) baktım .. ~ 6448514x farkı göz önüne alındığında muhtemelen bunu düşünmeliydin! Vektör sürümünün neden aynı optimizasyonu yapamayacağını merak ediyorum .. Yeniden boyutlandırılmak yerine boyutlarla inşa edilmişse bunu yapar.
Daniel

34

Adil olmak gerekirse, malloc sürümünüzü çağırdığım için bir C ++ uygulamasını bir C uygulamasıyla karşılaştıramazsınız. malloc nesne oluşturmaz - yalnızca ham bellek ayırır. Daha sonra bu belleği yapıcıyı çağırmadan nesneler olarak ele almanız kötü C ++ (muhtemelen geçersiz - bunu dil avukatlarına bırakacağım).

Bununla birlikte, malloc'u basitçe new Pixel[dimensions*dimensions]ve ücretsiz olarak değiştirmek, delete [] pixelssahip olduğunuz Pixel'in basit uygulamasıyla fazla bir fark yaratmaz. Kutumdaki sonuçlar (E6600, 64 bit):

UseArray completed in 0.269 seconds
UseVector completed in 1.665 seconds
UseVectorPushBack completed in 7.309 seconds
The whole thing completed in 9.244 seconds

Ancak küçük bir değişiklikle tablolar dönüyor:

Pixel.h

struct Pixel
{
    Pixel();
    Pixel(unsigned char r, unsigned char g, unsigned char b);

    unsigned char r, g, b;
};

Pixel.cc

#include "Pixel.h"

Pixel::Pixel() {}
Pixel::Pixel(unsigned char r, unsigned char g, unsigned char b) 
  : r(r), g(g), b(b) {}

main.cc

#include "Pixel.h"
[rest of test harness without class Pixel]
[UseArray now uses new/delete not malloc/free]

Bu şekilde derlendi:

$ g++ -O3 -c -o Pixel.o Pixel.cc
$ g++ -O3 -c -o main.o main.cc
$ g++ -o main main.o Pixel.o

çok farklı sonuçlar alıyoruz:

UseArray completed in 2.78 seconds
UseVector completed in 1.651 seconds
UseVectorPushBack completed in 7.826 seconds
The whole thing completed in 12.258 seconds

Pixel için satır içi olmayan bir kurucu ile, std :: vector artık ham bir diziyi atıyor.

Görünüşe göre std :: vector ve std: allocator ile tahsis karmaşıklığı, basit kadar etkili bir şekilde optimize edilemeyecek kadar fazla new Pixel[n]. Bununla birlikte, vektörü / diziyi bir kez döngü dışına taşıyarak oluşturmak için birkaç test fonksiyonunu değiştirerek sorunun sadece vektör erişimi değil tahsis ile ilgili olduğunu görebiliriz:

void UseVector()
{
    TestTimer t("UseVector");

    int dimension = 999;
    std::vector<Pixel> pixels;
    pixels.resize(dimension * dimension);

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

ve

void UseArray()
{
    TestTimer t("UseArray");

    int dimension = 999;
    Pixel * pixels = new Pixel[dimension * dimension];

    for(int i = 0; i < 1000; ++i)
    {
        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
    delete [] pixels;
}

Bu sonuçları şimdi alıyoruz:

UseArray completed in 0.254 seconds
UseVector completed in 0.249 seconds
UseVectorPushBack completed in 7.298 seconds
The whole thing completed in 7.802 seconds

Bundan öğrenebileceğimiz, std :: vector'un erişim için ham bir diziyle karşılaştırılabilir olmasıdır, ancak vektör / diziyi birçok kez oluşturmanız ve silmeniz gerekiyorsa, karmaşık bir nesne oluşturmak basit bir dizi oluşturmaktan daha fazla zaman alır öğenin yapıcısı satır içine alınmadığında. Bunun çok şaşırtıcı olduğunu düşünmüyorum.


3
Hâlâ eğik bir kurucunuz var - kopya kurucusu.
Ben Voigt

26

Şunu deneyin:

void UseVectorCtor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));
    }
}

Dizi ile neredeyse aynı performansı elde ediyorum.

Mesele vectorşu ki, bir diziden çok daha genel bir araç. Ve bu, onu nasıl kullandığınızı düşünmeniz gerektiği anlamına gelir . Bir dizinin sahip olmadığı işlevsellik sağlayarak birçok farklı şekilde kullanılabilir. Ve eğer amacınız için "yanlış" kullanırsanız, çok fazla ek yüke maruz kalırsınız, ancak doğru kullanırsanız, temelde sıfır ek yük veri yapısıdır. Bu durumda, sorun vektörü ayrı olarak başlatmanız (tüm öğelerin varsayılan ctor'larının çağrılmasına neden olur) ve ardından her öğenin ayrı ayrı doğru değerle üzerine yazmanızdır. Bu derleyici için bir dizi ile aynı şeyi yaptığınızdan daha iyi optimize etmek çok daha zordur. Bu yüzden vektör, tam olarak bunu yapmanıza izin veren bir oluşturucu sağlar:NX.

Ve bunu kullandığınızda, vektör bir dizi kadar hızlıdır.

Yani hayır, performans efsanesini tutmadın. Ancak bunun sadece vektörü en iyi şekilde kullandığınızda doğru olduğunu gösterdiniz, bu da oldukça iyi bir nokta. :)

Parlak tarafta, gerçekten en hızlı olduğu ortaya çıkan en basit kullanım. Kod snippet'imi (tek bir satır), performans farkını hala tamamen ortadan kaldırmayan yığınlar ve optimizasyonlar içeren John Kugelman'ın cevabı ile karşılaştırırsanız, vectorher şeyden önce oldukça akıllıca tasarlanmış olduğu açıktır . Bir diziye eşit hız elde etmek için çemberlerden atlamak zorunda değilsiniz. Aksine, mümkün olan en basit çözümü kullanmanız gerekir.


1
Hala bunun adil bir karşılaştırma olup olmadığını sorguluyorum. İç döngüden kurtuluyorsanız, dizi eşdeğeri tek bir Pixel nesnesi oluşturmak ve bunu tüm dizi boyunca karıştırmak olacaktır.
John Kugelman

1
Kullanmak new[], aynı varsayılan yapıları gerçekleştirir vector.resize(), ancak çok daha hızlıdır. new[]+ iç döngü + iç döngü ile aynı hızda olmalıdır vector.resize(), ancak değil, neredeyse iki kat daha hızlıdır.
John Kugelman

@John: Bu adil bir karşılaştırma. Orijinal kodunda, dizi ile tahsis edilir malloco kadar sıfırlanması veya yapı şey olmadığı ise etkili bir sadece benim gibi tek geçişli algoritması vectornumunesi. Ve new[]cevaba gelince, her ikisinin de iki geçiş gerektirdiği açıktır, ancak new[]durumda, derleyici, ek yükü optimize edebilir ve bu vectordurumda yapmaz . Ancak, yetersiz vakalarda olanların neden ilginç olduğunu anlamıyorum. Performansı önemsiyorsanız, böyle bir kod yazmazsınız.
jalf

@John: İlginç bir yorum. Tüm dizi boyunca blit yapmak istedim, sanırım dizi yine en uygun çözümdür - çünkü vector::resize()işe yaramaz kurucular çağırmak için zaman kaybetmeden bana bir durumsal bellek yığını vermek söyleyemem .
kizzx2

@ kizzx2: evet ve hayır. C ++ 'ta da bir dizi normal olarak başlatılır. C'de, mallocbaşlatma işlemini gerçekleştirmeyen, ancak P ++ olmayan türlerle C ++ 'da çalışmaz. Genel durumda, bir C ++ dizisi de o kadar kötü olurdu. Belki de soru şu, eğer bu blitting'i sık sık yapacaksanız, aynı diziyi / vektörü tekrar kullanmazsınız? Ve bunu yaparsanız, o zaman "işe yaramaz kurucuların" maliyetini en başta bir kez ödersiniz. Sonuçta gerçek blitting hızlıdır.
jalf

22

Kodunuza ilk baktığımda pek adil bir karşılaştırma değildi; Kesinlikle elmaları elma ile karşılaştırmayacağınızı sanıyordum. Bu yüzden düşündüm, yapıcıların ve yıkıcıların tüm testlerde çağrılmasını sağlayalım; ve sonra karşılaştırın.

const size_t dimension = 1000;

void UseArray() {
    TestTimer t("UseArray");
    for(size_t j = 0; j < dimension; ++j) {
        Pixel* pixels = new Pixel[dimension * dimension];
        for(size_t i = 0 ; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
        delete[] pixels;
    }
}

void UseVector() {
    TestTimer t("UseVector");
    for(size_t j = 0; j < dimension; ++j) {
        std::vector<Pixel> pixels(dimension * dimension);
        for(size_t i = 0; i < dimension * dimension; ++i) {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = (unsigned char) (i % 255);
        }
    }
}

int main() {
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();

    return 0;
}

Düşüncelerim, bu kurulumla tamamen aynı olmaları gerektiğiydi . Anlaşıldı, yanılmışım.

UseArray completed in 3.06 seconds
UseVector completed in 4.087 seconds
The whole thing completed in 10.14 seconds

Peki bu% 30'luk performans kaybı neden meydana geldi? STL'nin başlıklarda her şeyi vardır, bu nedenle derleyicinin gerekli olan her şeyi anlaması mümkün olmalıydı.

Düşüncelerim, döngünün tüm değerleri varsayılan kurucuya nasıl başlattığıydı. Bu yüzden bir test yaptım:

class Tester {
public:
    static int count;
    static int count2;
    Tester() { count++; }
    Tester(const Tester&) { count2++; }
};
int Tester::count = 0;
int Tester::count2 = 0;

int main() {
    std::vector<Tester> myvec(300);
    printf("Default Constructed: %i\nCopy Constructed: %i\n", Tester::count, Tester::count2);

    return 0;
}

Sonuçlar şüphelendiğim gibiydi:

Default Constructed: 1
Copy Constructed: 300

Bu, yavaşlamanın kaynağıdır, vektörün varsayılan yapılandırılmış bir nesneden öğeleri başlatmak için kopyalama yapıcısını kullanmasıdır.

Bu, vektörün yapımı sırasında aşağıdaki sözde işlem sırasının gerçekleştiği anlamına gelir:

Pixel pixel;
for (auto i = 0; i < N; ++i) vector[i] = pixel;

Hangi, derleyici tarafından yapılan örtük kopya yapıcı nedeniyle, aşağıdakilere genişletilir:

Pixel pixel;
for (auto i = 0; i < N; ++i) {
    vector[i].r = pixel.r;
    vector[i].g = pixel.g;
    vector[i].b = pixel.b;
}

Varsayılan Yani Pixelkalanı ise un-başlatıldı kalır ilklendirilmiş varsayılan Pixel'ın un-başlatıldı değerler.

Alternatif duruma kıyasla New[]/ ' Delete[]:

int main() {
    Tester* myvec = new Tester[300];

    printf("Default Constructed: %i\nCopy Constructed:%i\n", Tester::count, Tester::count2);

    delete[] myvec;

    return 0;
}

Default Constructed: 300
Copy Constructed: 0

Hepsi başlatılmamış değerlerine bırakılır ve sekans üzerinde çift tekrarlama yapılmaz.

Bu bilgilerle donanmış olarak, bunu nasıl test edebiliriz? Örtük kopya yapıcısının üzerine yazmayı deneyelim.

Pixel(const Pixel&) {}

Peki sonuçlar?

UseArray completed in 2.617 seconds
UseVector completed in 2.682 seconds
The whole thing completed in 5.301 seconds

Özetle, çok sık yüzlerce vektör yapıyorsanız: algoritmanızı yeniden düşünün .

Her durumda, STL uygulaması bilinmeyen bir nedenden dolayı daha yavaş değildir, sadece tam olarak ne istersen yapar; daha iyi bilmeni umuyorum.


3
Biz abartmayı ve okuduğum umduğunu sonucuna varabiliriz P Temelde ve: Biz (sen ve ben ve burada diğer akıllı kişi) oldu eğlenceli bakılırsa, STL implemenation en "umut" oldukça zorlu bir gerçekten de analiz edilen tüm kaynağını kodu. Her neyse: P
kizzx2

1
Awsome! VS 2013'te bu vektörü dizilerden daha hızlı yaptı. Her ne kadar performans açısından kritik sistemler için STL'yi etkili bir şekilde kullanabilmek için çok fazla test etmeniz gerekiyor.
rozina

7

İşaretli yineleyicileri devre dışı bırakmayı ve serbest bırakma modunda oluşturmayı deneyin . Performans farkının çoğunu görmemelisiniz.


1
Denedim #define _SECURE_SCL 0. Bu UseVectoryaklaşık 4 saniye ( gccaşağı benzer ) bir yerde yaptı ama yine de iki kat daha yavaş.
kizzx2

Neredeyse kesin olan bu. Microsoft nezaketle hem hata ayıklama hem de sürüm için varsayılan olarak yineleyici hata ayıklama var. Bunu 2003'ten 2008'e yükselttikten sonra büyük bir yavaşlamanın temel nedeni olarak gördük. Kesinlikle görsel stüdyonun en zararlı gotchas biri.
Doug T.

2
@ kizzx2 devre dışı bırakılacak başka bir makro var: HAS_ITERATOR_DEBUGGING veya benzeri.
Doug T.

@Martin ve cevaplarımın gösterdiği gibi, gcc, optimizasyonda bile aynı modeli gösterir -O3.
John Kugelman

1
@Doug: Dokümana baktığımda, _HAS_ITERATOR_DEBUGGINGsürüm derlemesinde devre dışı olduğunu düşünüyorum : msdn.microsoft.com/en-us/library/aa985939(VS.80).aspx
kizzx2 8:10

4

Verilen GNU STL (ve diğerleri), vector<T>(n)varsayılan bir prototypal nesne oluşturur T()derleyici boş bir yapıcı uzağa optimize eder - - ama sonra ne olursa olsun çöp bir kopyası STL en tarafından alınır şimdi nesne için ayrılan bellek adresleri olması oldu __uninitialized_fill_n_aux, hangi o nesnenin kopyalarını vektördeki varsayılan değerler olarak doldurarak döngüler. Yani, "my" STL döngü oluşturmayı değil, sonra döngü / kopyalamayı oluşturuyor. Bu sezgisel karşıtı, ama bu nokta hakkında son bir stackoverflow sorusuna yorum yaparken hatırladım gerekirdi: yapı / kopya sayılan nesneler vb için daha verimli olabilir.

Yani:

vector<T> x(n);

veya

vector<T> x;
x.resize(n);

- birçok STL uygulamasında - şuna benzer:

T temp;
for (int i = 0; i < n; ++i)
    x[i] = temp;

Sorun şu ki, derleyici iyileştiricilerinin mevcut nesli, temp'in başlatılmamış çöp olduğu ve döngü ve varsayılan kopya oluşturucu çağrılarını optimize edemediği anlayışından çalışmıyor gibi görünüyor. Derleyicilerin bunu kesinlikle optimize etmemesi gerektiğini güvenilir bir şekilde söyleyebilirsiniz, çünkü yukarıda yazan bir programcı, çöp olsa bile, döngüden sonra tüm nesnelerin özdeş olacağı konusunda makul bir beklentiye sahiptir ('özdeş' / operator == vs memcmp / operator = etc geçerlidir). Derleyicinin daha geniş std :: vector <> bağlamı veya bu optimizasyonun güvenli olmasını önerecek verilerin daha sonra kullanımı hakkında daha fazla bilgi sahibi olması beklenemez.

Bu daha açık, doğrudan uygulama ile karşılaştırılabilir:

for (int i = 0; i < n; ++i)
    x[i] = T();

Hangi bir derleyici optimize etmek bekleyebilirsiniz.

Vektör davranışının bu yönünün gerekçesi hakkında biraz daha açık olmak için şunları göz önünde bulundurun:

std::vector<big_reference_counted_object> x(10000);

Açıkçası 10000 aynı nesneye karşılık 10000 bağımsız nesne yaparsak, bu büyük bir farktır. Sıradan C ++ kullanıcılarını yanlışlıkla bu kadar pahalı bir şey yapmaktan korumanın avantajının, optimize edilmesi zor kopya yapısının çok küçük gerçek dünya maliyetinden daha ağır bastığının makul bir argümanı var.

ORİJİNAL CEVAP (yorum yapmak / yorum yapmak için): Şans yok. vektör, bir dizi kadar hızlıdır, en azından yer anlamlı olarak ayırırsanız. ...


6
Bu cevabı kimsenin biraz faydalı bulduğu bir yerde haklı çıkaramam. Umarım iki kez inebilirim.
kizzx2

-1, kizzx2'ye desteğim var. Vektör, sağladığı ek özellik, evrenin kuralı, her şeyin bir bedeli olduğundan dizi kadar hızlı olmayacak!
YeenFei

Kaçırıyorsunuz Tony ... bu yapay bir ölçüt örneğidir, ancak neyi iddia ettiğini kanıtlıyor.
Potatoswatter

Güller yeşil, menekşeler turuncu, aşağı oylar acı, ama cevap onlara yalvarıyor.
Pavel Minaev

3

Martin York'un yanıtı beni rahatsız ediyor çünkü halının altındaki başlatma problemini fırçalama girişimi gibi görünüyor. Ancak yedek varsayılan yapıyı performans sorunlarının kaynağı olarak tanımlamakta haklı.

[EDIT: Martin'in cevabı artık varsayılan kurucuyu değiştirmeyi önermiyor.]

Eldeki acil sorun için, vector<Pixel>bunun yerine ctor'un 2 parametreli sürümünü kesinlikle çağırabilirsiniz :

std::vector<Pixel> pixels(dimension * dimension, Pixel(255, 0, 0));

Bu, ortak bir durum olan sabit bir değerle başlatmak istiyorsanız işe yarar. Ancak daha genel olan sorun şudur: Sabit bir değerden daha karmaşık bir şeyle nasıl verimli bir şekilde başlangıç ​​yapabilirsiniz?

Bunun için back_insert_iteratorbir yineleyici adaptörü olan a'yı kullanabilirsiniz . intHer ne kadar genel fikir Pixels için de işe yarıyor olsa da , s vektörü içeren bir örnek :

#include <iterator>
// Simple functor return a list of squares: 1, 4, 9, 16...
struct squares {
    squares() { i = 0; }
    int operator()() const { ++i; return i * i; }

private:
    int i;
};

...

std::vector<int> v;
v.reserve(someSize);     // To make insertions efficient
std::generate_n(std::back_inserter(v), someSize, squares());

Alternatif olarak copy()veya transform()yerinegenerate_n() .

Dezavantajı, başlangıç ​​değerlerini oluşturmak için mantığın, (C ++ 1x'deki lambdalar bu kadar güzel olmasına rağmen) yerinde olmaktan daha az uygun olan ayrı bir sınıfa taşınması gerektiğidir. Ayrıca, bunun hala malloc()STL tabanlı olmayan bir sürüm kadar hızlı olmayacağını düşünüyorum , ancak her öğe için sadece bir yapı yaptığı için yakın olacağını umuyorum.


2

Vektörler ayrıca Pixel yapıcıları olarak adlandırılıyor.

Her biri, zamanladığınız neredeyse bir milyon ctor koşusuna neden oluyor.

edit: o zaman dış 1 ... 1000 döngü, yani bir milyar ctor çağırır olun!

edit 2: UseArray kasasının sökülmesini görmek ilginç olurdu. Bir optimize edici her şeyi optimize edebilir, çünkü CPU yakmaktan başka bir etkisi yoktur.


Haklısın, ama soru şu: bu anlamsız ctor çağrıları nasıl kapatılabilir? STL olmayan yaklaşım için kolaydır, ancak STL yolu için zor / çirkin.
j_random_hacker

1

Vektördeki push_backyöntem şu şekilde çalışır:

  1. Vektör, başlatıldığında X miktarında alan ayırır.
  2. Aşağıda belirtildiği gibi, öğenin altında yatan geçerli dizide yer olup olmadığını kontrol eder.
  3. Push_back çağrısındaki öğenin bir kopyasını oluşturur.

push_backX öğeyi çağırdıktan sonra :

  1. Vektör, kX alan miktarını 2. bir diziye yeniden tahsis eder.
  2. İlk dizinin girişlerini ikinciye kopyalar.
  3. İlk diziyi atar.
  4. Şimdi ikinci diziyi kX girişlerine ulaşıncaya kadar depolama olarak kullanır.

Tekrar et. Eğer değilsenreserving , kesinlikle daha yavaş olacaktır. Dahası, öğeyi kopyalamak pahalıysa, o zaman 'push_back' sizi canlı canlı yiyecektir.

Gelince vectorkarşı dizisi şey, ben diğer insanlarla anlaşmak zorunda kalacağım. Sürümde çalıştırın, optimizasyonları açın ve birkaç bayrak daha ekleyin, böylece Microsoft'taki dost insanlar sizin için # @% $ ^ bunu yapmaz.

Bir şey daha, yeniden boyutlandırmanız gerekmiyorsa Boost.Array kullanın.


İnsanların kelimesi kelimesine yayınlandığında bir grup kodu okumaktan hoşlanmadığını anlıyorum. Ama istediğim gibi kullandım reserve.
kizzx2

Üzgünüm özledim. Oraya koyduğum başka bir şey hiç yardımcı olmadı mı?
wheaties

push_backamortisman süresi sabittir. Görünüşe göre bir O (N) sürecini tanımlıyorsunuz. (Adım 1 ve 3 tamamen yerinde görünmüyor.) push_backOP için yavaşlayan şey, yeniden tahsisin gerekip gerekmediğini görmek için aralık kontrolü, işaretçileri güncelleme, yerleşim içindeki NULL'a karşı kontrol newve normalde tarafından boğulan diğer küçük şeyler programın gerçek çalışması.
Potatoswatter

Her reserveseferinde (yeniden tahsis edilmesi gerekip gerekmediği) bu kontrolü yapmak zorunda olduğu için bile daha yavaş olacaktır push_back.
Pavel Minaev

Tüm iyi puan. Tarif ettiğim şey bir O (N) süreci gibi geliyor ama değil, pek değil. Tanıdığım çoğu insan bir vectoryeniden boyutlandırma işlevini nasıl gerçekleştirdiğini anlamıyor , sadece "sihir". Burada biraz daha açıklayayım.
wheaties

1

Bazı profil oluşturucu verileri (piksel 32 bitle hizalanır):

g++ -msse3 -O3 -ftree-vectorize -g test.cpp -DNDEBUG && ./a.out
UseVector completed in 3.123 seconds
UseArray completed in 1.847 seconds
UseVectorPushBack completed in 9.186 seconds
The whole thing completed in 14.159 seconds

falan

andrey@nv:~$ opannotate --source libcchem/src/a.out  | grep "Total samples for file" -A3
Overflow stats not available
 * Total samples for file : "/usr/include/c++/4.4/ext/new_allocator.h"
 *
 * 141008 52.5367
 */
--
 * Total samples for file : "/home/andrey/libcchem/src/test.cpp"
 *
 *  61556 22.9345
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_vector.h"
 *
 *  41956 15.6320
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_uninitialized.h"
 *
 *  20956  7.8078
 */
--
 * Total samples for file : "/usr/include/c++/4.4/bits/stl_construct.h"
 *
 *   2923  1.0891
 */

İçinde allocator:

               :      // _GLIBCXX_RESOLVE_LIB_DEFECTS
               :      // 402. wrong new expression in [some_] allocator::construct
               :      void
               :      construct(pointer __p, const _Tp& __val)
141008 52.5367 :      { ::new((void *)__p) _Tp(__val); }

vector:

               :void UseVector()
               :{ /* UseVector() total:  60121 22.3999 */
...
               :
               :
 10790  4.0201 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
   495  0.1844 :            pixels[i].r = 255;
               :
 12618  4.7012 :            pixels[i].g = 0;
               :
  2253  0.8394 :            pixels[i].b = 0;
               :
               :        }

dizi

               :void UseArray()
               :{ /* UseArray() total:  35191 13.1114 */
               :
...
               :
   136  0.0507 :        for (int i = 0; i < dimension * dimension; ++i) {
               :
  9897  3.6874 :            pixels[i].r = 255;
               :
  3511  1.3081 :            pixels[i].g = 0;
               :
 21647  8.0652 :            pixels[i].b = 0;

Genel giderlerin çoğu kopya oluşturucudadır. Örneğin,

    std::vector < Pixel > pixels;//(dimension * dimension, Pixel());

    pixels.reserve(dimension * dimension);

    for (int i = 0; i < dimension * dimension; ++i) {

        pixels[i].r = 255;

        pixels[i].g = 0;

        pixels[i].b = 0;
    }

Bir diziyle aynı performansa sahiptir.


2
Ne yazık ki, verdiğiniz "çözüm" sonra, pixels.size()kırık olacak.
kizzx2

1
bu yanlış, yedek arayamaz ve öğeleri kullanamazsınız, yine de öğeler eklemek için push_back'i kullanmalısınız
paulm

1

Dizüstü bilgisayarım Lenova G770 (4 GB RAM).

İşletim sistemi Windows 7 64 bit (dizüstü bilgisayar ile)

Derleyici MinGW 4.6.1'dir.

IDE Code :: Blocks'tur .

İlk yazının kaynak kodlarını test ediyorum.

Sonuçlar

O2 optimizasyonu

Dizi 2.841 saniyede tamamlandı

Kullanım 2.548 saniyede tamamlandı

UseVectorPushBack 11,95 saniyede tamamlandı

Her şey 17.342 saniyede tamamlandı

sistem duraklaması

O3 optimizasyonu

Dizi 1.452 saniyede tamamlandı

Kullanım 2.514 saniyede tamamlandı

UseVectorPushBack 12.967 saniyede tamamlandı

Her şey 16.937 saniyede tamamlandı

O3 optimizasyonu altında vektörün performansı daha kötü görünüyor.

Döngüyü şu şekilde değiştirirseniz:

    pixels[i].r = i;
    pixels[i].g = i;
    pixels[i].b = i;

O2 ve O3 altındaki dizi ve vektörün hızı neredeyse aynıdır.


Hatta malloc'u yeniye değiştiriyorum, O3 altındaki ilk test durumunda, vektörün performansı hala diziden daha yavaş.Ama atama değerini (255, 0, 0) yerine (i, i, i), performansı vektör ve dizi O2 ve O3 altında neredeyse aynı, oldukça garip
StereoMatching 20:12

Üzgünüz, silmeyi serbest bırakmayı unutuyorum. Silmeyi ücretsiz değiştirdikten sonra, vektör ve dizinin O3 altındaki performansı şimdi aynı, allocator ana neden gibi görünüyor?
StereoMatching 20:12

1

Daha iyi bir karşılaştırma (sanırım ...), optimizasyon nedeniyle derleyici kodu değiştirebilir, çünkü tahsis edilen vektörlerin / dizilerin sonuçları hiçbir yerde kullanılmaz. Sonuçlar:

$ g++ test.cpp -o test -O3 -march=native
$ ./test 
UseArray inner completed in 0.652 seconds
UseArray completed in 0.773 seconds
UseVector inner completed in 0.638 seconds
UseVector completed in 0.757 seconds
UseVectorPushBack inner completed in 6.732 seconds
UseVectorPush completed in 6.856 seconds
The whole thing completed in 8.387 seconds

Derleyici:

gcc version 6.2.0 20161019 (Debian 6.2.0-9)

İŞLEMCİ:

model name  : Intel(R) Core(TM) i7-3630QM CPU @ 2.40GHz

Ve kod:

#include <cstdlib>
#include <vector>

#include <iostream>
#include <string>

#include <boost/date_time/posix_time/ptime.hpp>
#include <boost/date_time/microsec_time_clock.hpp>

class TestTimer
{
    public:
        TestTimer(const std::string & name) : name(name),
            start(boost::date_time::microsec_clock<boost::posix_time::ptime>::local_time())
        {
        }

        ~TestTimer()
        {
            using namespace std;
            using namespace boost;

            posix_time::ptime now(date_time::microsec_clock<posix_time::ptime>::local_time());
            posix_time::time_duration d = now - start;

            cout << name << " completed in " << d.total_milliseconds() / 1000.0 <<
                " seconds" << endl;
        }

    private:
        std::string name;
        boost::posix_time::ptime start;
};

struct Pixel
{
    Pixel()
    {
    }

    Pixel(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b)
    {
    }

    unsigned char r, g, b;
};

void UseVector(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVector inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
        pixels.resize(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}

void UseVectorPushBack(std::vector<std::vector<Pixel> >& results)
{
    TestTimer t("UseVectorPushBack inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel>& pixels = results.at(i);
            pixels.reserve(dimension * dimension);

        for(int i = 0; i < dimension * dimension; ++i)
            pixels.push_back(Pixel(255, 0, 0));
    }
}

void UseArray(Pixel** results)
{
    TestTimer t("UseArray inner");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        Pixel * pixels = (Pixel *)malloc(sizeof(Pixel) * dimension * dimension);

        results[i] = pixels;

        for(int i = 0 ; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }

        // free(pixels);
    }
}

void UseArray()
{
    TestTimer t("UseArray");
    Pixel** array = (Pixel**)malloc(sizeof(Pixel*)* 1000);
    UseArray(array);
    for(int i=0;i<1000;++i)
        free(array[i]);
    free(array);
}

void UseVector()
{
    TestTimer t("UseVector");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVector(vector);
    }
}

void UseVectorPushBack()
{
    TestTimer t("UseVectorPush");
    {
        std::vector<std::vector<Pixel> > vector(1000, std::vector<Pixel>());
        UseVectorPushBack(vector);
    }
}


int main()
{
    TestTimer t1("The whole thing");

    UseArray();
    UseVector();
    UseVectorPushBack();

    return 0;
}

1

Bir süredir yapmak istediğim bazı kapsamlı testler yaptım. Bunu da paylaşabilir.

Bu, Windows 8.1 ve Ubuntu 16.04'te çift önyükleme makinem i7-3770, 16GB Ram, x86_64. Daha fazla bilgi ve sonuç, aşağıdaki açıklamalar. Hem MSVS 2017 hem de g ++ 'ı test etti (hem Windows'ta hem de Linux'ta).

Test programı

#include <iostream>
#include <chrono>
//#include <algorithm>
#include <array>
#include <locale>
#include <vector>
#include <queue>
#include <deque>

// Note: total size of array must not exceed 0x7fffffff B = 2,147,483,647B
//  which means that largest int array size is 536,870,911
// Also image size cannot be larger than 80,000,000B
constexpr int long g_size = 100000;
int g_A[g_size];


int main()
{
    std::locale loc("");
    std::cout.imbue(loc);
    constexpr int long size = 100000;  // largest array stack size

    // stack allocated c array
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
    int A[size];
    for (int i = 0; i < size; i++)
        A[i] = i;

    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style stack array size=" << sizeof(A) << "B\n\n";

    // global stack c array
    start = std::chrono::steady_clock::now();
    for (int i = 0; i < g_size; i++)
        g_A[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "global c-style stack array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "global c-style stack array size=" << sizeof(g_A) << "B\n\n";

    // raw c array heap array
    start = std::chrono::steady_clock::now();
    int* AA = new int[size];    // bad_alloc() if it goes higher than 1,000,000,000
    for (int i = 0; i < size; i++)
        AA[i] = i;

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "c-style heap array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "c-style heap array size=" << sizeof(AA) << "B\n\n";
    delete[] AA;

    // std::array<>
    start = std::chrono::steady_clock::now();
    std::array<int, size> AAA;
    for (int i = 0; i < size; i++)
        AAA[i] = i;
    //std::sort(AAA.begin(), AAA.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::array duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::array size=" << sizeof(AAA) << "B\n\n";

    // std::vector<>
    start = std::chrono::steady_clock::now();
    std::vector<int> v;
    for (int i = 0; i < size; i++)
        v.push_back(i);
    //std::sort(v.begin(), v.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::vector duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::vector size=" << v.size() * sizeof(v.back()) << "B\n\n";

    // std::deque<>
    start = std::chrono::steady_clock::now();
    std::deque<int> dq;
    for (int i = 0; i < size; i++)
        dq.push_back(i);
    //std::sort(dq.begin(), dq.end());

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::deque duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::deque size=" << dq.size() * sizeof(dq.back()) << "B\n\n";

    // std::queue<>
    start = std::chrono::steady_clock::now();
    std::queue<int> q;
    for (int i = 0; i < size; i++)
        q.push(i);

    duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - start).count();
    std::cout << "std::queue duration=" << duration / 1000.0 << "ms\n";
    std::cout << "std::queue size=" << q.size() * sizeof(q.front()) << "B\n\n";
}

Sonuçlar

//////////////////////////////////////////////////////////////////////////////////////////
// with MSVS 2017:
// >> cl /std:c++14 /Wall -O2 array_bench.cpp
//
// c-style stack array duration=0.15ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.130ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.90ms
// c-style heap array size=4B
//
// std::array duration=0.20ms
// std::array size=400,000B
//
// std::vector duration=0.544ms
// std::vector size=400,000B
//
// std::deque duration=1.375ms
// std::deque size=400,000B
//
// std::queue duration=1.491ms
// std::queue size=400,000B
//
//////////////////////////////////////////////////////////////////////////////////////////
//
// with g++ version:
//      - (tdm64-1) 5.1.0 on Windows
//      - (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609 on Ubuntu 16.04
// >> g++ -std=c++14 -Wall -march=native -O2 array_bench.cpp -o array_bench
//
// c-style stack array duration=0ms
// c-style stack array size=400,000B
//
// global c-style stack array duration=0.124ms
// global c-style stack array size=400,000B
//
// c-style heap array duration=0.648ms
// c-style heap array size=8B
//
// std::array duration=1ms
// std::array size=400,000B
//
// std::vector duration=0.402ms
// std::vector size=400,000B
//
// std::deque duration=0.234ms
// std::deque size=400,000B
//
// std::queue duration=0.304ms
// std::queue size=400,000
//
//////////////////////////////////////////////////////////////////////////////////////////

notlar

  • Ortalama 10 çalışma ile monte edilir.
  • Başlangıçta std::sort()çok fazla test yaptım (yorum yaptığını görebilirsiniz) ancak daha sonra önemli bir göreceli farklılık olmadığı için bunları kaldırdım.

Sonuç ve Açıklamalarım

  • küresel c-tarzı dizinin yığın c-tarzı dizinin neredeyse ne kadar zaman aldığını fark edin
  • Tüm testler std::arrayarasında, ardışık çalışmalar arasındaki zaman değişimlerinde dikkate değer bir istikrar fark ederken, diğerleri özellikle std :: veri yapıları karşılaştırıldığında çok çılgınca değişiyordu.
  • O3 optimizasyonu dikkate değer bir zaman farkı göstermedi
  • Windows cl (-O2 yok) ve g ++ (Win / Linux no -O2, no -march = native) üzerinde optimizasyon kaldırıldığında ÖNEMLİ şekilde süreler artar. Özellikle std :: veri yapıları için. MSVS'de g ++ 'dan genel olarak daha yüksek süreler, ancakstd::array ve c tarzı optimizasyon olmadan Windows'ta daha hızlı diziler
  • g ++, Microsoft'un derleyicisinden daha hızlı kod üretir (görünüşe göre Windows'da bile daha hızlı çalışır).

Karar

Tabii ki bu optimize edilmiş bir yapı için kod. Ve soru hakkında olduğu içinstd::vector olduğu için evet! düz dizilerden daha yavaş (optimize edilmiş / optimize edilmemiş). Ancak bir kıyaslama yaparken, doğal olarak optimize edilmiş kod üretmek istersiniz.

Benim için gösterinin yıldızı oldu std::array.


0

Doğru seçeneklerle, vektörler ve diziler aynı grubu oluşturabilir . Bu durumlarda, elbette aynı hızdır, çünkü aynı yürütülebilir dosyayı her iki şekilde de alırsınız.


1
Bu durumda, aynı montajı oluşturuyor gibi görünmüyorlar. Özellikle, vektörleri kullanan kuruculara yapılan çağrıyı bastırmanın bir yolu yoktur. Bu sorunun cevabını burada bulabilirsiniz (uzun bir okuma ama kanıtladığınız bağlantıdaki basit test durumundan farklı durumlarda neden bir performans farkı olduğunu açıklıyor.) (Aslında, bir yol var gibi görünüyor - - önerildiği gibi özel bir STL ayırıcısı
yazmak.Kişisel olarak,

1
@ kizzx2: Kullanmak için böyle uzunlukları gitmek zorunda O unconstructed bu bir hata% 99 olduğu için nesneleri iyi bir şey olduğunu zaman (ben fena halde hafife olabilir). Diğer cevapları okudum ve özel durumunuza değinmediğimi fark ettim (gerek yok, diğer cevaplar doğru), ancak yine de size vektörlerin ve dizilerin tam olarak aynı şekilde davranabileceğine dair bu örneği sunmak istedim.

@Roger: bu harika! Bağlantı için teşekkürler
kizzx2

0

Bu arada, vektörü kullanarak sınıflarda görmenizi yavaşlatmak da int gibi standart türlerde gerçekleşir. Çok iş parçacıklı kod heres:

#include <iostream>
#include <cstdio>
#include <map>
#include <string>
#include <typeinfo>
#include <vector>
#include <pthread.h>
#include <sstream>
#include <fstream>
using namespace std;

//pthread_mutex_t map_mutex=PTHREAD_MUTEX_INITIALIZER;

long long num=500000000;
int procs=1;

struct iterate
{
    int id;
    int num;
    void * member;
    iterate(int a, int b, void *c) : id(a), num(b), member(c) {}
};

//fill out viterate and piterate
void * viterate(void * input)
{
    printf("am in viterate\n");
    iterate * info=static_cast<iterate *> (input);
    // reproduce member type
    vector<int> test= *static_cast<vector<int>*> (info->member);
    for (int i=info->id; i<test.size(); i+=info->num)
    {
        //printf("am in viterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

void * piterate(void * input)
{
    printf("am in piterate\n");
    iterate * info=static_cast<iterate *> (input);;
    int * test=static_cast<int *> (info->member);
    for (int i=info->id; i<num; i+=info->num) {
        //printf("am in piterate loop\n");
        test[i];
    }
    pthread_exit(NULL);
}

int main()
{
    cout<<"producing vector of size "<<num<<endl;
    vector<int> vtest(num);
    cout<<"produced  a vector of size "<<vtest.size()<<endl;
    pthread_t thread[procs];

    iterate** it=new iterate*[procs];
    int ans;
    void *status;

    cout<<"begining to thread through the vector\n";
    for (int i=0; i<procs; i++) {
        it[i]=new iterate(i, procs, (void *) &vtest);
    //  ans=pthread_create(&thread[i],NULL,viterate, (void *) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the vector";
    //reuse the iterate structures

    cout<<"producing a pointer with size "<<num<<endl;
    int * pint=new int[num];
    cout<<"produced a pointer with size "<<num<<endl;

    cout<<"begining to thread through the pointer\n";
    for (int i=0; i<procs; i++) {
        it[i]->member=&pint;
        ans=pthread_create(&thread[i], NULL, piterate, (void*) it[i]);
    }
    for (int i=0; i<procs; i++) {
        pthread_join(thread[i], &status);
    }
    cout<<"end of threading through the pointer\n";

    //delete structure array for iterate
    for (int i=0; i<procs; i++) {
        delete it[i];
    }
    delete [] it;

    //delete pointer
    delete [] pint;

    cout<<"end of the program"<<endl;
    return 0;
}

Koddaki davranış, vektörün somutlaştırılmasının kodun en uzun kısmı olduğunu gösterir. Bir kez o şişe boynundan geçiyorsun. Kodun geri kalanı son derece hızlı çalışır. Kaç tane iş parçacığı üzerinde çalıştığınız önemli değil.

Bu arada kesinlikle deli sayıda içerir görmezden gelin. Bu kodu kullanarak bir proje için bir şeyler test etmek için kullanıyorum içerir dahil sayısı büyümeye devam.


0

Ben sadece vektör (ve smart_ptr) sadece ince diziler (ve ham işaretçiler) üstüne ince bir katman eklemek olduğunu belirtmek istiyorum. Ve aslında sürekli bellekteki bir vektörün erişim süresi diziden daha hızlıdır. Aşağıdaki kod, başlatma ve erişim vektörü ve dizisinin sonucunu gösterir.

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
#define SIZE 20000
int main() {
    srand (time(NULL));
    vector<vector<int>> vector2d;
    vector2d.reserve(SIZE);
    int index(0);
    boost::posix_time::ptime start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        vector2d.push_back(vector<int>(SIZE));
    }
    boost::posix_time::ptime start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            vector2d[index][index]++;
        }
    }
    boost::posix_time::ptime end = boost::posix_time::microsec_clock::local_time();
    boost::posix_time::time_duration msdiff = end - start_total;
    cout << "Vector total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Vector access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 


    int index(0);
    int** raw2d = nullptr;
    raw2d = new int*[SIZE];
    start_total = boost::posix_time::microsec_clock::local_time();
    //  timer start - build + access
    for (int i = 0; i < SIZE; i++) {
        raw2d[i] = new int[SIZE];
    }
    start_access = boost::posix_time::microsec_clock::local_time();
    //  timer start - access
    for (int i = 0; i < SIZE; i++) {
        index = rand()%SIZE;
        for (int j = 0; j < SIZE; j++) {

            raw2d[index][index]++;
        }
    }
    end = boost::posix_time::microsec_clock::local_time();
    msdiff = end - start_total;
    cout << "Array total time: " << msdiff.total_milliseconds() << "milliseconds.\n";
    msdiff = end - start_acess;
    cout << "Array access time: " << msdiff.total_milliseconds() << "milliseconds.\n"; 
    for (int i = 0; i < SIZE; i++) {
        delete [] raw2d[i];
    }
    return 0;
}

Çıktı:

    Vector total time: 925milliseconds.
    Vector access time: 4milliseconds.
    Array total time: 30milliseconds.
    Array access time: 21milliseconds.

Eğer düzgün kullanırsanız hız neredeyse aynı olacaktır. (yedek () veya resize () kullanılarak belirtildiği gibi).


0

Çünkü vector :: resize (), düz bellek ayırmadan (malloc ile) çok daha fazla işlem yapar.

Kopya oluşturucunuza bir kesme noktası koymaya çalışın (kesme noktası oluşturabilmeniz için tanımlayın!) Ve ek işlem süresi geçer.


0

C ++ konusunda uzman olmadığımı söylemeliyim. Ancak bazı deney sonuçları eklemek için:

derleme: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp

makine:

Intel(R) Xeon(R) CPU E5-2690 v2 @ 3.00GHz 

İŞLETİM SİSTEMİ:

2.6.32-642.13.1.el6.x86_64

Çıktı:

UseArray completed in 0.167821 seconds
UseVector completed in 0.134402 seconds
UseConstructor completed in 0.134806 seconds
UseFillConstructor completed in 1.00279 seconds
UseVectorPushBack completed in 6.6887 seconds
The whole thing completed in 8.12888 seconds

Burada garip hissettiğim tek şey, "UseConstructor" ile karşılaştırıldığında "UseFillConstructor" performansı.

Kod:

void UseConstructor()
{
    TestTimer t("UseConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension);
        for(int i = 0; i < dimension * dimension; ++i)
        {
            pixels[i].r = 255;
            pixels[i].g = 0;
            pixels[i].b = 0;
        }
    }
}


void UseFillConstructor()
{
    TestTimer t("UseFillConstructor");

    for(int i = 0; i < 1000; ++i)
    {
        int dimension = 999;

        std::vector<Pixel> pixels(dimension*dimension, Pixel(255,0,0));
    }
}

Sağlanan ek "değer" performansı oldukça yavaşlatır, bu da kopya yapıcısına yapılan çoklu çağrıdan kaynaklandığını düşünüyorum. Fakat...

Derleme:

gcc-6.2.0/bin/g++ -std=c++14 -O vector.cpp

Çıktı:

UseArray completed in 1.02464 seconds
UseVector completed in 1.31056 seconds
UseConstructor completed in 1.47413 seconds
UseFillConstructor completed in 1.01555 seconds
UseVectorPushBack completed in 6.9597 seconds
The whole thing completed in 11.7851 seconds

Bu durumda, gcc optimizasyonu çok önemlidir, ancak varsayılan olarak bir değer sağlandığında size çok yardımcı olamaz. Bu aslında dersime aykırı. Umarım hangi vektör başlatma formatını seçtiğinde yeni programcıya yardımcı olur.


0

Derleyici bayraklarına bağlı gibi görünüyor. İşte bir karşılaştırma kodu:

#include <chrono>
#include <cmath>
#include <ctime>
#include <iostream>
#include <vector>


int main(){

    int size = 1000000; // reduce this number in case your program crashes
    int L = 10;

    std::cout << "size=" << size << " L=" << L << std::endl;
    {
        srand( time(0) );
        double * data = new double[size];
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C style heap array:    " << duration << "ms\n";
        delete data;
    }

    {
        srand( 1 + time(0) );
        double data[size]; // technically, non-compliant with C++ standard.
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of C99 style stack array: " << duration << "ms\n";
    }

    {
        srand( 2 + time(0) );
        std::vector<double> data( size );
        double result = 0.;
        std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
        for( int l = 0; l < L; l++ ) {
            for( int i = 0; i < size; i++ ) data[i] = rand() % 100;
            for( int i = 0; i < size; i++ ) result += data[i] * data[i];
        }
        std::chrono::steady_clock::time_point end   = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        std::cout << "Calculation result is " << sqrt(result) << "\n";
        std::cout << "Duration of std::vector array:     " << duration << "ms\n";
    }

    return 0;
}

Farklı optimizasyon bayrakları farklı yanıtlar verir:

$ g++ -O0 benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181182
Duration of C style heap array:    118441ms
Calculation result is 181240
Duration of C99 style stack array: 104920ms
Calculation result is 181210
Duration of std::vector array:     124477ms
$g++ -O3 benchmark.cpp
$ ./a.out 
size=1000000 L=10
Calculation result is 181213
Duration of C style heap array:    107803ms
Calculation result is 181198
Duration of C99 style stack array: 87247ms
Calculation result is 181204
Duration of std::vector array:     89083ms
$ g++ -Ofast benchmark.cpp 
$ ./a.out 
size=1000000 L=10
Calculation result is 181164
Duration of C style heap array:    93530ms
Calculation result is 181179
Duration of C99 style stack array: 80620ms
Calculation result is 181191
Duration of std::vector array:     78830ms

Kesin sonuçlarınız değişecektir, ancak bu benim makinemde oldukça tipiktir.


0

Deneyimlerime göre, bazen, sadece bazen, vector<int>bundan çok daha yavaş olabilir int[]. Akılda tutulması gereken bir şey, vektörlerin vektörlerinin çok benzememesidir int[][]. Çünkü elemanlar muhtemelen bellekte bitişik değildir. Bu, ana vektör içindeki farklı vektörleri yeniden boyutlandırabileceğiniz anlamına gelir, ancak CPU, durumun yanı sıra öğeleri önbelleğe alamayabilir int[][].

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.