Bağlantılı listeler için nadiren bir kuyruk işaretçisi kullanıyorum ve yığın ve benzer bir ekleme / çıkarma deseninin (veya yalnızca ortadan doğrusal zamanla çıkarmanın) yeterli olduğu yerlerde, tekli bağlantılı listeleri daha sık kullanma eğilimindeyim. Çünkü yaygın kullanım durumlarımda, kuyruk işaretçisi listesinin çift bağlantılı bir listeye dönüştürülmesi pahalı olduğu için aslında pahalı.
Çoğunlukla tekil bağlantılı bir liste için genel vaka kullanımım, her biri yalnızca birkaç liste düğümü içeren yüz binlerce bağlantılı liste saklayabilir. Ayrıca genellikle bağlantılı listeler için işaretçiler kullanmıyorum. Endeksleri 32 bit olabileceği için bir dizinin içine indeksler kullanıyorum, örneğin 64 bitlik bir işaretçinin yarısını alan. Ayrıca genellikle liste düğümlerini birer birer ayırmıyorum ve bunun yerine, yine, tüm düğümleri saklamak için büyük bir dizi kullanın ve daha sonra düğümleri birbirine bağlamak için 32 bit indeksleri kullanın.
Örnek olarak, çarpışma algılamayı hızlandırmak için hareket eden ve birbirinden seken bir milyon parçacığı bölmek için 400x400 ızgara kullanan bir video oyunu düşünün. Bu durumda, depolamanın oldukça verimli bir yolu, 160.000 tek bağlantılı listeyi depolamaktır, bu da benim durumumda 160.000 32 bit tam sayıya (~ 640 kilobayt) ve parçacık başına bir 32 bit tam sayı ek yüküne dönüşür. Parçacıklar ekranda hareket ettikçe, tek yapmamız gereken bir parçacığı bir hücreden diğerine taşımak için birkaç 32 bit tamsayıyı güncellemektir:
... next
ya bir parçacık düğümünün indeksi ("işaretçisi"), ya hücredeki bir sonraki parçacığa ya da parçacığın öldüğü takdirde geri kazanılacak bir sonraki serbest parçacığa bir indeks görevi görür (temel olarak indeksleri kullanarak bir serbest liste ayırıcı uygulaması):
Hücreden doğrusal zamanın çıkarılması aslında bir ek yük değildir, çünkü parçacık mantığını bir hücredeki parçacıklar arasında yineleyerek işliyoruz, bu nedenle çift bağlantılı bir liste sadece yararlı olmayan bir tür ek yükü ekleyecektir. benim durumumda bir kuyruk gibi bana da faydası olmazdı.
Bir kuyruk işaretçisi, ızgaranın bellek kullanımını iki katına çıkaracak ve önbellek kaçırma sayısını artıracaktır. Ayrıca, dalsız olmak yerine listenin boş olup olmadığını denetlemek için bir dal gerektirmesi için ekleme yapılması gerekir. Çift bağlantılı bir liste yapmak, her parçacığın liste ek yükünü iki katına çıkarır. Bağlantılı listeleri kullandığım zamanın% 90'ı, bu gibi durumlar için ve bu yüzden bir kuyruk işaretçisinin depolanması nispeten pahalı olurdu.
Yani 4-8 bayt, aslında bağlantılı listeleri ilk başta kullandığım bağlamların çoğunda önemsiz değil. Sadece bir çip yükü depolamak için bir veri yapısı kullanıyorsanız, o zaman 4-8 bayt her zaman önemsiz olmayabilir. Ben aslında patlayıcı bellek kullanımı (genellikle bir işaretçi artı en az ızgara hücre başına iki tamsayı) olacak ızgara için büyüyen 160.000 dinamik diziler depolamak yerine gerekli bellek bellek sayısını ve bellek miktarını azaltmak için bağlantılı listeleri kullanın hücre başına tek başına bir tamsayı ve hücre başına sıfır yığın tahsisi yerine ızgara hücresi başına yığın atamaları).
LL'lerin genel yakınlık eksikliğinden dolayı bu durumlarda genellikle kötü bir seçim olduğu durumlarda, çoğu zaman ön / orta kaldırma ve ön / orta yerleştirme ile ilişkili sabit zamanlı karmaşıklıkları için bağlantılı listelere ulaşan birçok insan bulurum. LL'lerin performans açısından bana göre güzel olduğu yerlerde, sadece birkaç noktayı manipüle ederek ve değişken boyutlu bir bellek ayırıcısı olmadan değişken boyutlu bir veri yapısına ulaşarak bir öğeyi bir listeden diğerine taşıma yeteneği (çünkü her bir düğümün tek biçimli bir boyutu vardır, ücretsiz listeleri kullanabiliriz, örneğin). Her liste düğümü genel amaçlı bir ayırıcıya karşı ayrı ayrı tahsis ediliyorsa, bu genellikle bağlantılı listelerin alternatiflere göre çok daha kötü olduğunu ve
Bunun yerine, bağlantılı listelerin basit alternatiflere göre çok etkili bir optimizasyon görevi gördüğü çoğu durumda, en kullanışlı formlar genellikle tekli bağlantılıdır, sadece bir baş işaretçisine ihtiyaç duyar ve başına genel amaçlı bellek tahsisi gerektirmez Bunun yerine genellikle düğüm başına zaten tahsis edilmiş belleği bir araya getirebilir (önceden tahsis edilmiş büyük bir diziden, örneğin). Ayrıca her SLL, genellikle bir grafik düğümüne bağlı kenarlar gibi çok az sayıda öğeyi depolar (tek bir büyük bağlantılı listenin aksine birçok küçük bağlantılı liste).
Bu günlerde bir DRAM tekne yükümüz olduğunu da unutmamak gerekir, ancak bu, ikinci en yavaş bellek türüdür. 64 bayt önbellek satırlarına sahip L1 önbellek söz konusu olduğunda hala çekirdek başına 64 KB gibi bir şeydeyiz. Sonuç olarak, bu küçük bayt tasarrufu, milyonlarca kez bir önbellek hattına iki kat depolamak veya aramamak arasındaki fark anlamına gelirse, yukarıdaki parçacık sim gibi performans açısından kritik bir alanda gerçekten önemli olabilir.