C ++ 'da listeleri vektörler üzerinde kullanmanın amacı nedir?


32

C ++ listelerini ve vektörlerini içeren 3 farklı deney yaptım.

Vektörleri olanlar, ortada çok fazla ekleme yapılsa bile daha etkili olduğunu ispatladılar.

Dolayısıyla soru: hangi durumda listeler vektörlerden daha anlamlı?

Vektörler çoğu durumda daha verimli görünüyorsa ve üyelerinin ne kadar benzer olduğunu düşünürsek, listeler için hangi avantajlar kalır?

  1. N tamsayıları oluşturun ve bunları bir kap içine koyun, böylece kap sıralı kalır. Ekleme, elemanların birer birer okunması ve yenisinin ilk büyüğünden hemen önce yerleştirilmesiyle saf bir şekilde gerçekleştirilmiştir.
    Bir liste ile, boyutlar arttıkça vektörlerle karşılaştırıldığında zaman çatıdan geçer.

  2. Kabın sonuna N tamsayıları yerleştirin.
    Listeler ve vektörler için, zaman, vektörlerle 3 kat daha hızlı olmasına rağmen, aynı büyüklük sırasına göre artmıştır.

  3. N tamsayısını bir kaba yerleştirin.
    Zamanlayıcıyı başlat.
    Listeleri için list.sort kullanarak kabı sıralayın ve vektörler için std :: sort. Zamanlayıcıyı durdur.
    Yine, zaman aynı büyüklük sırasına göre artar, ancak vektörlerde ortalama 5 kat daha hızlıdır.

Testler yapmaya devam edebilir ve listelerin daha iyi olacağı birkaç örnek bulabilirim.

Ancak bu mesajı okuduğunuzda ortak deneyimler daha verimli cevaplar verebilir.

Listelerin daha kolay kullanılabildiği veya daha iyi performans gösterdiği durumlar ile karşılaşmış olabilirsiniz?



1
İşte konuyla ilgili başka bir iyi kaynak: stackoverflow.com/a/2209564/8360 ayrıca, duyduğum C ++ rehberinin çoğu varsayılan olarak vektör kullanmak, yalnızca belirli bir nedeniniz varsa listelemek.
Zachary Yates

Teşekkür ederim. Ancak en sevdiğim cevapta söylenenlerin çoğuna katılmıyorum. Bu önyargılı fikirlerin çoğu deneylerim tarafından geçersiz kılındı. Bu kişi herhangi bir test yapmadı ve kitaplarda veya okulda öğretilen yaygın teoriyi uyguladı.
Marek Stanley

1
Çok listsayıda öğeyi kaldırıyorsanız, muhtemelen daha iyi olur. Bir vectorvektörün tüm vektör silinene kadar sisteme bir bellek geri döneceğine inanmıyorum . Ayrıca, 1 numaralı testinizin yalnızca ekleme zamanını test etmediğini unutmayın. Arama ve ekleme birleştiren bir testtir. Yavaşça nereye yerleştirileceği yeri bulmak list. Gerçek ek vektörden daha hızlı olacaktır.
Robotu

3
O kadar tipiktir ki, bu soru (çalışma süresi) performansı, performansı ve sadece performans olarak tanımlanır. Bu, pek çok programcının kör bir noktası gibi gözüküyor - bu konuya odaklanıyorlar ve çoğu zaman çok, çok daha önemli olan düzinelerce başka yönlerin olduğunu unutuyorlar.
Doktor Brown

Yanıtlar:


34

Kısa cevap, davaların az ve çok uzak göründüğü. Muhtemelen birkaç tane var.

Bunlardan biri, az sayıda büyük nesneyi, özellikle de birkaç tanesi için bile alan ayırmanın pratik olmadığı o kadar büyük nesneleri saklamanız gerektiğinde olacaktır. Temel olarak, bir vektör veya dizinin ekstra nesnelere alan tahsis etmesini durdurmanın bir yolu yoktur - nasıl tanımlandıkları (yani karmaşıklık gereksinimlerini karşılamak için ekstra alan tahsis etmeleri gerekir ). Eğer düz dışarı Eğer olamaz bu ekstra boşluk tahsis edilecek izin, bir std::listolabilir sadece ihtiyaçlarınızı karşılayan standart konteyner.

Bir diğeri, bir yineleyiciyi uzun bir süre bir listede "ilginç" bir noktaya kaydederseniz / ekler ve / veya silmeler yaptığınızda, (neredeyse) her zaman bunu yaptığınız bir noktadan yaparsınız. zaten bir yineleyiciniz olduğundan, ekleme veya silme işlemini gerçekleştireceğiniz noktaya ulaşmak için listede dolaşmazsınız. Açıkçası aynı şey, birden fazla nokta ile çalışıyorsanız, ancak yine de üzerinde çalışacağınız her yere bir yineleyici kaydetmeyi planlıyorsanız, doğrudan ulaşabileceğiniz noktaları manipüle edersiniz ve listeye ulaşmak için nadiren dolaşırsınız. bu noktalara.

İlk örnek için, bir web tarayıcı düşünün. TabHer sekme nesnesi tarayıcıda açık sekmede temsil edildiğinde, bağlantılı bir nesne listesi tutabilir . Her sekme birkaç düzine megabayt veri olabilir (özellikle video gibi bir şey varsa). Tipik açık sekmelerinizin sayısı kolayca bir düzineden az olabilir ve 100 muhtemelen üst ekstremiteye yakındır.

İkincisinin bir örneği için, metni, her biri bağlantılı bir (örneğin) paragraf listesi içeren bir bağlantılı bölümler listesi olarak saklayan bir kelime işlemcisini düşünün. Kullanıcı düzenleme yaparken, genellikle düzenleme yapacakları belirli bir yeri bulacaklar ve daha sonra o noktada (ya da yine de bu paragrafın içinde) makul miktarda çalışma yapacaklar. Evet, şimdi ve tekrar tekrar bir paragraftan diğerine geçecekler, ancak çoğu durumda, çalıştıkları yerin yakınında bir paragraf olacak.

Arada sırada (global arama ve değiştirme gibi şeyler), tüm listelerdeki tüm öğeleri gözden geçiriyorsunuz, ancak bu oldukça nadir bir durum. Liste, listeyi geçme zamanının neredeyse eşitsiz olduğunu gösterir.

Tipik bir durumda, bunun da ilk kritere uyması muhtemeldir - bir bölüm , her biri oldukça büyük olması muhtemel oldukça az sayıda paragraf içerir (en azından işaretçilerin büyüklüğüne göre düğüm vb. Aynı şekilde, her biri birkaç kilobayt olabilen nispeten az sayıda bölüm vardır.

Bununla birlikte, bu örneklerin her ikisinin de muhtemelen biraz tartışmalı olduğunu ve bağlantılı bir listenin her ikisi için de mükemmel çalışabileceğini itiraf etmeliyim ki, muhtemelen her iki durumda da büyük bir avantaj sağlamayacaktır. Her iki durumda da, örneğin, bazı (boş) web sayfaları / sekmeler veya bazı boş bölümler için bir vektöre fazladan boşluk ayırmak, gerçek bir sorun olabilir.


4
+1, ancak: İlk vaka, her zaman büyük nesnelerle kullanmanız gereken işaretçiler kullandığınızda kaybolur. Bağlantılı listeler de ikinci örnek için uygun değildir; Diziler , bu kadar kısa olduklarında tüm işlemler için kendilerine aittir.
amara

2
Büyük nesne durumu hiç çalışmıyor. İşaretçilerden std::vectorbirinin kullanılması , tüm bu bağlantılı liste düğümü nesnelerinden daha verimli olacaktır.
Winston Ewert

Bağlantılı listeler için birçok kullanım vardır - bunlar sadece dinamik diziler kadar yaygın değildir. Bir LRU önbelleği, bağlantılı bir listenin yaygın bir kullanımıdır.
Charles Salvia

Ayrıca, std::vector<std::unique_ptr<T>>iyi bir alternatif olabilir.
Deduplicator

24

Bjarne Stroustrup'un kendisine göre, vektörler her zaman veri dizileri için varsayılan koleksiyon olmalıdır. Öğelerin eklenmesi ve silinmesi için optimizasyon yapmak istiyorsanız listeyi seçebilirsiniz, ancak normalde yapmamalısınız. Listenin maliyeti yavaş geçiş ve hafıza kullanımıdır.

Bu sunumda bundan bahsediyor .

Yaklaşık 0:44'te genel olarak vektörler ve listelerden bahsediyor.

Kompaktlık önemlidir. Vektörler listelerden daha küçüktür. Ve öngörülebilir kullanım şekilleri çok önemlidir. Vektörlerle çok fazla öğeye dokunmanız gerekir, ancak önbellekleri gerçekten çok iyi. ... Listelerde rasgele erişim yok. Ancak bir listeyi dolaştırdığınızda, rasgele erişime devam edersiniz. Burada bir düğüm var ve hafızadaki o düğüme gidiyor. Yani hafızanıza erişimde rastgele bir durum sergiliyorsunuz ve önbellek sıranızı en üst düzeye çıkarıyorsunuz, ki bu tam olarak istediğiniz şeyin tam tersi.

Yaklaşık 1: 08'de, bu konu hakkında kendisine bir soru verildi.

Görmemiz gereken, bir dizi elemente ihtiyacımız olduğunu. Ve C ++ 'daki öğelerin varsayılan dizisi vektördür. Şimdi, çünkü bu kompakt ve verimli. Uygulama, donanıma haritalama, konular. Şimdi, ekleme ve silme için optimize etmek istiyorsanız - der ki 'iyi, bir dizinin varsayılan sürümünü istemiyorum. Uzman olanı istiyorum, ki bu liste. ' Ve bunu yaparsanız, 'yavaş geçişler ve daha fazla bellek kullanımı gibi bazı maliyetleri ve bazı sorunları kabul ediyorum' diyecek kadar bilginiz olmalıdır.


1
Kısaca , "yaklaşık 0:44 ve 01:08" de bağladığınız sunumda söylenenleri yazmayı düşünür müsünüz?
gnat

2
@gnat - kesinlikle. Ayrı olarak anlamlı olan ve slaytların bağlamına ihtiyaç duyan şeyleri alıntılamaya çalıştım.
Pete

11

Genelde listeleri kullandığım tek yer, öğeleri silmem ve yinelemeleri geçersiz kılmam gereken yer. std::vectorekleme ve silme işleminde tüm yineleyicileri geçersiz kılar. std::listekleme veya silme işleminden sonra yineleyicilerin mevcut öğelere yönelik geçerli olduğunu garanti eder


4

Daha önce verilmiş olan diğer cevaplara ek olarak, listeler vektörlerde bulunmayan bazı özelliklere sahiptir (çünkü delice pahalı olurlardı.) Ekleme ve birleştirme işlemleri en önemlisidir. Sık sık eklenmesi veya birleştirilmesi gereken bir sürü listeniz varsa, bir liste muhtemelen iyi bir seçimdir.

Ancak bu işlemleri yapmanız gerekmiyorsa, o zaman muhtemelen değil.


3

Bağlantılı listelerin doğal önbellek / sayfa dostu olmayışı, neredeyse tamamen birçok C ++ geliştiricisi tarafından reddedilme eğilimindedir ve bu varsayılan biçimde iyi bir gerekçe gösterir.

Bağlantılı Listeler Hala Harika Olabilir

Bununla birlikte, bağlantılı listeler, doğal olarak sahip olmadıkları mekânsal bölgeleri geri veren sabit bir tahsisatçı tarafından desteklendiğinde harika olabilir .

Örneğin, bir listeyi iki listeye ayırabiliriz, örneğin, sadece yeni bir işaretçiyi depolayarak ve bir veya iki işaretçiyi işleyerek. İşaretçi manipülasyonuyla düğümleri sabit bir zamanda bir listeden diğerine taşıyabiliriz ve boş bir liste tek bir headişaretçinin hafıza maliyetine sahip olabilir .

Basit Izgara Hızlandırıcı

Pratik bir örnek olarak, 2D görsel simülasyonu düşünün. Her karede hareket eden milyonlarca parçacık arasında çarpışma algılaması gibi şeyleri hızlandırmak için kullanılan 400x400 (160.000 ızgara hücresi) alan bir haritaya sahip kaydırma ekranına sahiptir (dörtlü ağaçlardan kaçınıyoruz. dinamik veri). Bir sürü parçacık sürekli olarak her karede hareket eder, bu da bir ızgara hücresinde bir başkasına sürekli olarak devam etmeleri anlamına gelir.

Bu durumda, eğer her bir parçacık tek bir bağlı liste düğümü ise, her bir ızgara hücresi sadece headişaret eden bir işaretçi olarak başlayabilir nullptr. Yeni bir parçacık doğduğunda, sadece headbu hücrenin imlecini bu parçacık düğümüne işaret edecek şekilde bulunduğu ızgara hücresine koyarız . Bir parçacık bir ızgara hücresinden diğerine geçerken, işaretçileri değiştiririz.

Bu, vectorsher bir ızgara hücresi için 160.000 depolamaktan ve her zaman kare kare bazda geri çekip ortadan silmekten çok daha etkili olabilir .

std :: liste

Bu olsa da, el ile haddelenmiş, müdahaleci, tek başına bağlı listeler olsa da sabit bir ayırıcı tarafından destekleniyor. std::listBir çift-bağlantılı liste temsil eder ve, ayrıca içinde özel ayırma uygulamaya tür bir ağrı olduğu zaman, tek bir işaretçi (satıcı uygulamasına göre değişir) boş kompakt yaklaşık olmayabilir std::allocatorformu.

İtiraf etmeliyim ki, asla kullanmam list. Ancak bağlantılı listeler hala harika olabilir! Yine de, insanların bunları kullanma eğiliminde olmaları nedeniyle harika değiller ve zorunlu sayfa hatalarını ve önbellek eksikliklerinin çoğunu azaltan çok etkili bir sabit ayırıcı tarafından desteklenmedikleri sürece çok da harika değiller.


1
C ++ 11'den beri standart olarak bağlanmış bir liste var std::forward_list.
sharyex

2

Kaptaki elemanların boyutunu dikkate almalısınız.

int Öğeler vektörü, verilerin çoğu CPU önbelleğine sığdığı için çok hızlıdır (ve SIMD talimatları muhtemelen veri kopyalama için kullanılabilir).

Elemanın boyutu daha büyükse, test 1 ve 3'ün sonucu önemli ölçüde değişebilir.

Bir itibaren çok kapsamlı performans karşılaştırmasını :

Bu, her veri yapısının kullanımı hakkında basit sonuçlar çıkarmaktadır:

  • Numara çatırtı: kullanın std::vectorveyastd::deque
  • Doğrusal arama: kullanın std::vectorveyastd::deque
  • Rastgele Ekle / Kaldır:
    • Küçük veri boyutu: kullanım std::vector
    • Büyük eleman boyutu: kullanın std::list(arama için esas olarak tasarlanmadıysa)
  • Önemsiz veri türü: std::listözellikle arama yapmak için kaba gerekmedikçe kullanın . Ancak kabın çoklu modifikasyonları için çok yavaş olacaktır.
  • Öne itin: kullanın std::dequeveyastd::list

(bir not std::dequeolarak çok az tahmin edilmiş bir veri yapısıdır).

Kolaylık açısından bakıldığında std::list, diğer elemanları takıp çıkarırken yineleyicilerin hiçbir zaman geçersiz hale gelmediği garantisini verir. Bu genellikle kilit bir özelliktir.


2

Bence listeleri kullanmanın en belirgin nedeni yineleyicinin geçersiz kılınmasıdır : bir vektöre eleman ekler / kaldırırsanız, tüm vektörler, referanslar, bu vektörün belirli elemanlarına tuttuğunuz yineleyiciler geçersiz olabilir ve ince hatalara yol açabilir. veya segmentasyon hataları.

Bu listelerde durum böyle değil.

Tüm standart kaplar için kesin kurallar bu StackOverflow yazısında verilmiştir .


0

Kısacası, kullanmak için iyi bir neden yoktur std::list<>:

  • Sıralanmamış bir kaba ihtiyacınız olursa, std::vector<>kurallar.
    (Elemanları vektörün son elemanı ile değiştirerek silin.)

  • Sıralanmış bir kaba ihtiyacınız varsa, std::vector<shared_ptr<>>kurallar.

  • Seyrek bir endekse ihtiyacınız varsa, std::unordered_map<>kurallar.

Bu kadar.

Bağlantılı bir liste kullanma eğiliminde olduğum tek bir durum olduğunu görüyorum: Bazı ek uygulama mantığı uygulamak için bir şekilde bağlanması gereken önceden var olan nesnelerim olduğunda. Ancak, bu durumda hiçbir zaman kullanmıyorum std::list<>, bunun yerine nesnenin içindeki (akıllı) bir sonraki işaretçiye başvuruyorum, çünkü çoğu kullanım durumu doğrusal bir listeden ziyade bir ağaçla sonuçlanıyor. Bazı durumlarda, ortaya çıkan yapı bağlantılı bir listedir, diğerlerinde ise bir ağaç veya yönlendirilmiş bir asiklik grafiktir. Bu işaretçilerin temel amacı, asla nesneleri yönetmek için her zaman mantıksal bir yapı oluşturmaktır. Bunun için bizde var std::vector<>.


-1

İlk testinizde ekleri nasıl yaptığınızı göstermeniz gerekiyor. İkinci ve üçüncü sınavlarınızda vektör kolayca kazanacak.

Listelerin önemli bir kullanımı, yineleme yaparken öğelerin kaldırılmasını desteklemeniz gerektiğidir. Vektör değiştirildiğinde, tüm yineleyiciler geçersiz (potansiyel olarak). Bir listeyle, kaldırılan öğeye yalnızca bir yineleyici geçersiz. Diğer tüm yineleyiciler geçerli kalır.

Kaplar için tipik kullanım sırası vektör, deque, sonra listedir. Konteyner seçimi genellikle push_back select vektörüne, pop_front select deque'e, seçim listesine ekle'ye dayanır.


3
yineleme yaparken öğeleri kaldırırken, genellikle bir vektör kullanmak ve sonuçlar için yeni bir vektör oluşturmak daha iyidir
amara

-1

Aklıma gelen bir faktör, bir vektör büyüdükçe, vektör hafızasını serbest bıraktığı ve tekrar tekrar daha büyük bir blok tahsis ettiği için boş hafızanın parçalanacağıdır. Bu listelerde bir sorun olmayacak.

Buna ek olarak, push_backrezerve edilmemiş çok sayıda s'nin her yeniden boyutlandırma sırasında bir kopyasına neden olacağı gerçeğine ek olarak, verimsiz hale gelir. Ortaya yerleştirmek benzer şekilde tüm elemanların sağa doğru hareket etmesine neden olur ve daha da kötüdür.

Bunun önemli bir sorun olup olmadığını bilmiyorum, ama işimde (mobil oyun geliştirme) vektörlerden kaçınmak için bana verilen sebep buydu.


1
hayır, vektör kopyalar ve bu pahalıdır. Ancak bağlantılı listenin üzerinden geçmek (nereye ekleneceğini bulmak için) de pahalıdır. Anahtar gerçekten ölçmek için
Kate Gregory

@KateGregory Buna ek olarak demek istedim, Buna göre düzenleyeyim
Karthik T

3
Doğru, ama inan ya da inanma (ve çoğu insan buna inanmaz) bahsetmediğiniz maliyette, bu kopyaları OUTWEIGHS nereye yerleştireceğini bulmak için bağlantılı listeyi dolaşır (özellikle de öğeler küçükse (ya da taşınabilirse, onlar hareket ederler)) ve vektör genellikle (ya da genellikle) daha hızlıdır. İnan ya da inanma.
Kate Gregory
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.