Länkade listor eller arrayer, rent generellt? (C-programmering)

Permalänk
Medlem

Länkade listor eller arrayer, rent generellt? (C-programmering)

Är det så enkelt som att:
- Är mängden data/antal element odefinerad, använd en länkad lista.
- Är mängden data/antal element känd, använd en array.

Eller bör man tänka annorlunda?
Hur ser det i så fall ut i fallet om jag vet att det kommer vara max x antal element i listan? Dvs, antalet är odefinerat men det finns ändå ett känt max.

Att dynamiskt allokera minne antar jag tar längre tid att göra fram och tillbaka än att ha de fasta minnesplatserna, men tar det så lång tid att det i dagsläget är värt att bry sig om?

EDIT: Antar att detta egentligen inte bara gäller C-programmering, utan säkert är generellt för alla språk där minnesallokeringen fungerar på samma sätt.

Permalänk
Medlem

Det beror ju hur du ska accessa data o.s.v. men generellt ger arrayer bättre prestanda.

Om det maximala antalet inte är väldigt stort kan du helt enkelt allokera minne för det maximala antalet element. Ett alternativ är att allokera plats för ett visst antal element och om du når eller börjar närma dig det antalet så omallokerar du minnet med realloc eller liknande.

Visa signatur

Assembly är ett högnivåspråk.

Permalänk
Medlem

"Rent generellt" spelar det ingen större roll. Ta eventuella problem med prestanda när de kommer, och använd det du tycker är bekvämast till dess.

Permalänk
Medlem

Länkade listor ger i de allra, allra, allra flesta fall sämre prestanda, om du inte är helt säker på (genom prestandatester) att en länkad lista är snabbare så är det bättre med en array. En länkad lista kan vara bättre (nåja, åtminstone inte mycket långsammare i alla fall) om du något har väldigt specifikt användningsmönster, exempelvis att du vill lagra element i en speciell ordning samt inte läsa av dessa element förrän prestanda inte spelar någon roll och du dessutom har förallokerat allt minne i en pool så att du slipper den kostnaden.

Anledningen till detta grundar sig iatt det är fruktansvärt hög fördröjning att läsa ram-minnet i förhållande till processorns klockfrekvens. Man brukar räkna med att det tar ungefär 300-400 klockcykler att hämta någonting från ram-minnet. Därför har processorer en eller flera nivåer av caches, moderna processorer har oftast 3 nivåer, där data kan lagras för att kunna användas snabbare. Dessa caches är mycket små, den första nivån brukar vara i storleksordningen (beror på modell av CPU, dessa siffror är mycket grova, ta dem med en nypa salt) 32kB, nästa nivå 256kB och sista nivån 4MB. En del av detta utrymme är delat mellan kärnor och andra har eget cache. Flera olika nivåer har man för att större minne innebär högre latency.

När data läses in i cachen görs detta i hela s.k. cache-lines. Det gör att det blir lättare för CPUn att hålla reda på vilken data som är cachead samt så gillar minnet att läsa mycket data samtidigt. Storleken på en cache-line beror på vilken nivå av cachen som den lagras i.

En CPU har en enhet som heter prefetcher, denna studerar (enkla) mönster för minnesåtkomst och försöker läsa in data till cachen innan programmet efterfrågar datan. Denna prefetcher kan tydligt se sekventiell minnesåtkomst och förutse denna mycket bra så att påverkan av fördröjningen mot minnet blir minimal. Om man läser från slumpmässiga ställen av minnet kan inte prefetchern läsa in datan i förväg.

När man då itererar genom en länkad lista måste datorn jaga reda på en ny minnesplats för varje element i listan, har man otur får man väldigt många cache missar som går ändå till ramminnet vilket innebär 400 klockcykler per miss. Även om man träffar relativt mycket i den tredje nivån av cacheminnet kan man räkna med ca 50 klockcykler i fördröjning per minnesaccess (första nivån av cachen har ca 2-5 klockcykler fördröjning, andra nivån ~20 klockcykler). Beroende på hur stora ens element är kanske de inte ens tar upp en hel cache-line vilket dessutom innebär att man läser in en massa minne i onödan.

En array å andra sidan ligger som en rad i minnet och kan itereras igenom i full fart. I värsta fall får man en cache-miss på första elementet, sedan kommer CPUn prefetcher att redan ha laddat så mycket som möjligt av arrayen till cache och det tar bara ett par klockcykler per element att hämta datan.

Följande klipp handlar om C++ vector vs list, vector är "bara" en array som kan växa genom att allokera om sig, och har testat det hela lite: http://www.youtube.com/watch?v=YQs6IC-vgmo

Notera att samtliga siffror i detta inlägget bara är ungefärliga och från mitt huvud, det är tänkt att de bara ska visa på storleksordningen, inte ett exakt värde. De beror dessutom mycket på vilken hårdvara man kör på.

Permalänk
Medlem

En bra sammanfattning kan läsas på: http://kjellkod.wordpress.com/2012/02/25/why-you-should-never...

Det finns fall där en länkad lista kan vara till nytta men precis som DanielL skriver är risken stor att man får fragmenterat minne och leder till många s.k. cache-missar. Ett sätt att lösa detta på är att allokera en minnes-arena (tänk det som att du i förväg allokerar en stor pool med data som du inte använder allt av på en gång) Du är (oftast) garanterad sekventiellt åtkomstbart minne då. Vilket gör att du kan skapa din länkade lista genom att _själv_ hålla koll på pekarna och se till att använda pekare till minne som är i din arena. Problem uppstår då arenan blir full ==> måste tänka igenom hur du ska utöka arenan etc.

Visa signatur

weeeee

Permalänk
Medlem

Medans DanielL har helt rätt när det gäller data locality och array så är det oftast två andra aspekter som avgör preferensen.

I en array är O(1) "snabb" på att hämta data (förenklat så går det lika snabbt att hämta ett element i listan).
I en länkad lista så är en hämtning O(n). Man måste stega igenom från första (eller sista) elementet igenom tills man kommit till elementet man vill ha.

Så ska du mest läsa från strukturen och då från godtyckliga positioner så är arrayen att föredra.
Håller du på med en lista där du kommer ta bort och lägga till många element på olika platser i listan kan den länkade listan vara att föredra då man inte behöver justera om hela listan när man modifierar den.

Sedan är ju allt relativt till hur stor roll prestandan verkligen spelar roll i relation till hur mycket komplexitet koden orsakar för dig

//C

Permalänk
Medlem
Skrivet av conio:

Medans DanielL har helt rätt när det gäller data locality och array så är det oftast två andra aspekter som avgör preferensen.

I en array är O(1) "snabb" på att hämta data (förenklat så går det lika snabbt att hämta ett element i listan).
I en länkad lista så är en hämtning O(n). Man måste stega igenom från första (eller sista) elementet igenom tills man kommit till elementet man vill ha.

Så ska du mest läsa från strukturen och då från godtyckliga positioner så är arrayen att föredra.
Håller du på med en lista där du kommer ta bort och lägga till många element på olika platser i listan kan den länkade listan vara att föredra då man inte behöver justera om hela listan när man modifierar den.

Sedan är ju allt relativt till hur stor roll prestandan verkligen spelar roll i relation till hur mycket komplexitet koden orsakar för dig

//C

Länkade listor passar även bättre som en omutbar datastruktur av samma anledning. Man behöver ofta inte kopiera hela listan för att skapa en ny lista med ett extra element på godtycklig plats. Att lägga till ett element i början av en omutbar länkad lista är en O(1)-operation, medan en array behöver kopieras om till en ny array med längd n+1

Visa signatur

Kom-pa-TI-bilitet