Sistemler için genel olarak Yığınlardan kurtulmak ve sadece bellek yönetimi için Heap kullanmak daha verimli olabilir mi?


14

Bana öyle geliyor ki bir yığınla yapılabilecek her şey yığınla yapılabilir, ancak yığınla yapılabilecek her şey yığınla yapılamaz. Bu doğru mu? O zaman sadelik için ve belirli iş yüklerinde az miktarda performans kaybetsek bile, sadece bir standarda (yani yığın) gitmek daha iyi olamaz mı?

Modülerlik ve performans arasındaki dengeyi düşünün. Bunun bu senaryoyu tanımlamanın en iyi yolu olmadığını biliyorum, ancak genel olarak daha iyi bir performans potansiyeli olsa bile anlayış ve tasarımın sadeliğinin daha iyi bir seçenek olabileceği anlaşılıyor.


1
C ve C ++ 'da, öbek üzerinde ayrılan belleği açıkça dağıtmanız gerekir. Bu daha basit değil.
user16764

Bir C # uygulaması yığın nesneleri korkunç çöp toplama ile yığın benzeri bir alanda tahsis edildiğini ortaya çıkaran profilleme kullandım. Çözümüm? Mümkün olan her şeyi (örn. Döngü değişkenleri, geçici değişkenler, vb.) Kalıcı yığın belleğe taşıyın . Programın RAM'in 10 katını yemesi ve 10 kat daha hızlı çalışması sağlandı.
imallett

@IanMallett: Sorun ve çözüm hakkındaki açıklamanızı anlamıyorum. Bir yerde daha fazla bilgi içeren bir bağlantınız var mı? Genellikle yığın tabanlı ayırmanın daha hızlı olduğunu düşünüyorum.
Frank Hileman

@FrankHileman temel sorun şuydu: kullandığım C # uygulaması son derece zayıf çöp toplama hızına sahipti. "Çözüm", tüm değişkenleri kalıcı hale getirmekti, böylece çalışma zamanında bellek işlemleri gerçekleşmedi. Bir süre önce genel olarak C # / XNA gelişimi hakkında bazı bağlamları tartışan bir fikir yazdım .
imallett

@IanMallett: teşekkür ederim. Bu günlerde çoğunlukla C # kullanan eski bir C / C ++ geliştiricisi olarak deneyimlerim oldukça farklıydı. Kütüphanelerin en büyük sorun olduğunu düşünüyorum. XBox360 platformu .net geliştiricileri için yarı pişmiş gibi görünüyor. Genellikle GC problemlerim olduğunda, havuza geçerim. Yardım eder.
Frank Hileman

Yanıtlar:


30

Yığınlar, hızlı bellek ayırma ve yeniden yerleştirmede kötüdür. Sınırlı bir süre için çok az miktarda bellek almak istiyorsanız, bir yığın en iyi seçiminiz değildir. Bir yığın, süper basit tahsis / dağıtma algoritması ile doğal olarak bu konuda mükemmelleşir (hatta donanımda yerleşikse daha da fazla), bu yüzden insanlar bunu işlevlere argüman iletmek ve yerel değişkenleri depolamak gibi şeyler için kullanırlar - en çok önemli dezavantajı, sınırlı bir alana sahip olmasıdır ve bu nedenle büyük nesneleri içinde tutmak veya onu uzun ömürlü nesneler için kullanmaya çalışmak kötü fikirlerdir.

Bir programlama dilini basitleştirmek için desteden tamamen kurtulmak yanlış bir şekilde IMO'dur - daha iyi bir yaklaşım, farkları soyutlamak, derleyicinin ne tür bir depolama alanı kullanacağını bulmasına izin verirken, programcı bir araya gelip İnsanların düşünme şekline daha yakın seviyedeki yapılar - ve aslında C #, Java, Python vb. gibi üst düzey diller tam olarak bunu yapar. Tamamen şeffaf olan veya dili kullanmak için anlamanız gereken birkaç işlevsel farkla, yığın tahsisli nesneler ve yığın tahsisli temel öğeler (.NET referansında 'referans türleri' ve 'değer türleri') için neredeyse aynı sözdizimi sunarlar. (ancak bir yığının ve bir yığının dahili olarak nasıl çalıştığını bilmek zorunda değilsiniz).


2
WOW BU İYİ OLDU :) Gerçekten özlü ve yeni başlayanlar için bilgilendirici!
Karanlık Templar

1
Birçok CPU'da yığın, dil dışında bir sorun olan donanımda işlenir, ancak çalışma zamanında büyük bir rol oynar.
Patrick Hughes

@Patrick Hughes: Evet, ama Heap ayrıca donanımda da bulunuyor, değil mi?
Karanlık Templar

@Dark Patrick'in muhtemelen söylemek istediği şey, x86 gibi mimarilerin yığını yönetmek için özel kayıtlara ve yığın üzerine / yığınından bir şey koymak veya kaldırmak için özel talimatlara sahip olmasıdır. Bu onu oldukça hızlı yapar.
FUZxxl

3
@Donal Fellows: Hepsi doğru. Ancak mesele, yığınların ve yığınların hem güçlü hem de zayıf noktalarına sahip olmalarıdır ve bunları uygun şekilde kullanmak en verimli kodu verecektir.
tdammers

8

Basitçe söylemek gerekirse, bir yığın biraz performans değil. Öbekten yüzlerce veya binlerce kat daha hızlı. Buna ek olarak, modern makinelerin çoğu, yığın için donanım desteğine sahiptir (x86 gibi) ve örneğin çağrı yığını için donanım işlevselliği kaldırılamaz.


Modern makinelerin yığın için donanım desteğine sahip olduğunu söylediğinde ne demek istiyorsun? Yığın zaten donanımda, değil mi?
Karanlık Templar

1
x86, özel kayıtlar ve yığınla başa çıkmak için talimatlar içerir. x86'nın yığınlar için desteği yoktur - bu tür şeyler işletim sistemi tarafından oluşturulur.
Pubby

8

Hayır

C ++ 'daki yığın alanı karşılaştırıldığında inanılmaz derecede hızlı. Hiçbir deneyimli C ++ geliştiricisinin bu işlevselliği devre dışı bırakmaya açık olacağını düşünmüyorum.

C ++ ile, seçiminiz var ve kontrolünüz var. Tasarımcılar, önemli yürütme süresi veya alanı ekleyen özellikler sunmaya özellikle meyilli değildi.

Bu seçimi uygulamak

Her nesnenin dinamik olarak tahsis edilmesini gerektiren bir kütüphane veya program oluşturmak istiyorsanız, bunu C ++ ile yapabilirsiniz. Nispeten yavaş çalışır, ancak o zaman bu 'modülerliğe' sahip olabilirsiniz. Geri kalanımız için, modülerlik her zaman isteğe bağlıdır, gerektiği gibi tanıtın, çünkü her ikisi de iyi / hızlı uygulamalar için gereklidir.

Alternatifler

Öbek üzerinde her nesne için depolama alanı oluşturulmasını gerektiren başka diller de vardır; tasarımları (gerçek dünya programları) her ikisini de öğrenmek zorunda kalmadan (IMO) daha kötü bir şekilde tehlikeye atacak şekilde oldukça yavaştır.

Her ikisi de önemlidir ve C ++, her bir senaryo için her ikisini de etkili bir şekilde kullanmanızı sağlar. Söyledikten sonra, OP'nizdeki bu faktörler sizin için önemliyse, C ++ dili tasarımınız için ideal olmayabilir (örneğin, daha üst düzey dillerde okuma).


Yığın aslında yığınla aynı hızda, ancak ayırma için özel donanım desteğine sahip değil. Öte yandan, yığınları çok hızlandırmanın yolları vardır (onları sadece uzman teknikler yapan bazı koşullara tabi).
Donal Fellows

@DonalFellows: Yığınlar için donanım desteği önemsizdir. Önemli olan şey, ne zaman bir şey serbest bırakılırsa o kişinin tahsis edilen herhangi bir şeyi serbest bırakabileceğini bilmesidir. Bazı programlama dilleri, nesneleri bağımsız olarak serbest bırakabilen yığınlara sahip değildir, bunun yerine yalnızca "sonra her şeyi serbest bırak" yöntemine sahiptir.
supercat

6

O zaman sadelik için ve belirli iş yüklerinde az miktarda performans kaybetsek bile, sadece bir standarda (yani yığın) gitmek daha iyi olamaz mı?

Aslında performans artışı büyük olasılıkla!

Diğerlerinin işaret ettiği gibi, yığınlar LIFO (son giren ilk çıkar) kurallarına uyan verileri yönetmek için son derece verimli bir yapıdır. Yığın üzerinde bellek ayırma / boşaltma genellikle CPU'daki bir kayıtta yapılan bir değişikliktir. Bir kaydı değiştirmek neredeyse her zaman bir işlemcinin gerçekleştirebileceği en hızlı işlemlerden biridir.

Yığın genellikle oldukça karmaşık bir veri yapısıdır ve bellek ayırma / boşaltma, ilgili tüm defter tutma işlemlerini yapmak için birçok talimat alacaktır. Daha da kötüsü, ortak uygulamalarda, yığınla çalışmak için yapılan her çağrı, işletim sistemine çağrı ile sonuçlanma potansiyeline sahiptir. İşletim sistemi çağrıları çok zaman alıyor! Programın tipik olarak kullanıcı modundan çekirdek moduna geçmesi gerekir ve bu gerçekleştiğinde işletim sistemi diğer programların daha fazla baskıya ihtiyacı olduğuna ve programınızın beklemesi gerekeceğine karar verebilir.


5

Simula öbeği her şey için kullandı. Öbek üzerine her şeyi koymak, yerel değişkenler için her zaman bir daha dolaylı indüksiyona neden olur ve Çöp Toplayıcıya ek baskı uygular (Çöp Toplayıcıların o zamanlar gerçekten emdiğini hesaba katmanız gerekir). Kısmen Bjarne'nin C ++ icat etmesinin nedeni budur.


Yani temelde C ++ sadece yığın kullanır?
Karanlık Templar

2
@Dark: Ne? Hayır. Simula'daki yığın eksikliği, bunu daha iyi yapmak için bir ilham kaynağı oldu.
fredoverflow

Ah şimdi ne demek istediğini anlıyorum! Teşekkürler +1 :)
Karanlık Templar

3

Yığınlar, örneğin işlev çağrılarıyla ilişkili meta veriler gibi LIFO verileri için son derece etkilidir. Yığın ayrıca CPU'nun doğal tasarım özelliklerinden de yararlanır. Bu seviyedeki performans, bir süreçteki hemen hemen her şey için temel olduğundan, bu seviyedeki "küçük" vuruşların alınması çok yaygın bir şekilde yayılır. Ek olarak, yığın belleği işletim sistemi tarafından hareket ettirilebilir, bu yığınlara ölümcül olacaktır. Bir yığın yığına uygulanabilirken, bir sürecin her parçasını tam anlamıyla en ayrıntılı düzeyde etkileyecek ek yük gerektirir.


2

"verimli" kod yazmanız açısından belki, ama kesinlikle yazılım verimliliği açısından değil. Yığın ayırmaları temelde ücretsizdir (yığın değişkenini taşımak ve yığın üzerinde yerel değişkenler için yer ayırmak yalnızca birkaç makine talimatı gerektirir).

Yığın tahsisi neredeyse hiç zaman almadığından, çok verimli bir yığın üzerinde bile bir ayırma 100k (1M + değilse) kat daha yavaş olacaktır.

Şimdi tipik bir uygulamanın kaç yerel değişken ve diğer veri yapısını kullandığını hayal edin. Döngü sayacı olarak kullandığınız her küçük "i" milyonlarca kez daha yavaş olarak ayrılır.

Donanım yeterince hızlıysa, yalnızca yığın kullanan bir uygulama yazabilirsiniz. Ancak şimdi, yığından yararlandığınızda ve aynı donanımı kullandığınızda ne tür bir uygulama yazabileceğinizi görüyorum.


"Tipik bir uygulamanın kaç yerel değişken ve diğer veri yapısını kullandığını hayal edin" dediğinizde, özellikle başka hangi veri yapılarından bahsediyorsunuz?
Karanlık Templar

1
"100k" ve "1M +" değerleri bir şekilde bilimsel midir? Yoksa sadece "çok" demenin bir yolu mu?
Bruno Reis

@Bruno - IMHO Kullandığım 100K ve 1M sayıları aslında bir noktayı kanıtlamak için muhafazakar bir tahmindir. VS ve C ++ hakkında bilginiz varsa, yığına 100 bayt ayıran bir program yazın ve yığına 100 bayt ayıran bir program yazın. Ardından, sökme görünümüne geçin ve her bir ayırmanın aldığı montaj talimatı sayısını sayın. Yığın işlemleri genellikle Windows DLL içine birkaç işlev çağrıları, kovalar ve bağlantılı listeler ve sonra birleştirme ve diğer algoritmalar vardır. Yığını ile, bir montaj talimatı "esp, 100 ekleyin" aşağı kaynar ...
DXM

2
"100 bin (1 milyondan fazla değilse) kat daha yavaş"? Bu biraz abartılı. İki büyüklük sırası daha yavaş olsun, belki üç olsun, ama hepsi bu. En azından, Linux'um bir çekirdek i5 üzerinde 6 saniyeden daha az bir sürede 100M yığın tahsisi (+ bazı çevre talimatları) yapabilir, bu da tahsis başına birkaç yüz komuttan fazla olamaz - aslında, neredeyse kesinlikle daha az. O ise altı yığını daha yavaş büyüklük sıralaması OS'nin yığın uygulamasıyla Majorly sorun var. Tabii ki Windows ile ilgili çok yanlış var, ama bu ...
leftaroundabout

1
moderatörler muhtemelen tüm yorum dizisini öldürmek üzeredir. İşte anlaşma, ben gerçek sayılar benim çıkarıldığını kabul ediyorum ...., ama kabul edelim faktör gerçekten, gerçekten büyük ve daha fazla yorum yapmak değil :)
DXM

2

"Çöp Toplama Hızlıdır, ancak Yığın Daha Hızlıdır" ile ilgilenebilirsiniz.

http://dspace.mit.edu/bitstream/handle/1721.1/6622/AIM-1462.ps.Z

Eğer doğru okursam, bu adamlar öbek üzerinde "yığın kareleri" ayırmak için bir C derleyicisini değiştirdiler ve sonra yığını atmak yerine kareleri ayırmak için çöp toplama kullanın.

Yığın tahsisli "yığın kareler", yığın tahsisli "yığın kareler" den büyük ölçüde daha iyi performans gösterir.


1

Çağrı yığını bir yığın üzerinde nasıl çalışacak? Esasen, her programda öbek üzerinde bir yığın tahsis etmeniz gerekir, bu yüzden neden OS + donanımı sizin için yapmıyor?

İşlerin gerçekten basit ve verimli olmasını istiyorsanız, kullanıcıya sadece bellek yığınını verin ve bunlarla baş etmelerine izin verin. Tabii ki, kimse her şeyi kendi başına uygulamak istemiyor ve bu yüzden bir yığınımız ve bir yığınımız var.


Kesinlikle bir "çağrı yığını" bir programlama dili çalışma zamanı ortamının gerekli bir özelliği değildir. Örneğin, tembel olarak değerlendirilmiş bir işlevsel dilin, grafik azaltma (kodladığım) ile doğrudan uygulanmasının çağrı yığını yoktur. Ancak çağrı yığını, özellikle modern işlemcilerin bunu kullandığını varsaydığı ve kullanımı için optimize edildiği için çok yaygın olarak kullanılan ve yaygın olarak kullanılan bir tekniktir.
Ben

@Ben - bir dilden bellek ayırma gibi soyut şeyler için doğru (ve iyi bir şey) olsa da, bu şu anda hakim olan bilgisayar mimarisini değiştirmez. Bu nedenle, grafik azaltma kodunuz yığını çalıştırırken kullanmaya devam edecektir - beğen ya da beğenme.
Ingo

@Ingo Gerçekten anlamlı bir anlamda değil. Elbette, işletim sistemi geleneksel olarak "yığın" olarak adlandırılan bir bellek bölümünü başlatacak ve buna işaret eden bir kayıt olacaktır. Ancak kaynak dildeki işlevler çağrı sırasında yığın kareleri olarak temsil edilmez. İşlev yürütme tamamen yığındaki veri yapılarının manipülasyonu ile temsil edilir. Son arama optimizasyonu kullanılmasa bile "yığını taşmak" mümkün değildir. Demek istediğim, "çağrı yığını" hakkında temel bir şey yok.
Ben

Kaynak dilin işlevlerinden değil, yorumlayıcıdaki (veya aslında grafik azaltmayı gerçekleştiren) işlevlerden bahsediyorum. Bir yığın isterler. Çağdaş donanım grafik azaltma yapmadığı için bu açıktır. Bu nedenle, grafik azaltma algoritmanız sonuçta makine ode ile eşlenir ve bahse girerim aralarında alt rutin çağrılar vardır. QED.
Ingo

1

Hem yığın hem de yığın gereklidir. Farklı durumlarda kullanılırlar, örneğin:

  1. Yığın ayırmanın sizeof (a [0]) == sizeof (a [1])
  2. Yığın tahsisinin sizeof (a) 'nın derleme zamanı sabiti olması konusunda bir sınırlaması vardır
  3. Yığın tahsisi döngü, grafik vb. Karmaşık veri yapıları yapabilir
  4. Yığın tahsisi derleme zamanı büyüklüğünde ağaçlar yapabilir
  5. Öbek mülkiyetin izlenmesini gerektirir
  6. Yığın tahsisi ve dağıtma otomatiktir
  7. Yığın bellek, işaretçiler aracılığıyla bir kapsamdan diğerine kolayca geçirilebilir
  8. Yığın belleği her işlev için yereldir ve ömrünü uzatmak için nesnelerin üst kapsama taşınması gerekir (veya üye işlevleri yerine nesnelerin içinde depolanır)
  9. Yığın performans için kötü
  10. Yığın oldukça hızlı
  11. Yığın nesneleri, sahiplik alan işaretçiler aracılığıyla işlevlerden döndürülür. Veya shared_ptrs.
  12. Yığın nesneleri, sahiplik almayan başvurular aracılığıyla işlevlerden döndürülür.
  13. Yığın, her yeni öğeyi doğru silme veya silme türüyle eşleştirmeyi gerektirir []
  14. Yığın nesneleri RAII ve yapıcı başlatma listelerini kullanır
  15. Yığın nesneleri bir işlevin içindeki herhangi bir noktadan başlatılabilir ve yapıcı parametrelerini kullanamaz
  16. Yığın nesneleri başlatma için yapıcı parametrelerini kullanır
  17. Yığın diziler kullanır ve dizi boyutu çalışma zamanında değişebilir
  18. Yığın tek nesneler içindir ve boyut derleme zamanında sabitlenir

Temel olarak mekanizmalar hiç karşılaştırılamaz çünkü birçok detay farklıdır. Onlarla ortak olan tek şey, her ikisinin de bir şekilde hafızayı işlemesi.


1

Modern bilgisayarlar, büyük ama yavaş bir ana bellek sistemine ek olarak birkaç önbellek katmanına sahiptir. Ana bellek sisteminden bir bayt okumak veya yazmak için gereken sürede en hızlı önbellek belleğine onlarca erişim yapılabilir. Bu nedenle, bir konuma bin kez erişmek, her biri bir kez 1.000 (hatta 100) bağımsız konuma erişmekten daha hızlıdır. Çoğu uygulama sürekli olarak yığının üst kısmına yakın küçük miktarlarda bellek ayırdığı ve yeniden konumlandırdığı için, yığının üstündeki konumlar büyük miktarda (tipik bir uygulamada% 99 +) olacak şekilde çok miktarda kullanılır ve yeniden kullanılır. yığın erişim önbellek kullanılarak işlenebilir.

Buna karşılık, bir uygulama, devamlılık bilgisini saklamak için yığın nesnelerini tekrar tekrar oluşturacak ve terk edecek olsaydı, şimdiye kadar oluşturulan her yığın nesnesinin her sürümünün ana belleğe yazılması gerekirdi. Bu tür nesnelerin büyük çoğunluğu, CPU'nun başladığı önbellek sayfalarını geri dönüştürmek istediği zaman tamamen işe yaramaz olsa bile, CPU'nun bunu bilmesinin bir yolu olmazdı. Sonuç olarak, CPU yavaş bellek yazmalarını gereksiz bilgi yazmak için çok zaman harcamak zorunda kalacaktır. Hız için bir reçete değil.

Dikkate alınması gereken başka bir şey, birçok durumda, rutine aktarılan bir nesne referansının rutin çıktıktan sonra kullanılmayacağını bilmek yararlıdır. Parametreler ve yerel değişkenler yığın üzerinden iletilirse ve rutinin kodunun incelenmesi, aktarılan referansın bir kopyasının kalıcı olmadığını gösterirse, rutini çağıran kodun, nesnesi çağrıdan önce vardı, sonra hiçbiri var olmayacak. Aksine, parametreler yığın nesneleri üzerinden iletildiyse, "rutin bir dönüşten sonra" gibi kavramlar biraz daha belirsiz hale gelir, çünkü kod devamın bir kopyasını saklarsa, rutinin bir kereden fazla "geri dönmesi" mümkündür tek çağrı.

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.