Neden uçucu var?


222

Ne geliyor volatileanahtar kelimeye? C ++ ile hangi sorunu çözer?

Benim durumumda, bilerek asla buna ihtiyaç duymadım.


İşte Singleton modeliyle ilgili olarak uçucu hakkında ilginç bir tartışma: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
chessguy

3
Derleyicinizin uçucu anahtar kelimeye büyük ölçüde bağlı olan olası yarış koşullarını algılamasını sağlayan ilginç bir teknik var, bunu http://www.ddj.com/cpp/184403766 adresinden okuyabilirsiniz .
Neno Ganchev

Bu, ne zaman volatileetkili bir şekilde kullanılabileceği, güzel layman terimleriyle bir araya getirilebileceği bir örnek . Bağlantı: publications.gbdirect.co.uk/c_book/chapter8/…
Optimize Edici

Kilidi serbest kod / çift kontrol kilitleme için kullanıyorum
paulm

Benim için anahtar kelimeden volatiledaha kullanışlı friend.
acegs

Yanıtlar:


268

volatile Eğer bellekteki bir noktadan okuyorsanız, yani tamamen ayrı bir işlem / cihaz / yazabileceğiniz herhangi bir şey okuyorsanız.

Eskiden C düzlüğünde çok işlemcili bir sistemde çift portlu ram ile çalışıyordum. Diğer adamın ne zaman yapıldığını bilmek için semafor olarak 16 bit değeri yönetilen bir donanım kullandık. Aslında bunu yaptık:

void waitForSemaphore()
{
   volatile uint16_t* semPtr = WELL_KNOWN_SEM_ADDR;/*well known address to my semaphore*/
   while ((*semPtr) != IS_OK_FOR_ME_TO_PROCEED);
}

Olmadan volatile, optimize edici döngüyü işe yaramaz olarak görür (Adam değeri asla ayarlamaz! O fındık, o koddan kurtulun!) Ve kodum semaforu almadan devam eder ve daha sonra sorunlara neden olur.


Bu durumda, uint16_t* volatile semPtrbunun yerine yazılırsa ne olur ? Bu, işaretçiyi geçici olarak işaret etmelidir (işaret edilen değer yerine), böylece işaretçinin kendisini kontrol eder, örneğin semPtr == SOME_ADDRoptimize edilmeyebilir. Ancak bu yine uçucu bir sivri değer anlamına gelir. Hayır?
Zyl

@Zyl Hayır, değil. Pratikte önerdiğiniz şey muhtemelen ne olacağıdır. Ancak teorik olarak, değerlere erişimi optimize eden bir derleyici ile sonuçlanabilir, çünkü bu değerlerin hiçbirinin değişmediğine karar verdi. Ve işaretçiye değil de değere uygulanacak geçici olsaydı, berbat olursun. Yine, olası değil, ama bugün işe yarayan davranıştan yararlanmaktan bir şeyleri doğru yapmaktan daha iyidir.
iheanyi

1
@Doug T. Daha iyi bir açıklama bu
machineaddict

3
@curiousguy yanlış karar vermedi. Verilen bilgilere dayanarak doğru kesinti yapılmıştır. Eğer uçucu işaretlemek için başarısız olursa, derleyici öyle olduğunu varsayıyoruz serbesttir uçucu olmayan . Kodu optimize ederken derleyici bunu yapar. Daha fazla bilgi, yani, söz konusu verilerin aslında değişken olması durumunda, bu bilgileri sağlamak programcıya aittir. Hatalı bir derleyici tarafından iddia ettiğiniz şey gerçekten sadece kötü programlama.
iheanyi

1
@curiousguy hayır, yalnızca geçici anahtar kelimenin bir kez görünmesi her şeyin birdenbire değişken olduğu anlamına gelmez. Derleyicinin doğru şeyi yaptığı ve programcının yanlış beklediği şeyin aksine bir sonuç elde ettiği bir senaryo verdim. Tıpkı "en sinir bozucu ayrıştırma" derleyici hatasının işareti değil gibi, burada da böyle değildir.
iheanyi

82

volatilebellek eşlemeli bir donanım aygıtını okumanız veya yazmanız gereken gömülü sistemler veya aygıt sürücüleri geliştirirken gereklidir. Belirli bir aygıt kaydının içeriği her zaman değişebilir, bu nedenle volatilebu tür erişimlerin derleyici tarafından optimize edilmemesini sağlamak için anahtar kelimeye ihtiyacınız vardır.


9
Bu yalnızca gömülü sistemler için değil, tüm aygıt sürücülerinin geliştirilmesi için de geçerlidir.
Mladen Janković

Aynı adresi iki kez okuduğunuz 8bit ISA veriyolunda ihtiyaç duyduğum tek zaman - derleyicinin bir hatası vardı ve görmezden geldi (erken Zortech c ++)
Martin Beckett

Harici cihazların kontrolü için uçuculuk çok nadiren yeterlidir. Anlamsallığı modern MMIO için yanlış: çok fazla nesneyi uçucu hale getirmelisiniz ve optimizasyona zarar veriyor. Ancak modern MMIO, bir bayrak bu kadar geçici olarak ayarlanıncaya kadar normal bellek gibi davranır. Birçok sürücü hiçbir zaman uçucu kullanmaz.
curiousguy

69

Bazı işlemciler, 64 bitten fazla hassasiyete sahip kayan nokta yazmaçlarına sahiptir (örn. SSE'siz 32 bit x86, bkz. Peter'ın yorumu). Bu şekilde, çift kesinlikli sayılar üzerinde birkaç işlem gerçekleştirirseniz, aslında her ara sonucu 64 bit olarak kesmekten daha yüksek kesinlikli bir yanıt alırsınız.

Bu genellikle mükemmeldir, ancak derleyicinin kayıt ve atamaların nasıl atandığına bağlı olarak, aynı girdilerdeki aynı işlemler için farklı sonuçlara sahip olacağınız anlamına gelir. Tutarlılığa ihtiyacınız varsa, her işlemi geçici anahtar sözcüğü kullanarak belleğe dönmeye zorlayabilirsiniz.

Cebirsel anlam ifade etmeyen ancak Kahan toplamı gibi kayan nokta hatasını azaltan bazı algoritmalar için de yararlıdır. Cebirsel olarak bir nop, bu nedenle bazı ara değişkenler uçucu olmadıkça genellikle yanlış optimize edilir.


5
Sayısal türevleri hesapladığınızda, x + h - x == h'nin hh = x + h - x değerini uçucu olarak tanımladığından emin olmak, böylece uygun bir deltanın hesaplanmasını sağlamak yararlı olur.
Alexandre

5
+1, gerçekten benim tecrübelerime göre, kayan noktalı hesaplamaların Hata Ayıklama ve Bırakmada farklı sonuçlar ürettiği bir durum vardı, bu nedenle bir yapılandırma için yazılmış birim testleri diğeri için başarısız oldu. Daha fazla hesaplama yapmaya devam etmeden önce FPU hassasiyetinden 64 bit (RAM) hassasiyetine indirilmesini sağlamak için bunu volatile doublesadece bir kayan nokta değişkeni olarak bildirerek çözdük double. Kayan nokta hatasının daha fazla abartılması nedeniyle sonuçlar büyük ölçüde farklıydı.
Serge Rogatch

"Modern" tanımınız biraz kapalı. SSE / SSE2'yi önleyen yalnızca 32 bit x86 kodu bundan etkilenir ve 10 yıl önce bile "modern" değildi. MIPS / ARM / POWER, 64 bit donanım kayıtlarına sahiptir ve SSE2 ile x86 da vardır. C ++ uygulamaları x86-64 her zaman SSE2 kullanır ve derleyiciler g++ -mfpmath=sse32 bit x86 için de kullanmak gibi seçeneklere sahiptir . X87 kullanırken bile her yerdegcc -ffloat-store yuvarlamayı zorlamak için kullanabilirsiniz veya x87 hassasiyetini 53 bit mantis olarak ayarlayabilirsiniz : randomascii.wordpress.com/2012/03/21/… .
Peter Cordes

Ama yine de iyi bir yanıt, eski x87 kod-gen için, volatileher yerde faydalarını kaybetmeden birkaç belirli yerde yuvarlamayı zorlamak için kullanabilirsiniz .
Peter Cordes

1
Yoksa yanlış olanı tutarsızla karıştırır mıyım?
Chipster


23

Kilitsiz veri yapılarını uygularken uçucu KULLANMALISINIZ. Aksi takdirde derleyici, semantiği değiştirecek olan değişkene erişimi optimize etmekte serbesttir.

Başka bir deyişle, uçucu derleyiciye bu değişkene erişimin fiziksel bir bellek okuma / yazma işlemine karşılık gelmesi gerektiğini söyler.

Örneğin, InterlockedIncrement, Win32 API'de şu şekilde bildirilir:

LONG __cdecl InterlockedIncrement(
  __inout  LONG volatile *Addend
);

InterlockedIncrement'ı kullanabilmek için değişken bir değişken bildirmeniz GEREKMEZ.
curiousguy

C + + 11'in sağladığı std::atomic<LONG>bu cevap artık geçersizdir, böylece saf yüklerin / saf mağazaların optimize edilmesini veya yeniden düzenlenmesini veya başka herhangi bir şeyle karşılaşmadan problemsiz olarak daha güvenli bir şekilde yazabilirsiniz.
Peter Cordes

10

1990'ların başında üzerinde çalıştığım büyük bir uygulama, setjmp ve longjmp kullanarak C tabanlı özel durum işleme içeriyordu. Değişken anahtar kelime, değerleri "catch" deyimi olarak işlev gören kod bloğunda korunması gereken değişkenler için gerekliydi, bu değişkenler yazmaçlarda saklanıp longjmp tarafından silinmelidir.


10

Standart C'de, kullanılacak yerlerden biri volatilesinyal işleyicisidir. Aslında, Standart C'de, bir sinyal işleyicide güvenli bir şekilde yapabileceğiniz tek şey bir volatile sig_atomic_tdeğişkeni değiştirmek veya hızlı bir şekilde çıkmaktır. Gerçekten de, AFAIK, volatiletanımlanmamış davranışlardan kaçınmak için Standart C'deki tek kullanım alanıdır.

ISO / IEC 9899: 2011 §7.14.1.1 signalİşlev

¶5 Sinyal, abortveya raiseişlevinin çağrılmasından farklı olarak ortaya çıkarsa, sinyal işleyicisi, bir değer atamak dışında kilitsiz bir atomik nesne olmayan statik veya iş parçacığı depolama süresi olan herhangi bir nesneye başvuruyorsa, davranış tanımsızdır. olarak bildirilen bir nesneye volatile sig_atomic_tveya sinyal işleyici, standart kitaplıkta abortişlev, _Exitişlev, quick_exitişlev veya signalilk bağımsız değişkeni olan işlevin çağrılmasına neden olan sinyale karşılık gelen sinyal numarasına eşit olan herhangi bir işlevi çağırır . işleyicisi. Ayrıca, böyle bir signalişlev çağrısı SIG_ERR dönüşü ile sonuçlanırsa, değeri errnobelirsizdir. 252)

252) Eşzamansız bir sinyal işleyici tarafından herhangi bir sinyal üretilirse, davranış tanımsızdır.

Bu, Standart C'de şunları yazabileceğiniz anlamına gelir:

static volatile sig_atomic_t sig_num = 0;

static void sig_handler(int signum)
{
    signal(signum, sig_handler);
    sig_num = signum;
}

ve çok fazla değil.

POSIX, bir sinyal işleyicide neler yapabileceğiniz konusunda çok daha yumuşaktır, ancak yine de sınırlamalar vardır (ve sınırlamalardan biri, Standart G / Ç kütüphanesinin - printf()ve diğerlerinin güvenle kullanılamamasıdır).


7

Gömülü bir geliştirme için, bir kesme işleyicisinde değiştirilebilen bir değişkeni kontrol eden bir döngü var. "Uçucu" olmadan, döngü bir noop haline gelir - derleyicinin anlayabildiği kadarıyla, değişken asla değişmez, bu nedenle kontrolü optimize eder.

Aynı şey, daha geleneksel bir ortamda farklı bir iş parçacığında değiştirilebilen bir değişken için de geçerlidir, ancak sık sık senkronizasyon çağrıları yaparız, bu nedenle derleyici optimizasyonla o kadar özgür değildir.


7

Derleyici kod adım adım olarak görmek mümkün bir değişken optimize uzak ısrar zaman hata ayıklama yapıları kullandım.


7

Amaçlandığı gibi kullanmanın yanı sıra, uç şablon (şablon) meta programlamasında kullanılır. Uçucu öznitelik (const gibi) aşırı yük çözünürlüğünde yer aldığından, yanlışlıkla aşırı yüklemeyi önlemek için kullanılabilir.

template <typename T> 
class Foo {
  std::enable_if_t<sizeof(T)==4, void> f(T& t) 
  { std::cout << 1 << t; }
  void f(T volatile& t) 
  { std::cout << 2 << const_cast<T&>(t); }

  void bar() { T t; f(t); }
};

Bu yasal; her iki aşırı yüklenme de potansiyel olarak çağrılabilir ve neredeyse aynıdır. Aşırı yükteki döküm volatileyasaldır, çünkü bar Tyine de kalıcı değildir . Bununla birlikte, volatilesürüm kesinlikle daha kötüdür, bu nedenle uçucu yoksa hiçbir zaman aşırı yük çözünürlüğünde seçilmez f.

Kodun asla volatilebellek erişimine bağlı olmadığını unutmayın .


Bunu bir örnekle açıklar mısınız? Gerçekten daha iyi anlamama yardımcı olur. Teşekkürler!
batbrat

" Uçucu aşırı yükte döküm" Bir döküm açık bir dönüşümdür. Bu bir SYNTAX yapısı. Birçok insan bu karışıklığı yapar (standart yazarlar bile).
curiousguy

6
  1. bazı kilitlenmemiş veri yapılarının yanı sıra kilitlenmeleri uygulamak için de kullanmalısınız
  2. atomik işlemlerle / talimatlarla kullanın
  3. Derleyicinin hatalarının üstesinden gelmeme yardımcı oldu (optimizasyon sırasında yanlış oluşturulan kod)

5
Bir kütüphane, derleyici özleri veya satır içi montaj kodu kullanmanız daha iyi olur. Uçucu güvenilir değildir.
Zan Lynx

1
1 ve 2'nin her ikisi de atomik işlemlerden yararlanır, ancak uçucu atomik semantik sağlamaz ve platforma özgü atomik uygulamalar uçucu kullanma ihtiyacını ortadan kaldıracaktır, bu nedenle 1 ve 2 için, bunlar için uçucuya İHTİYACINIZ YOKTUR.

Kim atomik anlambilim sağlayan uçucu hakkında bir şey söylüyor? Atomik operasyonlarla uçucu KULLANMAK gerektiğini söyledim ve bunun doğru olduğunu düşünmüyorsanız, win32 API'nin birbirine bağlı operasyonlarının beyanlarına bakın (bu adam da cevabında bunu açıkladı)
Mladen Janković

4

volatileAnahtar kelime derleyici tarafından tespit edilemeyen yollarla değiştirebilir nesneler üzerinde herhangi optimizasyon uygulamaktan derleyici engellemeye yöneliktir.

volatileDeğerleri herhangi bir zamanda geçerli kodun kapsamı dışındaki kodlarla değiştirilebildiği için, bildirilen nesneler optimizasyondan çıkarılır. Sistem, bir volatilenesnenin geçerli değerini , daha önceki bir talimat aynı nesneden bir değer istemiş olsa bile, değerini istenen kayıt noktasında tutmak yerine bellek konumundan her zaman okur .

Aşağıdaki durumları düşünün

1) Kapsam dışında bir kesme servisi rutini tarafından değiştirilen global değişkenler.

2) Çok iş parçacıklı bir uygulama içindeki global değişkenler.

Uçucu niteleyici kullanmazsak, aşağıdaki sorunlar ortaya çıkabilir

1) Optimizasyon açıkken kod beklendiği gibi çalışmayabilir.

2) Kesmeler etkinleştirildiğinde ve kullanıldığında kod beklendiği gibi çalışmayabilir.

Uçucu: Bir programcının en iyi arkadaşı

https://en.wikipedia.org/wiki/Volatile_(computer_programming)


Gönderdiğiniz bağlantı son derece güncel değil ve mevcut en iyi uygulamaları yansıtmıyor.
Tim Seguine

2

Uçucu anahtar kelimenin derleyiciye bazı değişkenlere (bir iş parçacığı veya bir kesme yordamı tarafından değiştirilebilir) erişimi optimize etmemesini söylemesi için kullanılması yanı sıra, bazı derleyici hatalarını kaldırmak için de kullanılabilir - EVET olmak ---.

Örneğin, derleyici bir değişkenin değeri ile ilgili yanlış varsayımlar yaparken gömülü bir platformda çalıştım. Kod optimize edilmemişse, program tamam çalışır. Optimizasyonlarda (kritik bir rutin olduğu için gerçekten gerekliydi) kod düzgün çalışmaz. Tek çözüm (çok doğru olmasa da) 'hatalı' değişkeni geçici olarak ilan etmekti.


3
Derleyicinin uçuculara erişimi optimize etmediği fikri yanlış bir varsayımdır. Standart optimizasyonlar hakkında hiçbir şey bilmiyor. Derleyici, standardın belirlediği şeye uymakla yükümlüdür, ancak normal davranışa müdahale etmeyen tüm optimizasyonları yapmak ücretsizdir.
Terminus

3
Deneyimlerime göre, gcc kolundaki tüm optimizasyon "hatalarının"% 99.9'u programcı tarafında hatalardır. Bunun bu cevap için geçerli olup olmadığı hakkında hiçbir fikrim yok. Genel konu hakkında bir rant
odinthenerd

@Terminus " Derleyicinin uçuculara erişimi optimize etmediği fikri yanlış bir varsayımdır " Kaynak?
curiousguy

2

Programınız volatileanahtar kelime olmadan da çalışıyor gibi görünüyor ? Belki de nedeni budur:

Daha önce de belirtildiği gibi, volatileanahtar kelime

volatile int* p = ...;  // point to some memory
while( *p!=0 ) {}  // loop until the memory becomes zero

Ancak harici veya satır içi olmayan bir fonksiyon çağrıldığında neredeyse hiçbir etkisi yok gibi görünüyor. Örneğin:

while( *p!=0 ) { g(); }

Sonra volatilehemen hemen aynı sonuçla veya sonuçsuz sonuç elde edilir.

G () tamamen satır içine alınabildiği sürece, derleyici devam eden her şeyi görebilir ve bu nedenle optimize edebilir. Ancak program, derleyicinin neler olduğunu göremediği bir yeri aradığında, derleyicinin artık herhangi bir varsayım yapması güvenli değildir. Böylece derleyici her zaman doğrudan bellekten okunan kod üretecektir.

Ancak g () işleviniz satır içi hale geldiğinde (açık değişiklikler veya derleyici / bağlayıcı akıllılığı nedeniyle) günün volatileanahtar kelimesini unuttuysanız kodunuz bozulabilir !

Bu nedenle volatile, programınız sorunsuz çalışıyor gibi görünse bile anahtar kelimeyi eklemenizi öneririm . Gelecekte yapılacak değişikliklerde niyeti daha net ve daha sağlam hale getirir.


Bir işlevin, anahat işlevine hala bir başvuru oluştururken (bağlantı zamanında çözülür) kodunun satır içine alınabileceğini unutmayın; bu kısmen eğimli bir özyinelemeli işlev olacaktır. Bir fonksiyon ayrıca derleyici tarafından semantik "inline" olabilir, yani derleyici yan etkilerin ve sonucun hala yan yana olmasa da kaynak koduna göre olası yan etkiler ve sonuçlar içinde olduğunu varsayar. Bu, bir işletmenin tüm tanımlarının etkili bir şekilde eşdeğer olacağını (tam olarak özdeş değilse) ifade eden "etkili Bir Tanımlama Kuralı" na dayanır.
curiousguy

Vücudu derleyici tarafından görülebilen bir işlevle (küresel optimizasyon ile bağlantı zamanında bile) bir çağrının satırdan (veya anlamsalının "satır içi") satırlanmasını önlemek, volatilenitelikli bir işlev işaretçisi kullanılarak mümkündür :void (* volatile fun_ptr)() = fun; fun_ptr();
curiousguy

2

C'nin ilk günlerinde derleyiciler, okuma ve yazma değerlerini bellek işlemleri olarak yorumlayacak ve kodda okunan ve yazılanlarla aynı sırayla gerçekleştirilecekleri yorumlayacaktır. Derleyicilere işlemleri yeniden sıralamak ve birleştirmek için belirli bir özgürlük verilmişse, birçok durumda verimlilik büyük ölçüde artırılabilir, ancak bununla ilgili bir sorun vardı. Operasyonlar bile çoğu zaman belirli bir sırayla belirtildi, çünkü bunları bir sırayla belirtmek gerekiyordu ve bu nedenle programcı eşit derecede iyi alternatiflerden birini seçti, bu her zaman böyle değildi. Bazen belirli işlemlerin belirli bir sırada gerçekleşmesi önemlidir.

Hangi sekanslama ayrıntılarının tam olarak önemli olduğu, hedef platforma ve uygulama alanına bağlı olarak değişecektir. Standart, özellikle ayrıntılı kontrol sağlamak yerine, basit bir model seçmiştir: bir dizi erişim nitelikli olmayan değerlerle yapılırsa volatile, bir derleyici bunları uygun gördüğü şekilde yeniden sıralayabilir ve birleştirebilir. volatileNitelikli bir değerleme ile bir işlem yapılırsa , kaliteli bir uygulama, standart olmayan sözdizimi kullanılmasına gerek kalmadan, amaçlanan platformunu ve uygulama alanını hedefleyen kodla gerekli her türlü sipariş garantisini sunmalıdır.

Ne yazık ki, programcıların hangi garantilere ihtiyaç duyacağını tanımlamak yerine, birçok derleyici bunun yerine Standart tarafından belirtilen asgari garantileri sunmayı seçmiştir. Bu olması volatilegerekenden çok daha az kullanışlı hale geliyor. Örneğin, gcc veya clang'da, temel bir "dağıtma muteksi" uygulamak isteyen bir programcı [bir muteksi edinip serbest bırakan bir görevin, diğer görev tamamlanana kadar bir daha yapmayacağı] dört şeyden:

  1. Muteksin elde edilmesini ve yayınlanmasını, derleyicinin satır içi yapamayacağı ve Tüm Program Optimizasyonunu uygulayamayacağı bir işleve yerleştirin.

  2. volatileMuteks tarafından korunan tüm nesneleri muteks'i aldıktan sonra ve serbest bırakmadan önce tüm erişim gerçekleşirse gerekli olmaması gereken bir şey olarak nitelendirin.

  3. Kalifiye olmayan tüm nesneler sanki kodu oluşturmak için derleyici zorlamak için optimizasyon düzeyini 0 kullanın registervardır volatile.

  4. Gcc'ye özgü yönergeler kullanın.

Buna karşılık, icc gibi sistem programlama için daha uygun olan daha yüksek kaliteli bir derleyici kullanıldığında, başka bir seçenek daha olacaktır:

  1. volatileNitelikli bir yazma işleminin, edinme veya serbest bırakma gereken her yerde gerçekleştirildiğinden emin olun .

Temel bir "teslim mutex" elde etmek için bir volatileokuma (hazır olup olmadığını görmek için) gerekir ve volatileayrıca bir yazma gerektirmemelidir (diğer taraf geri verilene kadar tekrar edinmeye çalışmaz) ama anlamsız bir volatileyazma gerçekleştirmek hala gcc veya clang altında mevcut seçeneklerden daha iyidir.


1

Hatırlatmam gereken bir kullanım, sinyal işleyici işlevinde, global bir değişkene erişmek / değiştirmek istiyorsanız (örneğin, exit = true olarak işaretleyin), bu değişkeni 'uçucu' olarak bildirmeniz gerekir.


1

Tüm cevaplar mükemmel. Ama bunun da ötesinde, bir örnek paylaşmak istiyorum.

Aşağıda küçük bir cpp programı var:

#include <iostream>

int x;

int main(){
    char buf[50];
    x = 8;

    if(x == 8)
        printf("x is 8\n");
    else
        sprintf(buf, "x is not 8\n");

    x=1000;
    while(x > 5)
        x--;
    return 0;
}

Şimdi, yukarıdaki kodun derlemesini oluşturalım (ve yalnızca derlemenin burada ilgili olan bölümlerini yapıştıracağım):

Montaj oluşturma komutu:

g++ -S -O3 -c -fverbose-asm -Wa,-adhln assembly.cpp

Ve meclis:

main:
.LFB1594:
    subq    $40, %rsp    #,
    .seh_stackalloc 40
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC0(%rip), %rcx     #,
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:10:         printf("x is 8\n");
    call    _ZL6printfPKcz.constprop.0   #
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    movl    $5, x(%rip)  #, x
    addq    $40, %rsp    #,
    ret 
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Derlemede derleme kodunun oluşturulmadığını görebilirsiniz, sprintfçünkü derleyici xprogramın dışında değişmeyeceğini varsaymıştır . Aynı şey whiledöngü için de geçerlidir. whilederleyici bir işe yaramaz kod olarak gördüler ve böylece doğrudan atanmış çünkü döngü tamamen nedeniyle optimizasyonu çıkarıldı 5için x(bkz movl $5, x(%rip)).

Sorun, harici bir işlem / donanım ve ile xarasındaki bir yerde değerini değiştirirse oluşur . Bloğun çalışmasını beklerdik ama maalesef derleyici bu kısmı düzeltti.x = 8;if(x == 8)else

Şimdi, içeri, bu çözmek için assembly.cpp, bizi değiştirmesine izin int x;için volatile int x;ve hızlı bir şekilde oluşturulan derleme kod bakın:

main:
.LFB1594:
    subq    $104, %rsp   #,
    .seh_stackalloc 104
    .seh_endprologue
 # assembly.cpp:5: int main(){
    call    __main   #
 # assembly.cpp:7:     x = 8;
    movl    $8, x(%rip)  #, x
 # assembly.cpp:9:     if(x == 8)
    movl    x(%rip), %eax    # x, x.1_1
 # assembly.cpp:9:     if(x == 8)
    cmpl    $8, %eax     #, x.1_1
    je  .L11     #,
 # assembly.cpp:12:         sprintf(buf, "x is not 8\n");
    leaq    32(%rsp), %rcx   #, tmp93
    leaq    .LC0(%rip), %rdx     #,
    call    _ZL7sprintfPcPKcz.constprop.0    #
.L7:
 # assembly.cpp:14:     x=1000;
    movl    $1000, x(%rip)   #, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_15
    cmpl    $5, %eax     #, x.3_15
    jle .L8  #,
    .p2align 4,,10
.L9:
 # assembly.cpp:16:         x--;
    movl    x(%rip), %eax    # x, x.4_3
    subl    $1, %eax     #, _4
    movl    %eax, x(%rip)    # _4, x
 # assembly.cpp:15:     while(x > 5)
    movl    x(%rip), %eax    # x, x.3_2
    cmpl    $5, %eax     #, x.3_2
    jg  .L9  #,
.L8:
 # assembly.cpp:18: }
    xorl    %eax, %eax   #
    addq    $104, %rsp   #,
    ret 
.L11:
 # assembly.cpp:10:         printf("x is 8\n");
    leaq    .LC1(%rip), %rcx     #,
    call    _ZL6printfPKcz.constprop.1   #
    jmp .L7  #
    .seh_endproc
    .p2align 4,,15
    .def    _GLOBAL__sub_I_x;   .scl    3;  .type   32; .endef
    .seh_proc   _GLOBAL__sub_I_x

Burada sprintf, printfve whileloop için derleme kodlarının oluşturulduğunu görebilirsiniz. Avantajı, xdeğişken bazı harici program veya donanım tarafından değiştirilirse sprintf, kodun bir kısmının yürütülmesidir. Ve benzer şekilde whiledöngü şimdi meşgul beklemek için kullanılabilir.


0

Diğer cevaplar, aşağıdakileri yapmak için bazı optimizasyonlardan kaçındığından bahsetmektedir:

  • bellek eşlemeli kayıtları (veya "MMIO") kullanın
  • aygıt sürücüleri yaz
  • programlarda daha kolay hata ayıklamaya izin ver
  • kayan noktalı hesaplamaları daha belirleyici hale getirme

Dışardan gelip öngörülemeyen bir değere ihtiyaç duyduğunuzda ve bilinen bir değere dayalı derleyici optimizasyonlarından kaçındığınızda ve bir sonuç gerçekten kullanılmadığında, ancak hesaplanmanız gerektiğinde veya kullanıldığında uçucu olması önemlidir, ancak bir kıyaslama için bunu birkaç kez hesaplamak istiyorsunuz ve hesaplamaların kesin noktalarda başlayıp bitmesi gerekiyor.

Uçucu bir okuma bir giriş işlemi gibidir ( scanfveya kullanımı gibi cin): değer programın dışından geliyor gibi görünür, bu nedenle değere bağımlı olan herhangi bir hesaplamanın bundan sonra başlaması gerekir .

Geçici bir yazma işlemi bir çıktı işlemi gibidir ( printfveya kullanımı gibi cout): değer programın dışında iletilmiş gibi görünür, bu nedenle değer bir hesaplamaya bağlıysa, daha önce bitirilmesi gerekir .

Bu nedenle , karşılaştırmaları değerlendirmek ve zaman ölçümünü anlamlı hale getirmek için bir çift uçucu okuma / yazma kullanılabilir .

Uçucu olmadan, hesaplamanız daha önce derleyici tarafından başlatılabilirdi, çünkü hiçbir şey hesaplamaların zaman ölçümü gibi işlevlerle yeniden sıralanmasını engellemeyecektir .

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.