Zawsze myślałem, że to ogólna mądrość std::vector
„zaimplementowana jako tablica”, bla bla bla. Dzisiaj zszedłem na dół i przetestowałem go, i wydaje się, że tak nie jest:
Oto kilka wyników testu:
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
To około 3 - 4 razy wolniej! Naprawdę nie usprawiedliwia to vector
komentarzy „ może być wolniejsze dla kilku nanosektów”.
I użyłem kodu:
#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;
}
Czy robię to źle czy coś? A może po prostu rozwaliłem ten mit o wydajności?
Używam trybu Release w Visual Studio 2005 .
W Visual C ++ , #define _SECURE_SCL 0
zmniejsza UseVector
się o połowę (doprowadzenia do 4 sekund). To naprawdę ogromne, IMO.
vector
jest to uniwersalna tablica o zmiennym rozmiarze. Gratulacje. Podobnie jak w przypadku wszystkich narzędzi ogólnego zastosowania, możliwe jest wymyślenie specjalistycznych sytuacji, w których nie jest on optymalny. Właśnie dlatego konwencjonalna mądrość ma zaczynać się odvector
i rozważać alternatywy, jeśli to konieczne.Odpowiedzi:
Wykorzystując następujące:
Tak więc tablica jest dwa razy szybsza niż wektor.
Ale po szczegółowym spojrzeniu na kod jest to oczekiwane; gdy dwa razy biegniesz przez wektor, a tablicę tylko raz. Uwaga: gdy jesteś
resize()
wektorem, nie tylko alokujesz pamięć, ale również biegniesz przez wektor i wywołujesz konstruktor na każdym elemencie.Lekkie ponowne uporządkowanie kodu, aby wektor inicjował każdy obiekt tylko raz:
Teraz znów wykonuj ten sam czas:
Wydajność wektora jest teraz tylko nieco gorsza niż macierz. IMO ta różnica jest nieznaczna i może być spowodowana całą masą rzeczy niezwiązanych z testem.
Chciałbym również wziąć pod uwagę, że nie inicjujesz poprawnie / niszczymy obiektu Pixel w
UseArrray()
metodzie, ponieważ żaden konstruktor / destruktor nie jest wywoływany (może to nie być problem dla tej prostej klasy, ale coś nieco bardziej złożonego (tj. Ze wskaźnikami lub elementami) ze wskaźnikami) spowoduje problemy.źródło
reserve()
zamiastresize()
. Przydziela to miejsce dla obiektów (to znaczy zmienia pojemność wektora), ale nie tworzy obiektów (tzn. Rozmiar wektora pozostaje niezmieniony).resize()
sięreserve()
, bo to nie regulować wewnętrzny pomysł nosiciela własnej wielkości, więc kolejne zapisuje jej elementów są technicznie „pisanie poza końcem” i będzie produkować UB. Chociaż w praktyce każda implementacja STL będzie się „zachowywać” w tym względzie, jak ponownie zsynchronizować rozmiar wektora? Jeśli spróbujesz wywołaćresize()
po zapełnieniu wektora, całkiem możliwe, że nadpisze wszystkie te elementy wartościami skonstruowanymi domyślnie!reserve
zmienia tylko pojemność wektora, a nie jego rozmiar./EHsc
przełączników kompilacji wyczyściło to iassign()
faktycznie bije teraz tablicę. TakŚwietne pytanie. Przyszedłem tutaj, oczekując prostej poprawki, która przyspieszyłaby testy wektorowe. To nie zadziałało tak, jak się spodziewałem!
Optymalizacja pomaga, ale to nie wystarczy. Przy włączonej optymalizacji wciąż widzę 2-krotną różnicę wydajności między UseArray i UseVector. Co ciekawe, UseVector był znacznie wolniejszy niż UseVectorPushBack bez optymalizacji.
Pomysł nr 1 - Użyj nowego [] zamiast malloc
Próbowałem zmienić
malloc()
nanew[]
UseArray, aby obiekty mogły zostać skonstruowane. I przejście z indywidualnego przypisania pola do przypisania instancji Pixel. Aha, i zmiana nazwy zmiennej pętli wewnętrznej naj
.Zaskakujące (dla mnie), żadna z tych zmian nie zrobiła żadnej różnicy. Nawet zmiana,
new[]
która domyślnie skonstruuje wszystkie piksele. Wygląda na to, że gcc może zoptymalizować domyślne wywołania konstruktora podczas używanianew[]
, ale nie podczas używaniavector
.Pomysł nr 2 - Usuwanie powtarzających się połączeń operatora []
Próbowałem także pozbyć się potrójnego
operator[]
wyszukiwania i buforować odniesienie dopixels[j]
. To faktycznie spowolniło UseVector! UpsPomysł # 3 - Usuń konstruktory
A co z całkowitym usunięciem konstruktorów? Być może wtedy gcc może zoptymalizować konstrukcję wszystkich obiektów podczas tworzenia wektorów. Co się stanie, jeśli zmienimy Pixel na:
Wynik: około 10% szybciej. Wciąż wolniejszy niż tablica. Hm
Pomysł 4 - Użyj iteratora zamiast indeksu pętli
A może
vector<Pixel>::iterator
zamiast indeksu pętli?Wynik:
Nie, nie inaczej. Przynajmniej nie jest wolniejszy. Myślałem, że będzie to miało wydajność podobną do # 2, w której użyłem
Pixel&
odniesienia.Wniosek
Nawet jeśli niektóre inteligentne pliki cookie dowiedzą się, jak zrobić pętlę wektorową tak szybko jak tablica, nie mówi to dobrze o domyślnym zachowaniu
std::vector
. Tyle, że kompilator jest wystarczająco inteligentny, aby zoptymalizować całą C ++ i sprawić, że kontenery STL są tak szybkie, jak surowe tablice.Najważniejsze jest to, że kompilator nie jest w stanie zoptymalizować domyślnych wywołań konstruktora, gdy nie można go użyć
std::vector
. Jeśli używasz zwykłegonew[]
, optymalizuje je w porządku. Ale nie zstd::vector
. Nawet jeśli możesz przepisać kod, aby wyeliminować wywołania konstruktora, które lecą w pobliżu mantry: „Kompilator jest mądrzejszy od ciebie. STL jest tak samo szybki jak zwykły C. Nie martw się o to”.źródło
vector<int>
.To stare, ale popularne pytanie.
W tym momencie wielu programistów będzie pracować w C ++ 11. A w C ++ 11 zapisany kod OP działa równie szybko dla
UseArray
lubUseVector
.Podstawowym problemem było to, że podczas gdy Twoja
Pixel
struktura nie była zainicjalizowana,std::vector<T>::resize( size_t, T const&=T() )
pobiera domyślną konstruktPixel
i kopiuje ją . Kompilator nie zauważył, że został poproszony o skopiowanie niezainicjowanych danych, więc faktycznie wykonał kopię.W C ++ 11
std::vector<T>::resize
ma dwa przeciążenia. Pierwsza tostd::vector<T>::resize(size_t)
drugastd::vector<T>::resize(size_t, T const&)
. Oznacza to, że gdy wywołujeszresize
bez drugiego argumentu, to po prostu domyślna konstrukcja, a kompilator jest wystarczająco inteligentny, aby zdać sobie sprawę, że domyślna konstrukcja nic nie robi, więc pomija przejście przez bufor.(Dwa przeciążenia zostały dodane w celu obsługi typów ruchomych, możliwych do zbudowania i niemożliwych do skopiowania - poprawa wydajności podczas pracy z niezainicjowanymi danymi jest zaletą).
push_back
Rozwiązanie ma również Fencepost kontrolnych, które spowalnia go, więc pozostaje wolniejszy odmalloc
wersji.przykład na żywo (zastąpiłem również timer
chrono::high_resolution_clock
).Pamiętaj, że jeśli masz strukturę, która zwykle wymaga inicjalizacji, ale chcesz obsłużyć ją po zwiększeniu bufora, możesz to zrobić za pomocą niestandardowego programu
std::vector
przydzielającego. Jeśli chcesz zmienić go na bardziej normalnystd::vector
, uważam, że ostrożne użycieallocator_traits
i zastąpienie==
może to spowodować, ale nie jestem pewien.źródło
emplace_back
vs.push_back
clang++ -std=c++11 -O3
maUseArray completed in 2.02e-07 seconds
iUseVector completed in 1.3026 seconds
. Dodałem takżeUseVectorEmplaceBack
wersję, która jest ok. 2,5x tak szybko jakUseVectorPushBack
.Szczerze mówiąc, nie można porównywać implementacji C ++ z implementacją C, jak nazwałbym twoją wersję malloc. malloc nie tworzy obiektów - przydziela tylko surową pamięć. To, że następnie traktujesz tę pamięć jako obiekty bez wywoływania konstruktora, to słabe C ++ (być może nieprawidłowe - zostawię to prawnikom językowym).
To powiedziawszy, po prostu zmiana malloc na
new Pixel[dimensions*dimensions]
i freedelete [] pixels
to nie robi dużej różnicy z prostą implementacją Pixela, którą masz. Oto wyniki na moim urządzeniu (E6600, 64-bit):Ale z niewielką zmianą tabele zmieniają się:
Pixel.h
Pixel.cc
main.cc
Opracowano w ten sposób:
otrzymujemy bardzo różne wyniki:
Z nie wbudowanym konstruktorem Pixela, std :: vector bije teraz surową tablicę.
Wydaje się, że złożoność alokacji poprzez std :: vector i std: alokator jest zbyt duża, aby ją zoptymalizować równie skutecznie jak prostą
new Pixel[n]
. Widzimy jednak, że problemem jest po prostu alokacja, a nie dostęp do wektora, poprzez dostosowanie kilku funkcji testowych w celu utworzenia wektora / tablicy raz poprzez przeniesienie go poza pętlę:i
Otrzymujemy teraz te wyniki:
Możemy się z tego nauczyć, że std :: vector jest porównywalny z surową tablicą w celu uzyskania dostępu, ale jeśli trzeba wiele razy tworzyć i usuwać wektor / tablicę, tworzenie obiektu złożonego będzie bardziej czasochłonne niż tworzenie prostej tablicy gdy konstruktor elementu nie jest wstawiony. Nie sądzę, żeby to było bardzo zaskakujące.
źródło
Spróbuj tego:
Dostaję prawie dokładnie taką samą wydajność jak w przypadku macierzy.
Chodzi o
vector
to, że jest to znacznie bardziej ogólne narzędzie niż tablica. A to oznacza, że musisz rozważyć, w jaki sposób niego korzystać. Można go używać na wiele różnych sposobów, zapewniając funkcjonalność, której tablica nawet nie ma. A jeśli użyjesz go „niewłaściwie” do swoich celów, poniesiesz dużo narzutu, ale jeśli użyjesz go poprawnie, zwykle jest to w zasadzie struktura danych zerowych narzutów. W takim przypadku problem polega na tym, że oddzielnie zainicjowałeś wektor (powodując wywołanie domyślnego ctor wszystkich elementów), a następnie nadpisałeś każdy element osobno prawidłową wartością. Jest to o wiele trudniejsze dla kompilatora do optymalizacji, niż gdy robisz to samo z tablicą. Właśnie dlatego wektor zapewnia konstruktor, który pozwala zrobić dokładnie to: .N
X
A kiedy go użyjesz, wektor jest tak samo szybki jak tablica.
Więc nie, nie zniszczyłeś mitu o wydajności. Ale pokazałeś, że jest to prawdą tylko wtedy, gdy używasz wektora optymalnie, co również jest całkiem dobrym punktem. :)
Z drugiej strony, to naprawdę najprostsze użycie, które okazuje się najszybsze. Jeśli porównasz mój fragment kodu (pojedynczą linię) z odpowiedzią Johna Kugelmana, zawierającą stosy ulepszeń i optymalizacji, które wciąż nie do końca eliminują różnicę wydajności, jest całkiem jasne, że
vector
jest całkiem sprytnie zaprojektowany. Nie musisz skakać przez obręcze, aby uzyskać prędkość równą tablicy. Przeciwnie, musisz użyć najprostszego możliwego rozwiązania.źródło
new[]
wykonuje te same domyślne konstrukcje, covector.resize()
robi, ale jest znacznie szybsze.new[]
+ wewnętrzna pętla powinna mieć taką samą prędkość jakvector.resize()
+ wewnętrzna pętla, ale nie jest, jest prawie dwa razy szybsza.malloc
co niczego nie inicjuje ani nie konstruuje, więc jest to algorytm jednoprzebiegowy, podobnie jak mojavector
próbka. A jeśli chodzinew[]
o odpowiedź, to oczywiście, że oba wymagają dwóch przebiegów, ale w tymnew[]
przypadku kompilator jest w stanie zoptymalizować to dodatkowe obciążenie, czego nie robi w tymvector
przypadku. Ale nie rozumiem, dlaczego interesujące jest to, co dzieje się w przypadkach nieoptymalnych. Jeśli zależy Ci na wydajności, nie piszesz takiego kodu.vector::resize()
aby dać mi sporą część pamięci bez marnowania czasu na wzywanie bezużytecznych konstruktorów.malloc
która nie wykonuje inicjalizacji, ale nie będzie działać w C ++ z typami innymi niż POD. Tak więc w ogólnym przypadku tablica C ++ byłaby równie zła. Być może pytanie brzmi: jeśli zamierzasz często wykonywać ten blitting, czy nie użyłbyś tego samego zestawu / wektora? A jeśli to zrobisz, koszty „niepotrzebnych konstruktorów” płacisz tylko raz, na samym początku. W końcu blitting jest tak samo szybki.To nie było uczciwe porównanie, kiedy po raz pierwszy spojrzałem na twój kod; Zdecydowanie myślałem, że nie porównujesz jabłek z jabłkami. Pomyślałem więc: wywołajmy konstruktory i destruktory wywoływane we wszystkich testach; a następnie porównaj.
Myślałem, że przy takim ustawieniu powinny być dokładnie takie same. Okazuje się, że się myliłem.
Dlaczego więc w ogóle spadła ta 30% wydajność? STL ma wszystko w nagłówkach, więc kompilator powinien mieć możliwość zrozumienia wszystkiego, co było wymagane.
Myślałem, że to w ten sposób pętla inicjuje wszystkie wartości do domyślnego konstruktora. Więc wykonałem test:
Wyniki były takie, jak podejrzewałem:
Jest to oczywiście źródło spowolnienia, fakt, że wektor używa konstruktora kopiowania do inicjowania elementów z domyślnie zbudowanego obiektu.
Oznacza to, że podczas budowy wektora zachodzi następująca kolejność pseudooperacji:
Który, ze względu na niejawny konstruktor kopii wykonany przez kompilator, jest rozszerzony do następujących elementów:
Tak więc domyślna
Pixel
pozostaje niezainicjalizowana, podczas gdy pozostałe są inicjalizowane z domyślnymPixel
„s un-zainicjowane wartościami.W porównaniu do alternatywnej sytuacji z
New[]
/Delete[]
:Wszystkie są pozostawione niezainicjowanym wartościom i bez podwójnej iteracji w sekwencji.
Uzbrojeni w te informacje, w jaki sposób możemy to przetestować? Spróbujmy nadpisać niejawny konstruktor kopii.
A wyniki?
Podsumowując, jeśli robisz setki wektorów bardzo często: przemyśl swój algorytm .
W każdym razie implementacja STL nie jest wolniejsza z nieznanego powodu, robi dokładnie to, o co prosisz; mając nadzieję, że wiesz lepiej.
źródło
Spróbuj wyłączyć sprawdzone iteratory i budować w trybie zwolnienia. Nie powinieneś widzieć dużej różnicy w wydajności.
źródło
#define _SECURE_SCL 0
. To zrobiłoUseVector
gdzieś około 4 sekund (podobnie jakgcc
poniżej), ale wciąż jest dwa razy wolniejsze.-O3
._HAS_ITERATOR_DEBUGGING
jest wyłączony w kompilacji wersji: msdn.microsoft.com/en-us/library/aa985939(VS.80).aspxBiorąc pod uwagę
vector<T>(n)
, że STL GNU (i inne) domyślnie konstruuje obiekt prototypowyT()
- kompilator zoptymalizuje pustego konstruktora - ale następnie kopia wszelkiego śmieci, które zdarzyły się w adresach pamięci zarezerwowanych teraz dla obiektu, jest pobierana przez STL__uninitialized_fill_n_aux
, które zapętla zapełniające się kopie tego obiektu jako wartości domyślne w wektorze. Tak więc „mój” STL nie tworzy pętli, lecz konstruuje następnie pętlę / kopiowanie. Jest to sprzeczne z intuicją, ale powinienem pamiętać, gdy skomentowałem ostatnie pytanie dotyczące przepełnienia stosu na ten temat: konstrukcja / kopia może być bardziej wydajna dla obiektów zliczanych według odniesienia itp.Więc:
lub
jest - w wielu implementacjach STL - coś takiego:
Problem polega na tym, że obecna generacja optymalizatorów kompilatora nie wydaje się działać z wglądu, że temp jest niezainicjowanym śmieciem, i nie optymalizuje pętli i domyślnych wywołań konstruktora kopii. Można rzetelnie argumentować, że kompilatory absolutnie nie powinny tego optymalizować, ponieważ programista piszący powyższe ma uzasadnione oczekiwania, że wszystkie obiekty będą identyczne po pętli, nawet jeśli zostaną wyrzucone (zwykłe zastrzeżenia dotyczące „identycznych” / operatora == vs obowiązują memcmp / operator = etc). Nie można oczekiwać, że kompilator będzie miał dodatkowy wgląd w szerszy kontekst std :: vector <> lub późniejsze użycie danych, które sugerują, że ta optymalizacja jest bezpieczna.
Można to skontrastować z bardziej oczywistą, bezpośrednią implementacją:
Którego możemy oczekiwać, że kompilator zoptymalizuje.
Aby być bardziej precyzyjnym na temat uzasadnienia tego aspektu zachowania wektora, rozważ:
Oczywiście jest to główna różnica, jeśli wykonujemy 10000 niezależnych obiektów w porównaniu do 10000 odwołujących się do tych samych danych. Istnieje uzasadniony argument, że korzyść z ochrony zwykłych użytkowników C ++ przed przypadkowym zrobieniem czegoś tak kosztownego przeważa nad bardzo małymi rzeczywistymi kosztami trudnej do optymalizacji konstrukcji kopii.
ORYGINALNA ODPOWIEDŹ (dla odniesienia / sensu komentarzy): Brak szansy. wektor jest tak szybki jak tablica, przynajmniej jeśli rozsądnie zarezerwujesz miejsce. ...
źródło
Martina mnie martwi odpowiedź Martina Yorka, ponieważ wydaje się to próbą wyszczotkowania problemu inicjalizacji pod dywan. Ma jednak rację, wskazując zbędną domyślną konstrukcję jako źródło problemów z wydajnością.
[EDYCJA: Odpowiedź Martina nie sugeruje już zmiany domyślnego konstruktora.]
W celu natychmiastowego rozwiązania problemu można
vector<Pixel>
zamiast tego wywołać 2-parametrową wersję ctor:Działa to, jeśli chcesz zainicjować ze stałą wartością, co jest częstym przypadkiem. Ale bardziej ogólnym problemem jest: Jak skutecznie zainicjować coś bardziej skomplikowanego niż stała wartość?
W tym celu można użyć
back_insert_iterator
adaptera iteratora. Oto przykład z wektoremint
s, chociaż ogólny pomysł działa równie dobrze dlaPixel
s:Alternatywnie możesz użyć
copy()
lubtransform()
zamiastgenerate_n()
.Minusem jest to, że logika do konstruowania wartości początkowych musi zostać przeniesiona do osobnej klasy, co jest mniej wygodne niż posiadanie jej w miejscu (chociaż lambdas w C ++ 1x sprawiają, że jest to o wiele ładniejsze). Spodziewam się również, że to nie będzie tak szybkie, jak
malloc()
wersja nieoparta na STL, ale spodziewam się, że będzie blisko, ponieważ robi tylko jedną konstrukcję dla każdego elementu.źródło
Te wektorowe wywołują dodatkowo konstruktory pikseli.
Każdy z nich powoduje prawie milion uruchomień ctor, które mierzysz.
edycja: wtedy jest zewnętrzna pętla 1 ... 1000, więc spraw, aby miliard wywołań ctor!
edycja 2: byłoby interesujące zobaczyć demontaż skrzynki UseArray. Optymalizator może zoptymalizować całość, ponieważ nie ma innego efektu niż spalanie procesora.
źródło
Oto jak
push_back
metoda w wektorze:Po wywołaniu
push_back
X przedmiotów:Powtarzać. Jeśli nie jesteś
reserving
miejsca, to na pewno będzie wolniej. Co więcej, jeśli kopiowanie elementu jest drogie, wówczas „push_back” zje cię żywcem.Co się tyczy
vector
kwestię porównania z tablicą, będę musiał się zgodzić z innymi ludźmi. Uruchom w wydaniu, włącz optymalizacje i dodaj jeszcze kilka flag, aby przyjaźni ludzie w Microsoft nie # @% $ ^ ci to zrobili.Jeszcze jedno, jeśli nie musisz zmieniać rozmiaru, użyj Boost.Array.
źródło
reserve
jak powinienem.push_back
zamortyzował stały czas. Brzmi, jakbyś opisywał proces O (N). (Kroki 1 i 3 wydają się zupełnie nie na miejscu.)push_back
Powolne dla OP jest sprawdzenie zasięgu, aby sprawdzić, czy należy dokonać realokacji, aktualizacja wskaźników, sprawdzenie względem NULL wewnątrz umieszczenianew
i inne małe rzeczy, które normalnie są zagłuszane przez rzeczywista praca programu.reserve
jeśli nadal musi to sprawdzać (czy trzeba go ponownie przydzielić) na każdympush_back
.vector
wykonuje swoją funkcję zmiany rozmiaru, to po prostu „magia”. Pozwól, że wyjaśnię to nieco bardziej.Niektóre dane profilera (piksel jest wyrównany do 32 bitów):
Bla
W
allocator
:vector
:szyk
Większość kosztów ogólnych jest w konstruktorze kopiowania. Na przykład,
Ma taką samą wydajność jak tablica.
źródło
pixels.size()
zostanie zepsute.Mój laptop to Lenova G770 (4 GB pamięci RAM).
System operacyjny to Windows 7 64-bit (ten z laptopem)
Kompilatorem jest MinGW 4.6.1.
IDE to Code :: Blocks .
Testuję kody źródłowe pierwszego postu.
Wyniki
Optymalizacja O2
Użycie tablicy zakończono w 2,841 sekund
UseVector ukończono w 2,548 sekund
UseVectorPushBack ukończony w 11,95 sekundy
Całość zakończona w 17.342 sekund
pauza systemu
Optymalizacja O3
Użycie tablicy zakończone w 1.452 sekund
UseVector ukończony w 2,514 sekund
UseVectorPushBack ukończono w 12,967 sekund
Całość zakończona w 16,937 sekund
Wygląda na to, że wydajność wektora jest gorsza przy optymalizacji O3.
Jeśli zmienisz pętlę na
Szybkość macierzy i wektora pod O2 i O3 są prawie takie same.
źródło
Lepszy test porównawczy (myślę ...), kompilator ze względu na optymalizacje może zmieniać kod, ponieważ wyniki przydzielonych wektorów / tablic nigdzie nie są używane. Wyniki:
Kompilator:
PROCESOR:
I kod:
źródło
Zrobiłem teraz kilka obszernych testów, które chciałem zrobić. Równie dobrze mogę się tym podzielić.
To moja maszyna z podwójnym uruchomieniem i7-3770, 16 GB RAM, x86_64, na Windows 8.1 i Ubuntu 16.04. Więcej informacji i wnioski, uwagi poniżej. Testowano zarówno MSVS 2017, jak i g ++ (zarówno w systemie Windows, jak i Linux).
Program testowy
Wyniki
Notatki
std::sort()
(możesz to skomentować), ale później je usunąłem, ponieważ nie było istotnych różnic względnych.Moje wnioski i uwagi
std::array
wahań czasowych między kolejnymi przebiegami, podczas gdy inne, szczególnie struktury std :: data, różniły się znacznie w porównaniustd::array
tablice w stylu c są szybsze w systemie Windows bez optymalizacjiWerdykt
Oczywiście jest to kod zoptymalizowanej kompilacji. A ponieważ pytanie dotyczyło
std::vector
tak, to jest! wolniej niż zwykłe tablice (zoptymalizowane / niezoptymalizowane). Ale kiedy przeprowadzasz test porównawczy, naturalnie chcesz stworzyć zoptymalizowany kod.Gwiazdą programu była jednak dla mnie
std::array
.źródło
Przy odpowiednich opcjach wektory i tablice mogą generować identyczny asm . W takich przypadkach mają one oczywiście tę samą prędkość, ponieważ i tak uzyskujesz ten sam plik wykonywalny.
źródło
Nawiasem mówiąc, spowolnienie widzenia w klasach za pomocą wektora występuje również w przypadku standardowych typów, takich jak int. Oto kod wielowątkowy:
Zachowanie z kodu pokazuje, że tworzenie wektora jest najdłuższą częścią kodu. Po przejściu przez szyjkę butelki. Reszta kodu działa bardzo szybko. Jest to prawdą bez względu na to, ile wątków używasz.
Nawiasem mówiąc, zignoruj absolutnie szaloną liczbę włączeń. Używam tego kodu do testowania różnych elementów projektu, więc liczba dołączeń ciągle rośnie.
źródło
Chcę tylko wspomnieć, że wektor (i smart_ptr) to po prostu cienka warstwa dodana na surowych tablicach (i surowych wskaźnikach). W rzeczywistości czas dostępu wektora w ciągłej pamięci jest szybszy niż macierz. Poniższy kod pokazuje wynik inicjalizacji i dostępu do wektora i tablicy.
Dane wyjściowe to:
Więc prędkość będzie prawie taka sama, jeśli użyjesz go prawidłowo. (jak inni wspominali przy użyciu zastrzeżone () lub resize ()).
źródło
Cóż, ponieważ vector :: resize () wykonuje znacznie więcej przetwarzania niż zwykły przydział pamięci (przez malloc).
Spróbuj umieścić punkt przerwania w swoim konstruktorze kopiowania (zdefiniuj go, aby można było zrobić punkt przerwania!), A wtedy pojawi się dodatkowy czas przetwarzania.
źródło
Muszę powiedzieć, że nie jestem ekspertem w C ++. Ale aby dodać wyniki niektórych eksperymentów:
kompilacja: gcc-6.2.0 / bin / g ++ -O3 -std = c ++ 14 vector.cpp
maszyna:
OS:
Wynik:
Tutaj jedyne, co wydaje mi się dziwne, to wydajność „UseFillConstructor” w porównaniu z „UseConstructor”.
Kod:
Tak więc podana dodatkowa „wartość” znacznie spowalnia działanie, co moim zdaniem wynika z wielokrotnego wywołania konstruktora. Ale...
Skompilować:
Wynik:
Tak więc w tym przypadku optymalizacja gcc jest bardzo ważna, ale nie może ci pomóc, gdy wartość jest podana jako domyślna. To w rzeczywistości jest sprzeczne z moją nauką. Mam nadzieję, że pomoże nowemu programistowi w wyborze formatu inicjowania wektora.
źródło
Wygląda na to, że zależy od flag kompilatora. Oto kod testu porównawczego:
Różne flagi optymalizacji dają różne odpowiedzi:
Twoje dokładne wyniki będą się różnić, ale jest to dość typowe na mojej maszynie.
źródło
Z mojego doświadczenia wynika, że czasami, po prostu czasami,
vector<int>
może być wiele razy wolniejsza niżint[]
. Należy pamiętać, że wektory wektorów są bardzo różneint[][]
. Ponieważ elementy prawdopodobnie nie są ciągłe w pamięci. Oznacza to, że możesz zmieniać rozmiar różnych wektorów w głównym, ale procesor może nie być w stanie buforować elementów tak dobrze, jak w przypadkuint[][]
.źródło