Hangisi daha hızlı: Yığın ayırma veya Yığın ayırma


503

Bu soru oldukça basit gelebilir, ancak bu çalıştığım başka bir geliştiriciyle yaptığım bir tartışma.

Yığın tahsis etmek yerine, elimden gelen şeyleri tahsis etmeye özen gösteriyordum. Benimle konuşuyordu ve omzumu izliyordu ve aynı performansa ihtiyaç duymadıklarını söyledi çünkü aynı performans açısından akıllıydılar.

Her zaman yığının büyümesinin sabit bir zaman olduğu izlenimi altındaydım ve yığın tahsisinin performansı, hem ayırma (uygun boyutta bir delik bulma) hem de ayırma (parçalamayı azaltmak için delikleri daraltma) için yığının mevcut karmaşıklığına bağlıydı. birçok standart kütüphane uygulaması, yanılmıyorsam silme işlemi sırasında bunu yapmak için zaman alır).

Bu beni muhtemelen derleyiciye bağımlı olacak bir şey olarak vuruyor. Özellikle bu proje için PPC mimarisi için bir Metrowerks derleyicisi kullanıyorum . Bu kombinasyon hakkındaki bilgiler en yararlı olacaktır, ancak genel olarak GCC ve MSVC ++ için durum nedir? Yığın ayırma, yığın ayırma kadar yüksek performans göstermiyor mu? Fark yok mu? Ya da farklar, anlamsız mikro optimizasyon haline geldiğinde.


11
Bunun oldukça eski olduğunu biliyorum, ancak farklı tahsis türlerini gösteren bazı C / C ++ snippet'lerini görmek güzel olurdu.
Joseph Weissman

42
İnek orkiniz çok cahil, ama daha da önemlisi tehlikelidir çünkü çok cahil olduğu şeyler hakkında yetkili iddialarda bulunur. Ekibinizden bu tür insanları olabildiğince çabuk tüketin.
Jim Balter

5
Yığın genellikle yığından çok daha büyük olduğunu unutmayın. Büyük miktarda veri tahsis edildiyse, bunu gerçekten yığına koymanız veya yığın boyutunu işletim sisteminden değiştirmeniz gerekir.
Paul Draper

1
Aksi ispatlayan karşılaştırmalı değerlendirmeler veya karmaşıklık argümanlarınız yoksa, varsayılan anlamsız mikro optimizasyonlar tüm optimizasyonlardır.
Björn Lindqvist

2
İş arkadaşınızın çoğunlukla Java veya C # deneyimine sahip olup olmadığını merak ediyorum. Bu dillerde, hemen hemen her şey, bu varsayımlara yol açabilecek kaputun altında yığın olarak ayrılmıştır.
Cort Ammon

Yanıtlar:


493

Yığın tahsisi çok daha hızlıdır çünkü gerçekten yaptığı tek şey yığın işaretçisini hareket ettirmektir. Bellek havuzlarını kullanarak, yığın tahsisinden karşılaştırılabilir performans elde edebilirsiniz, ancak bu biraz ek bir karmaşıklık ve kendi baş ağrılarıyla birlikte gelir.

Ayrıca, yığın ve yığın yalnızca bir performans değerlendirmesi değildir; ayrıca nesnelerin beklenen ömrü hakkında da çok şey anlatır.


211
Ve daha da önemlisi, yığın her zaman sıcaktır, aldığınız hafızanın önbellekte olması çok daha fazla yığın bellekten daha fazladır
Benoît

47
Bazı (çoğunlukla gömülü, bildiğim) mimarilerde, yığın hızlı kalıp bellekte (örn. SRAM) saklanabilir. Bu büyük bir fark yaratabilir!
leander

38
Çünkü yığın aslında bir yığın. Üstünde olmadıkça yığın tarafından kullanılan bir bellek alanını boşaltamazsınız. Yönetim yok, üzerinde bir şeyleri itiyorsun ya da patlatıyorsun. Öte yandan, yığın bellek yönetilir: çekirdekten bellek parçaları ister, belki onları böler, birleştirir, yeniden kullanır ve serbest bırakır. Yığın gerçekten hızlı ve kısa tahsisler içindir.
Benoît

24
@Pacerier Çünkü Yığın Öbek'ten çok daha küçük. Büyük diziler ayırmak istiyorsanız, bunları Yığına ayırmanız daha iyi olur. Yığında büyük bir dizi ayırmaya çalışırsanız, size bir Yığın Taşması verir. Örneğin C ++ ile şunu deneyin: int t [100000000]; Örnek olarak deneyin t [10000000] = 10; ve sonra cout << t [10000000]; Size bir yığın taşması vermelidir veya işe yaramaz ve size hiçbir şey göstermez. Ancak diziyi öbek üzerinde ayırırsanız: int * t = new int [100000000]; ve aynı işlemleri yaptıktan sonra işe yarayacak, çünkü Heap böyle büyük bir dizi için gerekli boyuta sahip.
Lilian A. Moraru

7
@Pacerier En belirgin neden, yığındaki nesnelerin, tahsis ettikleri bloktan çıktıktan sonra kapsam dışı kalmasıdır.
Jim Balter

166

Yığın çok daha hızlı. Kelimenin tam anlamıyla çoğu mimaride tek bir komut kullanır, çoğu durumda, örneğin x86:

sub esp, 0x10

(Bu, yığın işaretçisini 0x10 bayt aşağı taşır ve böylece bu baytları bir değişken tarafından kullanılmak üzere "ayırır".

Tabii ki, yığının boyutu çok, çok sonludur, çünkü yığın tahsisini aşırı kullanırsanız veya özyineleme yapmaya çalışırsanız hızlıca öğreneceksiniz :-)

Ayrıca, profilleme ile gösterildiği gibi, kodu doğrulaması gerekmeyen kod performansını optimize etmek için çok az neden vardır. "Erken optimizasyon" genellikle değerinden daha fazla soruna neden olur.

Temel kuralım: Derleme zamanında bazı verilere ihtiyacım olacağını biliyorsanız ve birkaç yüz baytın altındaysa, bunu yığın olarak ayırırım. Aksi takdirde öbek ayırıyorum.


20
Bir komut ve genellikle yığındaki TÜM nesneler tarafından paylaşılır.
MSalters

9
Özellikle doğrulanabilir bir şekilde ihtiyaç duyulan noktaya dikkat çekti. İnsanların performans hakkındaki kaygılarının nasıl yanlış yerleştirildiğine sürekli şaşıyorum.
Mike Dunlavey

6
"Deallocation" da çok basit ve tek bir leavetalimatla yapılır .
doc

15
Burada özellikle gizli ilk kez "gizli" maliyet unutmayın. Bunu yapmak, bir sayfa hatasına, çekirdeğe bellek ayırmak için bazı işler yapmak (ya da en kötü durumda takastan yüklemek) için gereken bir bağlam anahtarı ile sonuçlanabilir.
nos

2
Bazı durumlarda, 0 talimatla bile tahsis edebilirsiniz. Kaç baytın tahsis edilmesi gerektiği hakkında bazı bilgiler biliniyorsa, derleyici bunları diğer yığın değişkenlerini tahsis ettiği anda önceden tahsis edebilir. Bu durumlarda, hiçbir şey ödemezsiniz!
Cort Ammon

119

Dürüst olmak gerekirse, performansı karşılaştırmak için bir program yazmak önemsizdir:

#include <ctime>
#include <iostream>

namespace {
    class empty { }; // even empty classes take up 1 byte of space, minimum
}

int main()
{
    std::clock_t start = std::clock();
    for (int i = 0; i < 100000; ++i)
        empty e;
    std::clock_t duration = std::clock() - start;
    std::cout << "stack allocation took " << duration << " clock ticks\n";
    start = std::clock();
    for (int i = 0; i < 100000; ++i) {
        empty* e = new empty;
        delete e;
    };
    duration = std::clock() - start;
    std::cout << "heap allocation took " << duration << " clock ticks\n";
}

Aptal bir kıvamın küçük zihinlerin hobgoblini olduğu söylenir . Görünüşe göre derleyicileri optimize eden birçok programcının zihninin hobgoblinleri. Bu tartışma daha önce cevabın altındaydı, ama insanlar görünüşe kadar okumaktan rahatsız olamazlar, bu yüzden zaten cevapladığım soruları almaktan kaçınmak için buraya taşıyorum.

Optimize edici bir derleyici, bu kodun hiçbir şey yapmadığını fark edebilir ve tamamen optimize edebilir. Böyle şeyler yapmak optimize edicinin işidir ve optimize ediciyle savaşmak aptalca bir iştir.

Şu anda kullanımda olan ya da gelecekte kullanımda olacak her optimizer kandırmak için iyi bir yolu olmadığı için bu kod optimizasyon kapalı ile derleme tavsiye ederim.

Optimize ediciyi açıp bununla mücadele etmekle ilgili şikayette bulunan herkes, halka açık alay konusu olmalıdır.

Eğer nanosaniye hassasiyetini önemsseydim kullanmazdım std::clock(). Sonuçları doktora tezi olarak yayınlamak isteseydim, bu konuda daha büyük bir anlaşma yapardım ve muhtemelen GCC, Tendra / Ten15, LLVM, Watcom, Borland, Visual C ++, Digital Mars, ICC ve diğer derleyicileri karşılaştırırdım. Olduğu gibi, yığın ayırma yığın ayırma yüzlerce kez daha uzun sürer ve soruyu daha fazla araştırmak için yararlı bir şey görmüyorum.

Optimize edicinin test ettiğim koddan kurtulmak için bir görevi var. Optimize ediciye çalışmasını söylemek için herhangi bir neden görmüyorum ve sonra optimize ediciyi gerçekten optimize etmemeye kandırmaya çalışıyorum. Ama bunu yaparken değer görürsem, aşağıdakilerden birini veya birkaçını yaparım:

  1. Bir veri üyesini ekleyin emptyve döngüdeki o veri üyesine erişin; ama sadece veri üyesinden okuduysam, optimizer sürekli katlama yapabilir ve döngüyü kaldırabilir; sadece veri üyesine yazarsam, optimizatör döngünün son yinelemesi dışında hepsini atlayabilir. Ayrıca, soru "yığın tahsisi ve veri erişimi ve yığın tahsisi ve veri erişimi" değildi.

  2. Bildirin e volatile, ancak volatilegenellikle yanlış derlendi (PDF).

  3. eDöngünün içindeki adresi alın (ve belki de externbaşka bir dosyada bildirilen ve tanımlanan bir değişkene atayın ). Ancak bu durumda bile, derleyici - en azından yığın üzerinde - eher zaman aynı bellek adresine tahsis edileceğini ve sonra yukarıdaki (1) 'de olduğu gibi sabit katlama yapacağını fark edebilir. Döngünün tüm yinelemelerini alıyorum, ancak nesne asla tahsis edilmedi.

Açık olanın ötesinde, bu test hem tahsisi hem de yeniden yerleştirmeyi ölçtüğü için kusurludur ve orijinal soru yeniden yerleştirmeyi sormamıştır. Elbette, yığına tahsis edilen değişkenler kapsamlarının sonunda otomatik olarak yerleştirilir, bu nedenle çağrı yapılmazsa delete(1) sayılar çarpıtılmaz (yığın ayırma, yığın tahsisi ile ilgili sayılara dahil edilir, bu nedenle yığın yığınının ölçülmesi adil olur) ve ( 2) yeni işaretçiye başvurmadıkça ve zaman ölçümümüzü deletealdıktan sonra aramadıkça, oldukça kötü bir bellek sızıntısına neden olun .

Makinemde, Windows'ta g ++ 3.4.4 kullanarak, 100000 ayırmadan daha az bir şey için hem yığın hem de yığın ayırma için "0 saat keneleri" alıyorum ve o zaman bile yığın tahsisi ve "15 saat keneleri için" 0 saat keneleri "alıyorum msgstr "öbek tahsisi için. 10.000.000 ayırmayı ölçtüğümde, yığın tahsisi 31 saat kenarı alır ve yığın tahsisi 1562 saat kenesi alır.


Evet, optimize edici bir derleyici boş nesneler oluşturmaya yardımcı olabilir. Doğru anlarsam, ilk döngüyü bile kaldırabilir. Yinelemeleri 10.000.000 yığın tahsisine çıkardığımda 31 saat kenarı ve yığın tahsisi 1562 saat kenesi aldı. G ++ 'nın çalıştırılabilir dosyayı optimize etmesini söylemeden, g ++' nın kurucuları seçmediğini söylemek güvenli.


Bunu yazdığım yıldan bu yana, Stack Overflow'daki tercih, optimize edilmiş yapılardan performans göndermek oldu. Genel olarak, bunun doğru olduğunu düşünüyorum. Ancak, aslında kodun optimize edilmesini istemediğinizde derleyicinin kodu optimize etmesini istemenin saçma olduğunu düşünüyorum. Valet parking için ekstra ödeme, ama anahtarlarını teslim reddetme çok benzer olarak beni grev. Bu özel durumda, optimize edicinin çalışmasını istemiyorum.

Karşılaştırma ölçüsünün biraz değiştirilmiş bir versiyonunu kullanma (orijinal programın döngü boyunca her seferinde yığına bir şey tahsis etmediği geçerli noktayı ele almak için) ve optimizasyon olmadan derleme, ancak serbest bırakma kitaplıklarına bağlantı (bağlanmadığımız geçerli noktayı ele almak için) hata ayıklama kitaplıklarına bağlamanın neden olduğu yavaşlamayı dahil etmek istemez):

#include <cstdio>
#include <chrono>

namespace {
    void on_stack()
    {
        int i;
    }

    void on_heap()
    {
        int* i = new int;
        delete i;
    }
}

int main()
{
    auto begin = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000000; ++i)
        on_stack();
    auto end = std::chrono::system_clock::now();

    std::printf("on_stack took %f seconds\n", std::chrono::duration<double>(end - begin).count());

    begin = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000000; ++i)
        on_heap();
    end = std::chrono::system_clock::now();

    std::printf("on_heap took %f seconds\n", std::chrono::duration<double>(end - begin).count());
    return 0;
}

görüntüler:

on_stack took 2.070003 seconds
on_heap took 57.980081 seconds

komut satırı ile derlendiğinde sistemime cl foo.cc /Od /MT /EHsc.

Optimize edilmemiş bir yapı elde etme yaklaşımımı kabul etmeyebilirsiniz. Bu iyi: karşılaştırmayı istediğiniz kadar değiştirmekten çekinmeyin. Optimizasyonu açtığımda şunu elde ederim:

on_stack took 0.000000 seconds
on_heap took 51.608723 seconds

Yığın tahsisi aslında anlık olduğu için değil, yarı iyi bir derleyici, on_stackyararlı bir şey yapmadığını ve optimize edilebileceğini fark edebileceğinden. Linux dizüstü bilgisayarımdaki GCC ayrıca on_heapyararlı bir şey yapmadığını fark eder ve onu da optimize eder:

on_stack took 0.000003 seconds
on_heap took 0.000002 seconds

2
Ayrıca, ana fonksiyonunuzun en başına bir "kalibrasyon" döngüsü eklemelisiniz, döngü döngüsü başına ne kadar zaman alacağınıza dair bir fikir verecek bir şey eklemeli ve örneğinizin çalışmasını sağlamak için diğer döngüleri ayarlamalısınız. kullandığınız sabit sabit yerine belirli bir süre.
Joe Pineda

2
Ayrıca her seçenek döngü (artı g ++ değil optimize etmek için talimat talimatı) çalışır sayısını artırmak sevindim önemli sonuçlar verdi. Şimdi yığının daha hızlı olduğunu söylemek zor gerçeklerimiz var. Çabaların için teşekkürler!
Joe Pineda

7
Böyle bir koddan kurtulmak, optimizasyon görevidir. Optimize ediciyi açmak ve daha sonra gerçekten optimize etmesini önlemek için iyi bir neden var mı? Cevabı daha net hale getirmek için düzenledim: optimize ediciyle savaşmaktan hoşlanıyorsanız, derleyici yazarlarının ne kadar akıllı olduğunu öğrenmeye hazır olun.
Max Lybbert

3
Çok geç kaldım, ancak burada, yığın tahsisinin çekirdek aracılığıyla bellek istediğini de belirtmek gerekir, bu yüzden performans isabeti de çekirdeğin verimliliğine bağlıdır. İK zamanlayıcı değiştirerek, Linux (Linux 3.10.7-gentoo 2. SMP Çar 4 Eylül 18:58:21 MDT 2013 x86_64) ile bu kodu kullanarak ve her döngüde 100 milyon tekrarı kullanılarak bu performansı verir: stack allocation took 0.15354 seconds, heap allocation took 0.834044 secondsile -O0seti, yapımı Linux yığın dağılımı sadece benim makinemde yaklaşık 5.5 kat daha yavaş.
Taywee

4
Optimizasyonları olmayan pencerelerde (hata ayıklama derlemesi) hata ayıklama öbeğinden çok daha yavaş olan hata ayıklama yığınını kullanır. Ben hiç optimize edici "kandırmak" için kötü bir fikir olduğunu sanmıyorum. Derleyici yazarları akıllıdır, ancak derleyiciler yapay zeka değildir.
paulm

30

Xbox 360 Xenon işlemcide diğer çok çekirdekli sistemlerde de geçerli olabilen Stack vs. Heap Tahsisi hakkında öğrendiğim ilginç bir şey, Heap'e tahsis etmenin, diğer tüm çekirdekleri durdurmak için bir Kritik Bölümün girilmesine neden olması, böylece tahsisin çatışma değil. Böylece, sıkı bir döngüde Yığın Tahsisi, durakları engellediğinden sabit boyutlu diziler için gidilecek yoldu.

Bu, çok çekirdekli / çoklu pili kodluyorsanız, yığın tahsinizin yalnızca kapsam işlevinizi çalıştıran çekirdek tarafından görüntülenebileceği ve diğer çekirdekleri / CPU'ları etkilemeyeceği düşünülmesi gereken başka bir hız olabilir.


4
Bu sadece Xenon için değil, çok çekirdekli makineler için de geçerli. Hücre bile bunu yapmalıdır çünkü bu PPU çekirdeğinde iki donanım iş parçacığı çalıştırıyor olabilirsiniz.
Crashworks

15
Bu, yığın ayırıcısının (özellikle zayıf) uygulanmasının bir etkisidir. Daha iyi yığın ayırıcıların her ayırmada bir kilit edinmesi gerekmez.
Chris Dodd

19

Çok performans gösteren belirli boyutlardaki nesneler için özel bir yığın ayırıcı yazabilirsiniz. Bununla birlikte, genel yığın ayırıcı özellikle performans göstermemektedir.

Ayrıca, nesnelerin beklenen kullanım ömrü konusunda Torbjörn Gyllebring ile aynı fikirdeyim. İyi bir nokta!


1
Buna bazen döşeme tahsisi denir.
Benoit

8

Ben yığın ayırma ve yığın ayırma genellikle değiştirilebilir olduğunu sanmıyorum. Ayrıca her ikisinin performansının genel kullanım için yeterli olduğunu umuyorum.

Hangisi tahsis kapsamına daha uygunsa, küçük eşyalar için şiddetle tavsiye ediyorum. Büyük eşyalar için yığın muhtemelen gereklidir.

Birden çok iş parçacığı olan 32 bit işletim sistemlerinde, adres alanının oyulması gerektiğinden ve er ya da geç bir iş parçacığı yığını diğerine geçeceğinden yığın genellikle oldukça sınırlıdır (tipik olarak en az birkaç mb olsa da). Tek iş parçacıklı sistemlerde (yine de Linux glibc tek iş parçacıklı) sınırlama çok daha azdır, çünkü yığın sadece büyüyebilir ve büyüyebilir.

64 bit işletim sistemlerinde iş parçacığı yığınlarını oldukça büyük hale getirmek için yeterli adres alanı vardır.


6

Genellikle yığın tahsisi sadece yığın işaretçi kaydından çıkarılmasından oluşur. Bu bir yığın aramaktan tonlarca daha hızlı.

Bazen yığın tahsisi sanal bellek sayfası eklemeyi gerektirir. Sıfırlanmış bellek içeren yeni bir sayfa eklemek diskten bir sayfa okumayı gerektirmez, bu nedenle bu genellikle bir yığın aramaya göre tonlarca daha hızlı olacaktır (özellikle yığının bir kısmı da çağrılmışsa). Nadir bir durumda ve böyle bir örnek oluşturabilirsiniz, zaten RAM'de olan yığının bir kısmında yeterli alan olur, ancak yığına yeni bir sayfa tahsis etmek için başka bir sayfanın yazılmasını beklemesi gerekir diske. Bu nadir durumda, yığın daha hızlıdır.


Sayfalanmadıkça öbek "arandı" sanmıyorum. Katı hal belleği çoklayıcı kullandığından ve belleğe doğrudan erişebildiğinden emin olur, dolayısıyla Rasgele Erişim Belleği.
Joe Phillips

4
İşte bir örnek. Çağıran program 37 bayt ayırmayı ister. Kütüphane işlevi en az 40 baytlık bir blok arar. Ücretsiz listedeki ilk blok 16 bayt içerir. Ücretsiz listedeki ikinci blok 12 bayt içerir. Üçüncü blokta 44 bayt vardır. Kütüphane bu noktada aramayı durdurur.
Windows programcısı

6

Yığın tahsisine göre büyüklük sıraları performans avantajının yanı sıra, uzun süre çalışan sunucu uygulamaları için yığın tahsisi tercih edilir. En iyi yönetilen yığınlar bile sonunda o kadar parçalı hale gelir ki uygulama performansı düşer.


4

Bir yığın sınırlı kapasiteye sahipken, yığın değil. Bir işlem veya iş parçacığı için tipik yığın yaklaşık 8K'dır. Ayrıldıktan sonra boyutu değiştiremezsiniz.

Bir yığın değişkeni kapsam belirleme kurallarına uyarken bir yığın değişmez. Talimat işaretçiniz bir işlevin ötesine geçerse, işlevle ilişkili tüm yeni değişkenler kaybolur.

En önemlisi, genel işlev çağrı zincirini önceden tahmin edemezsiniz. Dolayısıyla, parçanızda yalnızca 200 baytlık bir ayırma yığını taşmasına neden olabilir. Bu özellikle bir uygulama değil, bir kitaplık yazıyorsanız önemlidir.


1
Modern bir işletim sistemindeki bir kullanıcı modu yığını için ayrılan sanal adres alanı miktarı varsayılan olarak en az 64 KB veya daha fazladır (Windows'ta 1 MB). Çekirdek yığını boyutlarından mı bahsediyorsunuz?
bk1e

1
Makinemde, bir işlem için varsayılan yığın boyutu kB değil 8MB'dir. Bilgisayarınız kaç yaşında?
Greg Rogers

3

Bence ömür çok önemlidir ve tahsis edilen şeyin karmaşık bir şekilde inşa edilmesi gerekip gerekmediği. Örneğin, işleme dayalı modellemede, genellikle, bir grup alanı olan bir işlem yapısını işlem işlevlerine doldurmanız ve iletmeniz gerekir. Örnek için OSCI SystemC TLM-2.0 standardına bakınız.

Bunları, operasyon çağrısına yakın olarak yığına yerleştirmek, inşaat pahalı olduğu için büyük bir ek yüke neden olma eğilimindedir. Yığın üzerinde tahsis ve işlem nesneleri havuzu ya da "bu modülün şimdiye kadar sadece bir işlem nesnesine ihtiyacı var" gibi basit bir ilke ile yeniden kullanmak için iyi bir yol.

Bu, nesneyi her işlem çağrısına ayırmaktan çok daha hızlıdır.

Bunun nedeni, nesnenin pahalı bir yapıya ve oldukça uzun bir kullanım ömrüne sahip olmasıdır.

Ben şunu söyleyebilirim: Her ikisini de deneyin ve durumunuzda en iyi olanı görün, çünkü gerçekten kodunuzun davranışına bağlı olabilir.


3

Yığın tahsisine karşı yığın tahsisine karşı muhtemelen en büyük sorun, genel durumda yığın tahsisinin sınırsız bir işlem olması ve böylece zamanlamanın bir sorun olduğu yerlerde kullanamamanızdır.

Zamanlamanın bir sorun olmadığı diğer uygulamalar için bu kadar önemli olmayabilir, ancak çok fazla yığın ayırırsanız, yürütme hızı etkilenir. Yığını her zaman kısa ömürlü ve genellikle ayrılan bellek (örneğin döngüler halinde) için kullanmaya çalışın ve mümkün olduğunca uzun süre - uygulama başlatma sırasında yığın ayırma yapın.


3

Daha hızlı jsut yığın tahsisi değil. Ayrıca yığın değişkenlerini kullanma konusunda da çok kazanırsınız. Referans yerleri daha iyi. Ve son olarak, anlaşma, çok daha ucuzdur.


3

Yığın tahsisi birkaç yönerge iken, bilinen en hızlı rtos yığın ayırıcısı (TLSF) ortalama 150 yönerge kullanır. Ayrıca yığın tahsisleri kilit gerektirmez, çünkü başka bir büyük performans kazancı olan yerel iş parçacığı depolama alanı kullanırlar. Bu nedenle, yığın tahsisleri, ortamınızın ne kadar çok iş parçacığına bağlı olduğuna bağlı olarak 2-3 derece daha hızlı olabilir.

Performansa önem veriyorsanız genel olarak yığın tahsisi son çarenizdir. Arasında uygulanabilir bir seçenek, sadece birkaç talimat olan ve çok az tahsis başına ek yüke sahip sabit bir havuz ayırıcı olabilir, bu nedenle küçük sabit boyutlu nesneler için harikadır. Dezavantajı sadece sabit boyutlu nesnelerle çalışır, doğası gereği iş parçacığı için güvenli değildir ve blok parçalanma sorunları vardır.


3

C ++ Diline Özgü Endişeler

Her şeyden önce, C ++ tarafından zorunlu kılınan "yığın" veya "yığın" tahsisi yoktur . Blok kapsamlarındaki otomatik nesneler hakkında konuşuyorsanız, bunlar "ayrılmaz". Dinamik olarak tahsis edilmiş hafıza ile ilgili olduğu; (ikinci C "dinamik" ++ parlance olup. Btw, C otomatik depolama süresi kesinlikle "ayrılmış" aynı değildir) serbest deposu da, zorunlu olarak "yığın" ile, ikincisi genellikle (varsayılan) uygulamadır .

Soyut makine semantik kurallarına göre, otomatik nesneler hala belleği işgal etse de , uygun bir C ++ uygulamasının bunun önemli olmadığını kanıtlayabildiğinde (programın gözlemlenebilir davranışını değiştirmediğinde) bu gerçeği göz ardı etmesine izin verilir. Bu izin, nesnelerin belirli tasarımlarının atlanmasına izin vermek için as-if kuralı aynı zamanda olağan optimizasyonları mümkün kılan genel fıkra olan ISO C ++ 'daki (ve ISO C'de de hemen hemen aynı kural vardır). AS-if kuralının yanı sıra ISO C ++ 'ın da kopya seçim kuralları vardır tarafından verilir . Bu şekilde dahil olan yapıcı ve yıkıcı çağrıları atlanmıştır. Sonuç olarak, bu yapıcılarda ve yıkıcılarda bulunan otomatik nesneler (varsa), kaynak kodunun ima ettiği naif soyut semantiğe kıyasla ortadan kaldırılır.

Öte yandan, ücretsiz mağaza tahsisi kesinlikle tasarım gereği "tahsis" tir. ISO C ++ kuralları uyarınca böyle bir ayırma, bir ayırma işlevinin çağrılmasıyla gerçekleştirilebilir . Ancak, ISO C ++ 14 olduğundan, belirli durumlarda genel ayırma işlevi (yani ) çağrılarının birleştirilmesine izin veren yeni (if-as) olmayan bir kural vardır::operator new . Dolayısıyla, dinamik ayırma işlemlerinin parçaları da otomatik nesneler gibi işlem yapmayabilir.

Tahsis fonksiyonları bellek kaynaklarını tahsis eder. Nesneler, ayırıcılar kullanılarak tahsisat bazında ayrıca tahsis edilebilir. Otomatik nesneler için doğrudan sunulur - temel belleğe erişilebilir ve diğer nesnelere (yerleştirme yoluyla new) bellek sağlamak için kullanılabilir , ancak bu serbest mağaza olarak çok mantıklı değildir, çünkü kaynakları başka bir yerde.

Diğer tüm endişeler C ++ kapsamı dışındadır. Bununla birlikte, yine de önemli olabilirler.

C ++ Uygulamaları Hakkında

C ++, birleştirilmiş etkinleştirme kayıtlarını veya bazı birinci sınıf süreklilikleri (örneğin ünlüler tarafından call/cc) ortaya çıkarmaz , etkinleştirme kayıt çerçevelerini doğrudan değiştirmenin bir yolu yoktur - uygulamanın otomatik nesneleri yerleştirmesi gerekir. Temel uygulama ile (taşınabilir olmayan) birlikte çalışma (satır içi montaj kodu gibi "yerel" taşınabilir olmayan kod) olduğunda, çerçevelerin temel tahsisinin ihmal edilmesi oldukça önemsiz olabilir. Örneğin, çağrılan işlev satır içine alındığında, çerçeveler başkalarıyla etkin bir şekilde birleştirilebilir, bu nedenle "ayırma" nın ne olduğunu göstermenin bir yolu yoktur.

Bununla birlikte, birlikte işlerliklere saygı duyulduktan sonra, işler karmaşıklaşmaktadır. Tipik bir C ++ uygulaması , yerel (ISA düzeyi makine) koduyla paylaşılan ikili sınır olarak bazı çağrı kurallarıyla birlikte ISA (yönerge kümesi mimarisi) üzerinde birlikte çalışabilme yeteneğini ortaya koyacaktır . Bu , genellikle doğrudan ISA düzeyinde bir kayıt tarafından tutulan (muhtemelen erişmek için belirli makine talimatları ile) yığın işaretçisini korurken, açıkça maliyetlidir . Yığın işaretçisi (şu anda etkin) işlev çağrısının üst çerçevesinin sınırını gösterir. Bir işlev çağrısı girildiğinde, yeni bir çerçeve gerekir ve yığın işaretçisi gerekli çerçeve boyutundan daha küçük olmayan bir değerle eklenir veya çıkarılır (ISA kuralına bağlı olarak). Sonra çerçeve söylenir tahsis edilirNe zaman işlemlerden sonra yığın işaretçi. İşlev parametreleri, çağrı için kullanılan çağrı kuralına bağlı olarak yığın çerçevesine de geçirilebilir. Çerçeve, C ++ kaynak kodu tarafından belirtilen otomatik nesnelerin (muhtemelen parametreler dahil) belleğini tutabilir. Bu tür uygulamalar anlamında, bu nesneler "tahsis edilir". Kontrol işlev çağrısından çıktığında, çerçeve artık gerekli değildir, genellikle yığın işaretçisini çağrıdan önceki duruma geri getirerek serbest bırakılır (daha önce çağrı kuralına göre kaydedilir). Bu "anlaşma" olarak görülebilir. Bu işlemler etkinleştirme kaydını etkili bir şekilde bir LIFO veri yapısı haline getirir, bu nedenle buna genellikle " (çağrı) yığını " denir .

Çoğu C ++ uygulaması (özellikle ISA düzeyindeki yerel kodu hedefleyen ve derleme dilini hemen çıktı olarak kullanan) bu tür benzer stratejiler kullandığından, bu tür kafa karıştırıcı bir "ayırma" düzeni popülerdir. Bu tür tahsisler (anlaşmaların yanı sıra) makine döngüleri harcar ve modern CPU mikro mimarileri, ortak kod deseni için donanım tarafından uygulanan karmaşık optimizasyonlara sahip olsa bile (optimize edilmemiş) çağrılar sık ​​olduğunda pahalı olabilir. Yığın motoru uygulama PUSH/ POPtalimatlar).

Ancak yine de, genel olarak, yığın çerçeve tahsisinin maliyetinin, yüzlerce (milyonlarca değilse ) serbest mağazayı çalıştıran (tamamen optimize edilmedikçe) bir tahsis fonksiyonuna yapılan çağrıdan önemli ölçüde daha düşük olduğu doğrudur . :-) yığın işaretçisini ve diğer durumları korumak için işlemler. Ayırma işlevleri genellikle barındırılan ortam tarafından sağlanan API'yi temel alır (örn. İşletim sistemi tarafından sağlanan çalışma zamanı). İşlev çağrıları için otomatik nesneleri tutma amacından farklı olarak, bu tür ayırmalar genel amaçlıdır, bu nedenle yığın gibi çerçeve yapısına sahip olmazlar. Geleneksel olarak, havuz depolama alanından yığın (veya birkaç yığın) adı verilen alan ayırırlar. "Yığın" konseptinden farklı olarak, burada "yığın" kavramı kullanılan veri yapısını göstermez;onlarca yıl önceki erken dil uygulamalarından türemiştir. (BTW, çağrı yığını genellikle program veya iş parçacığı başlangıcında yığın tarafından ortamdan sabit veya kullanıcı tarafından belirtilen boyutta ayrılır.) Kullanım durumlarının doğası, bir yığından ayırma ve ayırma işlemlerini (push veya pop'dan çok daha karmaşık hale getirir) yığın çerçeveleri) ve donanım tarafından doğrudan optimize edilmesi pek mümkün değildir.

Bellek Erişimi Üzerindeki Etkileri

Her zamanki yığın tahsisi her zaman yeni çerçeveyi en üste koyar, bu yüzden oldukça iyi bir konuma sahiptir. Bu önbellek dostudur. OTOH, serbest depoda rastgele tahsis edilen belleğin böyle bir özelliği yoktur. ISO C ++ 17'den beri, tarafından sağlanan havuz kaynağı şablonları vardır <memory>. Böyle bir arayüzün doğrudan amacı, ardışık tahsislerin sonuçlarının bellekte birbirine yakın olmasına izin vermektir. Bu, bu stratejinin genellikle modern uygulamalardaki performans için iyi olduğunu, örneğin modern mimarilerde önbellek dostu olduğunu kabul eder. Bu, tahsis yerine erişim performansı ile ilgilidir .

eşzamanlılık

Belleğe eşzamanlı erişim beklentisi yığın ve yığınlar arasında farklı etkilere neden olabilir. Bir çağrı yığını genellikle yalnızca bir C ++ uygulamasında bir yürütme iş parçacığına aittir. OTOH, yığınlar genellikle bir işlemde dişler arasında paylaşılır . Bu yığınlar için, tahsis ve yeniden konumlandırma işlevleri paylaşılan iç idari veri yapısını veri yarışından korumak zorundadır. Sonuç olarak, yığın ayırma ve ayırma işlemleri, iç eşitleme işlemleri nedeniyle ek yüke sahip olabilir.

Alan Verimliliği

Kullanım durumlarının ve dahili veri yapılarının doğası gereği yığınlar, dahili bellek parçalanmasından muzdarip olabilir , ancak yığınlar bunu yapmaz. Bunun bellek ayırma performansı üzerinde doğrudan etkisi yoktur, ancak sanal belleğe sahip bir sistemde , düşük alan verimliliği bellek erişiminin genel performansını bozabilir. Bu, HDD fiziksel bellek değişimi olarak kullanıldığında özellikle korkunçtur. Oldukça uzun bir gecikmeye neden olabilir - bazen milyarlarca döngü.

Yığın Tahsislerinin Sınırlamaları

Her ne kadar yığın tahsisleri performansta gerçekte yığın tahsislerinden daha üstün olsa da, kesinlikle yığın tahsislerinin her zaman yığın tahsislerinin yerini alabileceği anlamına gelmez.

İlk olarak, ISO C ++ ile taşınabilir bir şekilde çalışma zamanında belirtilen boyutta yığın üzerinde alan ayırmanın bir yolu yoktur. allocaG ++ 'ın VLA (değişken uzunluk dizisi) gibi uygulamalar tarafından sağlanan uzantılar vardır , ancak bunlardan kaçınmak için nedenler vardır. (IIRC, Linux kaynağı son zamanlarda VLA kullanımını kaldırmaktadır.) (Ayrıca ISO C99'un VLA'yı zorunlu kıldığını, ancak ISO C11'in desteği isteğe bağlı hale getirdiğini unutmayın.)

İkincisi, yığın boşluğu yorgunluğunu tespit etmek için güvenilir ve taşınabilir bir yol yoktur. Buna genellikle yığın taşması (hmm, bu sitenin etimolojisi) denir , ancak muhtemelen daha doğrusu yığın taşması olarak adlandırılır . Gerçekte, bu genellikle geçersiz bellek erişimine neden olur ve programın durumu daha sonra bozulur (... veya daha da kötüsü, bir güvenlik deliği). Aslında, ISO C ++ 'da "yığın" kavramı yoktur ve kaynak tükendiğinde tanımsız davranış haline getirir . Otomatik nesneler için ne kadar alan kalması gerektiğine dikkat edin.

Yığın alanı tükenirse, çok fazla etkin işlev çağrısından veya otomatik nesnelerin yanlış kullanılmasından kaynaklanabilecek yığınta çok fazla nesne tahsis edilir. Bu gibi durumlar, hataların varlığını önerebilir, örn. Doğru çıkış koşulları olmadan özyinelemeli bir işlev çağrısı.

Bununla birlikte, bazen derin yinelemeli çağrılar istenir. Sınırsız aktif çağrıların desteklenmesi gereken dillerin uygulamalarında (çağrı derinliğinin sadece toplam hafıza ile sınırlı olduğu yerlerde), (çağdaş) yerel çağrı yığınını doğrudan tipik C ++ uygulamaları gibi hedef dil aktivasyon kaydı olarak kullanmak imkansızdır . Bu soruna geçici bir çözüm bulmak için, etkinleştirme kayıtlarının oluşturulmasının alternatif yollarına ihtiyaç vardır. Örneğin, SML / NJ açıkça yığın üzerinde kareler ayırır ve kaktüs yığınları kullanır . Bu tür aktivasyon kayıt çerçevelerinin karmaşık tahsisi genellikle çağrı yığını çerçeveleri kadar hızlı değildir. Bununla birlikte, bu tür diller, uygun kuyruk özyineleme, nesne dilinde doğrudan yığın tahsisi (yani, dilde "nesne" referans olarak saklanmaz, ancak paylaşılmayan C ++ nesnelerine bire bir eşleştirilebilen yerel ilkel değerler) daha da karmaşıktır genel olarak performans cezası. Bu dilleri uygulamak için C ++ kullanırken, performans etkilerini tahmin etmek zordur.


Stl gibi daha az ve daha azı bu kavramları dağıtmaya isteklidir. Cppcon2018'deki birçok adam da heapsık sık kullanır .
力 力

@ 陳 力 "Öbek" akılda tutulan bazı özel uygulamalar ile açık olabilir, bu yüzden bazen Tamam olabilir. Yine de "genel olarak" gereksizdir.
FrankHB

Birlikte çalışma nedir?
力 力

@ 陳 力 C ++ kaynağında yer alan her türlü "doğal" kod birlikte çalışması, örneğin herhangi bir satır içi montaj kodu anlamına geliyordum. Bu, C ++ tarafından kapsanmayan varsayımlara (ABI) dayanmaktadır. COM birlikte çalışma (bazı Windows'a özgü ABI tabanlı), çoğunlukla C ++ için tarafsız olmasına rağmen, aşağı yukarı benzerdir.
FrankHB

2

Bu optimizasyonlarla ilgili genel bir nokta var.

Aldığınız optimizasyon, program sayacının aslında bu kodda bulunduğu süre ile orantılıdır.

Program sayacını örneklerseniz, zamanını nerede harcadığını öğrenirsiniz ve bu genellikle kodun küçük bir kısmındadır ve genellikle kütüphane rutinlerinde üzerinde hiçbir kontrolünüz yoktur.

Yalnızca nesnelerinizin yığın ayırmasında çok fazla zaman harcadığını görürseniz, bunları yığınlara ayırmak fark edilir şekilde daha hızlı olacaktır.


2

Yığın ayırma hemen hemen her zaman yığın ayırmadan daha hızlı veya daha hızlı olacaktır, ancak bir yığın ayırıcısının yalnızca yığın tabanlı bir ayırma tekniği kullanması kesinlikle mümkündür.

Bununla birlikte, yığına dayalı ayırmaya (ya da biraz daha iyi terimlerle, yerel ve dış ayırmaya) genel olarak bakıldığında daha büyük sorunlar vardır. Genellikle, yığın (dış) tahsisi yavaştır, çünkü birçok farklı tahsis ve tahsis modeli ile uğraşmaktadır. Kullandığınız paylaştırıcının kapsamını azaltmak (algoritma / kod için yerel hale getirmek) büyük değişiklikler yapmadan performansı artırma eğiliminde olacaktır. Ayırma düzenlerinize daha iyi bir yapı eklemek, örneğin, ayırma ve ayırma çiftleri üzerinde bir LIFO siparişini zorlamak, ayırıcıyı daha basit ve daha yapısal bir şekilde kullanarak ayırıcı performansınızı artırabilir. Veya, özel atama düzeniniz için ayarlanmış bir ayırıcı kullanabilir veya yazabilirsiniz; çoğu program sık sık birkaç ayrık boyut ayırır, bu nedenle, birkaç sabit (tercihen bilinen) boyuttaki bir lookasid tamponuna dayanan bir yığın son derece iyi performans gösterir. Windows bu nedenle düşük parçalanma yığınını kullanır.

Öte yandan, çok fazla iş parçacığınız varsa, 32 bit bellek aralığındaki yığın tabanlı ayırma da tehlikeyle doludur. Yığınlar bitişik bir bellek aralığına ihtiyaç duyar, bu nedenle daha fazla iş parçacığına sahip olduğunuzda, yığın taşması olmadan çalışması için daha fazla sanal adres alanı gerekir. Bu 64 bit ile ilgili bir sorun olmayacak, ancak uzun süren programlarda çok sayıda iş parçacığına kesinlikle zarar verebilir. Parçalanma nedeniyle sanal adres alanının tükenmesi her zaman uğraşılması gereken bir acıdır.


İlk cümlenize katılmıyorum.
brian beuning

2

Diğerlerinin söylediği gibi, yığın tahsisi genellikle çok daha hızlıdır.

Bununla birlikte, nesnelerinizin kopyalanması pahalıysa, yığına ayırmak, dikkatli değilseniz nesneleri kullandığınızda daha sonra büyük bir performansa neden olabilir.

Örneğin, yığın üzerinde bir şey tahsis edip bir kaba koyarsanız, öbek üzerinde tahsis etmek ve işaretçiyi kapta saklamak daha iyi olurdu (örneğin bir std :: shared_ptr <> ile). Nesneleri değere ve diğer benzer senaryolara göre geçiriyorsanız veya döndürüyorsanız aynı şey geçerlidir.

Mesele şu ki, yığın tahsisi genellikle birçok durumda yığın tahsisinden daha iyi olsa da, bazen hesaplama modeline en uygun olmadığında yığın tahsisatından uzaklaşırsanız, çözdüğünden daha fazla soruna neden olabilir.


2
class Foo {
public:
    Foo(int a) {

    }
}
int func() {
    int a1, a2;
    std::cin >> a1;
    std::cin >> a2;

    Foo f1(a1);
    __asm push a1;
    __asm lea ecx, [this];
    __asm call Foo::Foo(int);

    Foo* f2 = new Foo(a2);
    __asm push sizeof(Foo);
    __asm call operator new;//there's a lot instruction here(depends on system)
    __asm push a2;
    __asm call Foo::Foo(int);

    delete f2;
}

Asm'de böyle olurdu. Siz olduğunuzda func, f1ve işaretçisi f2yığına ayrılmıştır (otomatik depolama). Ve bu arada, Foo'nun f1(a1)yığın işaretçisi ( esp) üzerinde talimat etkisi yok , Üyeyi funcalmak istiyorsa tahsis edildif1 , 's talimat böyle bir şey olur: lea ecx [ebp+f1], call Foo::SomeFunc(). Yığın tahsis ettiği başka bir şey, birisinin hafızanın bir şey olduğunu düşünmesini sağlayabilir FIFO, FIFOsadece bir işleve girdiğinizde oldu, eğer işlevdeyseniz ve böyle bir şey tahsis ederseniz, int i = 0hiçbir itme olmadı.


1

Yığın tahsisinin, yığın işaretçisini, yani çoğu mimaride tek bir talimatı hareket ettirmesinden önce bahsedilmişti. Bunu genel olarak yığın ayırma durumunda gerçekleşir.

İşletim sistemi, boş bellek bölümlerini, serbest bölümün başlangıç ​​adresine ve serbest bölümün boyutuna ilişkin göstergeden oluşan yük verileri ile bağlantılı bir liste olarak tutar. X bayt bellek tahsis etmek için, bağlantı listesi gezilir ve her not sırayla ziyaret edilir, boyutunun en az X olup olmadığını kontrol eder. P> = X boyutuna sahip bir kısım bulunduğunda, P iki parçaya bölünür X ve PX boyutları. Bağlantılı liste güncellenir ve ilk parçaya ilişkin işaretçi döndürülür.

Gördüğünüz gibi, yığın ayırma, ne kadar bellek istediğinizi, belleğin ne kadar parçalanmış olduğunu vb.


1

Genel olarak, yığın tahsisi yukarıdaki hemen hemen her cevapta belirtildiği gibi yığın tahsisinden daha hızlıdır. Bir yığın itme veya pop, O (1) 'dir, oysa bir öbeğin tahsis edilmesi veya serbest bırakılması, önceki tahsislerin yürümesini gerektirebilir. Bununla birlikte, genellikle sıkı, performans yoğun döngülere tahsis edilmemelisiniz, bu nedenle seçim genellikle diğer faktörlere inecektir.

Bu ayrımı yapmak iyi olabilir: yığın üzerinde bir "yığın ayırıcı" kullanabilirsiniz. Açıkçası, tahsisin yerine gerçek tahsis yöntemini ifade etmek için yığın tahsisi alıyorum. Gerçek program yığınına çok fazla şey ayırıyorsanız, bu çeşitli nedenlerden dolayı kötü olabilir. Öte yandan, mümkün olduğunda yığın üzerinde tahsis etmek için bir yığın yöntemi kullanmak, bir tahsis yöntemi için yapabileceğiniz en iyi seçimdir.

Metrowerks ve PPC'den bahsettiğinizden beri, Wii demek istediğinizi tahmin ediyorum. Bu durumda bellek birinci sınıftır ve mümkün olan her yerde bir yığın tahsis yöntemi kullanmak, parçaları parçalar üzerinde bellek harcamamanızı garanti eder. Tabii ki, bunu yapmak "normal" yığın ayırma yöntemlerinden çok daha fazla bakım gerektirir. Her durum için ödünleşmeleri değerlendirmek akıllıca olacaktır.


1

Yığın tahsisine karşı yığın seçerken genellikle hız ve performansla ilgili hususların dikkate alınmadığına dikkat edin. Yığın bir yığın gibi davranır, yani blokları itmek ve tekrar haşlamak için son derece uygun olan anlamına gelir. Prosedürlerin yürütülmesi de yığın gibidir, ilk girilen prosedürden çıkılmalıdır. Çoğu programlama dilinde, bir prosedürde ihtiyaç duyulan tüm değişkenler yalnızca prosedürün yürütülmesi sırasında görünür olacaktır, bu nedenle bir prosedür girildikten sonra itilirler ve çıkış veya dönüşte yığından çıkarılırlar.

Şimdi yığının kullanılamayacağı bir örnek için:

Proc P
{
  pointer x;
  Proc S
  {
    pointer y;
    y = allocate_some_data();
    x = y;
  }
}

S prosedüründe bir miktar bellek ayırır ve bunu yığına koyar ve sonra S'den çıkarsanız, ayrılmış veriler yığından çıkarılır. Ancak P'deki x değişkeni de bu verilere işaret etti, bu nedenle x şimdi bilinmeyen bir içeriğe sahip yığın işaretçisinin altında bir yere işaret ediyor (yığının aşağı doğru büyüdüğünü varsayın). Yığın işaretçisi altındaki verileri silmeden yeni hareket ettirildiyse içerik hala orada olabilir, ancak yığına yeni veri ayırmaya başlarsanız, x işaretçisi aslında bunun yerine yeni verileri gösterebilir.


0

Asla erken varsayımlarda bulunmayın, çünkü diğer uygulama kodu ve kullanımı işlevinizi etkileyebilir. Yani işleve bakmak izolasyonun hiçbir faydası olmadığıdır.

Uygulama konusunda ciddiyseniz VTune alın veya benzer bir profil oluşturma aracı kullanın ve sıcak noktalara bakın.

Ketan


-1

Aslında GCC tarafından oluşturulan kod (VS de hatırlıyorum) yığını ayırma yapmak için ek yükü yok demek istiyorum .

Aşağıdaki işlevi söyleyin:

  int f(int i)
  {
      if (i > 0)
      {   
          int array[1000];
      }   
  }

Kod oluşturmak aşağıdadır:

  __Z1fi:
  Leh_func_begin1:
      pushq   %rbp
  Ltmp0:
      movq    %rsp, %rbp
  Ltmp1:
      subq    $**3880**, %rsp <--- here we have the array allocated, even the if doesn't excited.
  Ltmp2:
      movl    %edi, -4(%rbp)
      movl    -8(%rbp), %eax
      addq    $3880, %rsp
      popq    %rbp
      ret 
  Leh_func_end1:

Ne kadar yerel değişkeniniz varsa (içeride veya anahtarda olsa bile), sadece 3880 başka bir değere değişecektir. Yerel değişkeniniz yoksa, bu talimatın yürütülmesi yeterlidir. Yani tahsis edilen yerel değişkenin ek yükü yoktur.

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.