Implementacja Visual C ++ std::dequema bardzo mały maksymalny rozmiar bloku (~ 16 bajtów, jeśli dobrze pamiętam; może 32) i jako taka nie działa zbyt dobrze dla realistycznych aplikacji. deque<T>Gdzie sizeof(T) > 8(lub 16? To mała liczba) ma o tych samych charakterystyk, jako vector<T*>, gdzie każdy element jest przydzielane dynamicznie. Inne implementacje mają różne maksymalne rozmiary bloków, więc pisanie kodu, który ma stosunkowo takie same parametry wydajności na różnych platformach, jest trudne deque.
James McNellis,
12
Deque nie jest kontenerem pamięci ciągłej.
Mohamed El-Nakib
@ravil Nie, to jest duplikat, wskazujący na to pytanie.
1
Trudno w to uwierzyć, na pytanie z nieokreślonym rażącym błędem merytorycznym przy bilansie 34 głosów
podkreślenie_d
2
@underscore_d, dlatego jest to pytanie. Inaczej historia, gdyby to była odpowiedź;)
Assimilater
Odpowiedzi:
115
Elementy w a niedeque są ciągłe w pamięci; elementy są gwarantowane. Więc jeśli potrzebujesz współdziałać ze zwykłą biblioteką C, która potrzebuje ciągłych tablic, lub jeśli zależy Ci (bardzo) na lokalności przestrzennej, możesz preferować . Ponadto, ponieważ istnieje dodatkowa księgowość, inne operacje są prawdopodobnie (nieco) droższe niż ich odpowiedniki . Z drugiej strony, używanie wielu / dużych wystąpień może prowadzić do niepotrzebnej fragmentacji sterty (spowolnienia wywołań ).vectorvectorvectorvectornew
Wygląda na to, że link do „gdzie indziej” jest teraz martwy (z powodu umiaru?).
esilk
37
Aby poznać różnicę, należy wiedzieć, w jaki sposób dequejest ogólnie wdrażany. Pamięć jest przydzielana w blokach o równych rozmiarach i są one połączone razem (jako tablica lub ewentualnie wektor).
Aby znaleźć n-ty element, należy znaleźć odpowiedni blok, a następnie uzyskać dostęp do elementu w nim zawartego. Jest to stały czas, ponieważ zawsze są to dokładnie 2 wyszukiwania, ale to wciąż więcej niż wektor.
vectordziała również dobrze z interfejsami API, które wymagają ciągłego bufora, ponieważ są to interfejsy API języka C lub są bardziej wszechstronne pod względem możliwości pobierania wskaźnika i długości. (W ten sposób możesz mieć wektor pod spodem lub zwykłą tablicę i wywołać API z bloku pamięci).
Gdzie dequema swoje największe zalety to:
Podczas wzrostu lub zmniejszania kolekcji z dowolnego końca
Kiedy masz do czynienia z bardzo dużymi rozmiarami kolekcji.
Kiedy masz do czynienia z bools i naprawdę chcesz, aby boole, a nie bitset.
Drugi z nich jest mniej znany, ale dla bardzo dużych rozmiarów kolekcji:
Koszt realokacji jest duży
Narzut związany ze znalezieniem ciągłego bloku pamięci jest restrykcyjny, więc możesz szybciej zabraknąć pamięci.
Kiedy w przeszłości miałem do czynienia z dużymi kolekcjami i przeniosłem się z modelu ciągłego do modelu blokowego, byliśmy w stanie przechowywać około 5 razy większą kolekcję, zanim skończyła się pamięć w systemie 32-bitowym. Dzieje się tak częściowo dlatego, że podczas ponownego przydzielania w rzeczywistości konieczne było przechowywanie starego bloku, a także nowego, zanim skopiował elementy.
Powiedziawszy to wszystko, możesz mieć kłopoty z std::dequesystemami używającymi „optymistycznej” alokacji pamięci. Chociaż jego próby zażądania dużego rozmiaru bufora w celu ponownej alokacji vectortestamentu prawdopodobnie zostaną w pewnym momencie odrzucone przez a bad_alloc, optymistyczny charakter osoby dokonującej alokacji prawdopodobnie zawsze spełni żądanie o mniejszy bufor, o który wnioskuje dequea, co prawdopodobnie spowoduje system operacyjny, aby zabić proces, aby spróbować zdobyć trochę pamięci. Ktokolwiek wybierze, może nie być zbyt przyjemny.
Rozwiązaniem w takim przypadku jest ustawienie flag na poziomie systemu, aby zastąpić optymistyczną alokację (nie zawsze wykonalne) lub nieco bardziej ręczne zarządzanie pamięcią, np. Za pomocą własnego alokatora sprawdzającego użycie pamięci lub podobnych. Oczywiście nie idealny. (Co może odpowiedzieć na twoje pytanie, jak preferować wektor ...)
Wielokrotnie zaimplementowałem zarówno wektor, jak i deque. deque jest znacznie bardziej skomplikowany z punktu widzenia implementacji. Ta komplikacja przekłada się na więcej kodu i bardziej złożony kod. Więc zazwyczaj zobaczysz trafienie rozmiaru kodu, gdy wybierzesz deque zamiast wektora. Możesz również doświadczyć niewielkiego spadku szybkości, jeśli twój kod używa tylko rzeczy, w których wektor się wyróżnia (tj. Push_back).
Jeśli potrzebujesz podwójnej kolejki, deque jest zdecydowanym zwycięzcą. Ale jeśli robisz większość swoich wstawek i wymazań z tyłu, wektor będzie wyraźnym zwycięzcą. Jeśli nie masz pewności, zadeklaruj swój kontener za pomocą typedef (aby łatwo było przełączać się tam iz powrotem) i dokonaj pomiaru.
Pytanie - czy komisja rozważała dodanie hybrydy tych dwóch (powiedzmy „talii”) do C ++? (tj. podwójnie zakończona vector.) Napisałem implementację, do której link znajduje się poniżej w mojej odpowiedzi . Może być równie szybki, vectorale ma znacznie szersze zastosowanie (np. Podczas tworzenia szybkiej kolejki).
user541686
5
std::dequenie ma gwarantowanej pamięci ciągłej - i często jest nieco wolniejszy w przypadku dostępu indeksowanego. Deque jest zwykle implementowany jako „lista wektorów”.
Nie sądzę, aby „lista wektorów” była poprawna: rozumiałem, że większość implementacji była „wektorem wskaźników do tablic”, chociaż zależy to od twojej definicji „listy” („listę” czytam jako „lista połączona , „co nie spełniałoby wymagań dotyczących złożoności.)
James McNellis
2
Według http://www.cplusplus.com/reference/stl/deque/ , „w przeciwieństwie do wektorów, deques nie mają gwarancji posiadania wszystkich swoich elementów w ciągłych lokalizacjach pamięci, co eliminuje możliwość bezpiecznego dostępu za pomocą arytmetyki wskaźnikowej”.
Deques są nieco bardziej skomplikowane, po części dlatego, że niekoniecznie mają ciągły układ pamięci. Jeśli potrzebujesz tej funkcji, nie powinieneś używać deque.
(Wcześniej moja odpowiedź wskazywała na brak standaryzacji (z tego samego źródła, co powyżej, „deques mogą być implementowane przez określone biblioteki na różne sposoby”), ale w rzeczywistości dotyczy to prawie każdego standardowego typu danych bibliotecznych.)
std::dequejest nie mniej znormalizowany niż std::vector. Nie sądzę, aby wymagania dotyczące złożoności std::dequemożna było spełnić dzięki ciągłemu przechowywaniu.
Jerry Coffin
1
Być może moje sformułowanie było słabe: chociaż prawdą jest, że standaryzacja nie jest dokładna, jak rozumiem, wektory są standaryzowane, aby były sekwencją koniugalną, a deki nie. Wydaje się, że jest to jeden decydujący czynnik.
patrickvacek
1
@JerryCoffin: Których wymagań dotyczących złożoności dequenie można spełnić w przypadku ciągłego przechowywania?
user541686
1
@Mehrdad: Szczerze mówiąc, nie pamiętam, co miałem na myśli. Ostatnio nie przyjrzałem się tej części normy na tyle, że czuję się komfortowo stwierdzając kategorycznie, że mój wcześniejszy komentarz był błędny, ale patrząc na to teraz, nie mogę też pomyśleć, jak by to było dobre.
Jerry Coffin
3
@JerryCoffin: Wymagania dotyczące złożoności są w rzeczywistości trywialne: możesz przydzielić dużą tablicę i zacząć przesuwać sekwencję od środka na zewnątrz (wydaje mi się, że tak właśnie działa implementacja Mehrdada), a następnie ponownie przydzielić, gdy dojdziesz do końca. Problem z tym podejściem polega na tym, że nie spełnia ono jednego z wymagań deque, a mianowicie, że wstawienie na końcach nie unieważnia odniesienia do istniejących elementów. To wymaganie oznacza nieciągłą pamięć.
Yakov Galka
0
Deque to kontener sekwencji, który umożliwia swobodny dostęp do swoich elementów, ale nie ma gwarancji ciągłego przechowywania.
Pytanie, czy którekolwiek można wydestylować spośród błędów rzeczowych i brakujących słów, brzmiało, dlaczego ktoś by wolał vector. Możemy wywnioskować, że dlaczego nie jest to następstwem. Stwierdzenie, że wolisz dequez nieznanych powodów od nieokreślonych testów, nie jest odpowiedzią.
Z jednej strony wektor jest dość często po prostu szybszy niż deque. Jeśli w rzeczywistości nie potrzebujesz wszystkich funkcji deque, użyj wektora.
Z drugiej strony, czasami to robisz funkcje, które potrzebują wektor nie daje, w takim przypadku należy użyć deque. Na przykład wzywam każdego, aby spróbował przepisać ten kod bez użycia deque i bez ogromnej zmiany algorytmu.
Faktycznie, na takich samych serii push_backi pop_backoperacji, deque<int>zawsze jest co najmniej 20% szybciej niż vector<int>w moich testów (GCC z O3). Myślę, że dlatego dequejest to standardowy wybór w przypadku std::stack...
igel
-1
Zauważ, że pamięć wektorowa jest ponownie alokowana, gdy tablica rośnie. Jeśli masz wskaźniki do elementów wektorowych, staną się nieprawidłowe.
Ponadto, jeśli usuniesz element, iteratory staną się nieprawidłowe (ale nie „for (auto ...)”).
std::deque
ma bardzo mały maksymalny rozmiar bloku (~ 16 bajtów, jeśli dobrze pamiętam; może 32) i jako taka nie działa zbyt dobrze dla realistycznych aplikacji.deque<T>
Gdziesizeof(T) > 8
(lub 16? To mała liczba) ma o tych samych charakterystyk, jakovector<T*>
, gdzie każdy element jest przydzielane dynamicznie. Inne implementacje mają różne maksymalne rozmiary bloków, więc pisanie kodu, który ma stosunkowo takie same parametry wydajności na różnych platformach, jest trudnedeque
.Odpowiedzi:
Elementy w a nie
deque
są ciągłe w pamięci; elementy są gwarantowane. Więc jeśli potrzebujesz współdziałać ze zwykłą biblioteką C, która potrzebuje ciągłych tablic, lub jeśli zależy Ci (bardzo) na lokalności przestrzennej, możesz preferować . Ponadto, ponieważ istnieje dodatkowa księgowość, inne operacje są prawdopodobnie (nieco) droższe niż ich odpowiedniki . Z drugiej strony, używanie wielu / dużych wystąpień może prowadzić do niepotrzebnej fragmentacji sterty (spowolnienia wywołań ).vector
vector
vector
vector
new
Ponadto, jak wskazano w innym miejscu w StackOverflow , jest więcej dobrej dyskusji tutaj: http://www.gotw.ca/gotw/054.htm .
źródło
Aby poznać różnicę, należy wiedzieć, w jaki sposób
deque
jest ogólnie wdrażany. Pamięć jest przydzielana w blokach o równych rozmiarach i są one połączone razem (jako tablica lub ewentualnie wektor).Aby znaleźć n-ty element, należy znaleźć odpowiedni blok, a następnie uzyskać dostęp do elementu w nim zawartego. Jest to stały czas, ponieważ zawsze są to dokładnie 2 wyszukiwania, ale to wciąż więcej niż wektor.
vector
działa również dobrze z interfejsami API, które wymagają ciągłego bufora, ponieważ są to interfejsy API języka C lub są bardziej wszechstronne pod względem możliwości pobierania wskaźnika i długości. (W ten sposób możesz mieć wektor pod spodem lub zwykłą tablicę i wywołać API z bloku pamięci).Gdzie
deque
ma swoje największe zalety to:Drugi z nich jest mniej znany, ale dla bardzo dużych rozmiarów kolekcji:
Kiedy w przeszłości miałem do czynienia z dużymi kolekcjami i przeniosłem się z modelu ciągłego do modelu blokowego, byliśmy w stanie przechowywać około 5 razy większą kolekcję, zanim skończyła się pamięć w systemie 32-bitowym. Dzieje się tak częściowo dlatego, że podczas ponownego przydzielania w rzeczywistości konieczne było przechowywanie starego bloku, a także nowego, zanim skopiował elementy.
Powiedziawszy to wszystko, możesz mieć kłopoty z
std::deque
systemami używającymi „optymistycznej” alokacji pamięci. Chociaż jego próby zażądania dużego rozmiaru bufora w celu ponownej alokacjivector
testamentu prawdopodobnie zostaną w pewnym momencie odrzucone przez abad_alloc
, optymistyczny charakter osoby dokonującej alokacji prawdopodobnie zawsze spełni żądanie o mniejszy bufor, o który wnioskujedeque
a, co prawdopodobnie spowoduje system operacyjny, aby zabić proces, aby spróbować zdobyć trochę pamięci. Ktokolwiek wybierze, może nie być zbyt przyjemny.Rozwiązaniem w takim przypadku jest ustawienie flag na poziomie systemu, aby zastąpić optymistyczną alokację (nie zawsze wykonalne) lub nieco bardziej ręczne zarządzanie pamięcią, np. Za pomocą własnego alokatora sprawdzającego użycie pamięci lub podobnych. Oczywiście nie idealny. (Co może odpowiedzieć na twoje pytanie, jak preferować wektor ...)
źródło
Wielokrotnie zaimplementowałem zarówno wektor, jak i deque. deque jest znacznie bardziej skomplikowany z punktu widzenia implementacji. Ta komplikacja przekłada się na więcej kodu i bardziej złożony kod. Więc zazwyczaj zobaczysz trafienie rozmiaru kodu, gdy wybierzesz deque zamiast wektora. Możesz również doświadczyć niewielkiego spadku szybkości, jeśli twój kod używa tylko rzeczy, w których wektor się wyróżnia (tj. Push_back).
Jeśli potrzebujesz podwójnej kolejki, deque jest zdecydowanym zwycięzcą. Ale jeśli robisz większość swoich wstawek i wymazań z tyłu, wektor będzie wyraźnym zwycięzcą. Jeśli nie masz pewności, zadeklaruj swój kontener za pomocą typedef (aby łatwo było przełączać się tam iz powrotem) i dokonaj pomiaru.
źródło
vector
.) Napisałem implementację, do której link znajduje się poniżej w mojej odpowiedzi . Może być równie szybki,vector
ale ma znacznie szersze zastosowanie (np. Podczas tworzenia szybkiej kolejki).std::deque
nie ma gwarantowanej pamięci ciągłej - i często jest nieco wolniejszy w przypadku dostępu indeksowanego. Deque jest zwykle implementowany jako „lista wektorów”.źródło
Według http://www.cplusplus.com/reference/stl/deque/ , „w przeciwieństwie do wektorów, deques nie mają gwarancji posiadania wszystkich swoich elementów w ciągłych lokalizacjach pamięci, co eliminuje możliwość bezpiecznego dostępu za pomocą arytmetyki wskaźnikowej”.
Deques są nieco bardziej skomplikowane, po części dlatego, że niekoniecznie mają ciągły układ pamięci. Jeśli potrzebujesz tej funkcji, nie powinieneś używać deque.
(Wcześniej moja odpowiedź wskazywała na brak standaryzacji (z tego samego źródła, co powyżej, „deques mogą być implementowane przez określone biblioteki na różne sposoby”), ale w rzeczywistości dotyczy to prawie każdego standardowego typu danych bibliotecznych.)
źródło
std::deque
jest nie mniej znormalizowany niżstd::vector
. Nie sądzę, aby wymagania dotyczące złożonościstd::deque
można było spełnić dzięki ciągłemu przechowywaniu.deque
nie można spełnić w przypadku ciągłego przechowywania?deque
, a mianowicie, że wstawienie na końcach nie unieważnia odniesienia do istniejących elementów. To wymaganie oznacza nieciągłą pamięć.Deque to kontener sekwencji, który umożliwia swobodny dostęp do swoich elementów, ale nie ma gwarancji ciągłego przechowywania.
źródło
Myślę, że to dobry pomysł, aby wykonać test wydajności w każdym przypadku. I podejmij decyzję opierając się na tych testach.
Wolałbym
std::deque
niżstd::vector
w większości przypadków.źródło
vector
. Możemy wywnioskować, że dlaczego nie jest to następstwem. Stwierdzenie, że woliszdeque
z nieznanych powodów od nieokreślonych testów, nie jest odpowiedzią.Nie wolałbyś, aby wektor był deque zgodnie z tymi wynikami testu (ze źródłem).
Oczywiście powinieneś przetestować swoją aplikację / środowisko, ale podsumowując:
Jeszcze kilka rozważań i uwaga do rozważenia round_buffer.
źródło
Z jednej strony wektor jest dość często po prostu szybszy niż deque. Jeśli w rzeczywistości nie potrzebujesz wszystkich funkcji deque, użyj wektora.
Z drugiej strony, czasami to robisz funkcje, które potrzebują wektor nie daje, w takim przypadku należy użyć deque. Na przykład wzywam każdego, aby spróbował przepisać ten kod bez użycia deque i bez ogromnej zmiany algorytmu.
źródło
push_back
ipop_back
operacji,deque<int>
zawsze jest co najmniej 20% szybciej niżvector<int>
w moich testów (GCC z O3). Myślę, że dlategodeque
jest to standardowy wybór w przypadkustd::stack
...Zauważ, że pamięć wektorowa jest ponownie alokowana, gdy tablica rośnie. Jeśli masz wskaźniki do elementów wektorowych, staną się nieprawidłowe.
Ponadto, jeśli usuniesz element, iteratory staną się nieprawidłowe (ale nie „for (auto ...)”).
Edycja: zmieniono „deque” na „vector”
źródło