“Uçucu” çok çekirdekli sistemler için taşınabilir C kodunda herhangi bir şeyi garanti ediyor mu?


12

Bir baktıktan sonra demet ait diğer sorular ve bunların cevapları , ben izlenimini almak C "uçucu" anahtar kelime tam olarak ne anlama geldiği konusunda yaygın bir görüş birliği vardır.

Standardın kendisi bile herkesin ne anlama geldiği konusunda anlaşacak kadar net görünmüyor .

Diğer sorunların yanı sıra:

  1. Donanımınıza ve derleyicinize bağlı olarak farklı garantiler sağlıyor gibi görünüyor.
  2. O kadar kendi çalışma zamanı optimizasyonlar yapar gelişmiş bir işlemci üzerinde, derleyici optimizasyonlar ancak donanım optimizasyonlar etkiler, hatta derleyici olmadığını belli değil edebilirsiniz sen engellemek istediğini optimizasyon önler. (Bazı derleyiciler, bazı sistemlerde bazı donanım optimizasyonlarını önlemek için talimatlar oluşturur, ancak bu herhangi bir şekilde standartlaştırılmış görünmemektedir.)

Sorunu özetlemek gerekirse, (çok fazla okuduktan sonra) "değişken" gibi bir şeyi garanti ettiği anlaşılmaktadır : Değer yalnızca bir kayıttan / kayıt defterine değil, en azından çekirdeğin L1 önbelleğine, aynı sırayla okunacak / yazılacaktır. kodda okuma / yazma görünür. Ancak bu bir işe yaramaz görünmektedir, çünkü bir kayıttan / bir kayda okuma / yazma aynı iş parçacığı içinde zaten yeterlidir, L1 önbelleği ile koordinasyon diğer iş parçacıklarıyla koordinasyon hakkında daha fazla bir şey garanti etmez. Sadece L1 önbellek ile senkronizasyonun ne zaman önemli olabileceğini hayal bile edemiyorum.

KULLANIM 1
Yaygın olarak kabul edilen tek uçucu madde kullanımı, belirli bellek konumlarının I / O işlevlerine donanımsal olarak eşleştirildiği (doğrudan donanımda) ışığı kontrol eden bir parça gibi eski veya gömülü sistemler için olduğu görülmektedir. veya bir klavye tuşunun kapalı olup olmadığını söyleyen bellekte biraz (donanım tarafından doğrudan tuşa bağlı olduğu için).

Gibi görünüyor "kullanım 1" olan hedefleri çok çekirdekli sistemleri bulunmaktadır taşınabilir kodunda oluşmaz.

KULLANIM 2
"1'i kullan", kesme işleyicisi tarafından herhangi bir zamanda okunabilen veya yazılabilen bellektir (bir ışığı kontrol edebilir veya bir anahtardan bilgi depolayabilir). Ancak zaten bunun için, sisteme bağlı olarak, kesme işleyicisinin kendi bellek önbelleği ile farklı bir çekirdek üzerinde çalışabileceği ve "geçici" nin tüm sistemlerde önbellek tutarlılığını garanti etmediği sorunu var.

Yani "kullanım 2", "uçucu" nun sağlayabileceğinin ötesinde görünüyor.

KULLANIM 3
Gördüğüm diğer tartışmasız kullanım, derleyicinin fark etmediği aynı belleğe işaret eden farklı değişkenler aracılığıyla erişimin yanlış optimizasyonunu önlemektir. Ama bu muhtemelen tartışmasız çünkü insanlar bunun hakkında konuşmuyor - sadece bir söz gördüm. Ve C standardının zaten "farklı" işaretçilerin (bir işleve farklı argümanlar gibi) aynı öğeyi veya yakındaki öğeleri işaret edebileceğini ve derleyicinin bu gibi durumlarda bile çalışan kod üretmesi gerektiğini zaten düşündüğünü düşündüm. Ancak, bu konuyu en son (500 sayfa!) Standardında hızlı bir şekilde bulamadım.

Yani "3'ü kullan" belki hiç yoktur ?

Dolayısıyla sorum:

"Uçucu", çok çekirdekli sistemler için taşınabilir C kodunda hiçbir şeyi garanti etmiyor mu?


EDIT - güncelleme

En son standarda göz attıktan sonra , cevap en azından çok sınırlı bir evet gibi görünmektedir :
1. Standart, "uçucu sig_atomic_t" tipi için özel muameleyi tekrar tekrar belirtir. Bununla birlikte standart, sinyal işlevinin çok iş parçacıklı bir programda kullanılmasının tanımsız davranışa neden olduğunu da belirtmektedir. Dolayısıyla bu kullanım durumu, tek iş parçacıklı bir program ile sinyal işleyicisi arasındaki iletişim ile sınırlı görünmektedir.
2. Standart ayrıca setjmp / longjmp ile ilişkili olarak "uçucu" için açık bir anlam belirtir. (Önemli olan örnek kod diğer soru ve cevaplarda verilmiştir .)

Böylece daha kesin soru şu olur:
"Uçucu", (1) tek iş parçacıklı bir programın sinyal işleyicisinden bilgi almasına izin vermek veya (2) setjmp'a izin vermek dışında, çok çekirdekli sistemler için taşınabilir C kodunda hiçbir şeyi garanti etmez mi? ve longjmp arasında değiştirilen değişkenleri görmek için kod?

Bu hala evet / hayır sorusu.

Eğer "evet" ise, "uçucu" atlanırsa buggy haline gelen bir hata içermeyen taşınabilir kod örneği göstermeniz harika olur. Eğer "hayır" ise, bir derleyici bu çok özel iki durumun dışında "uçucu" yu çok çekirdekli hedefler için göz ardı etmekte serbesttir.


3
Taşınabilir C'de sinyaller mevcuttur; sinyal işleyici tarafından güncellenen global bir değişkene ne dersiniz? Bunun volatile, programı eşzamansız olarak değişebileceği konusunda bilgilendirmesi gerekir .
Nate Eldredge

2
@NateEldredge Global, tek başına oynak olsa da yeterince iyi değil. Atomik olması da gerekiyor.
Eugene Sh.

@EugeneSh .: Evet, elbette. Ancak eldeki soru volatileözellikle gerekli olduğuna inandığım hakkında.
Nate Eldredge

" L1 önbellek ile koordine edilirken, diğer iş parçacıkları ile koordinasyon hakkında başka bir şey garanti etmez " " L1 önbellek ile koordine " , diğer iş parçacıkları ile iletişim kurmak için yeterli değildir?
curiousguy

1
Belki alakalı, geçici olmayan C ++ önerisi , teklif burada dile getirdiğiniz endişelerin çoğunu ele alıyor ve belki de sonucu C komitesi üzerinde etkili olacak
MM

Yanıtlar:


1

Sorunu özetlemek gerekirse, (çok fazla okuduktan sonra) "uçucu" gibi bir şeyi garanti ettiği anlaşılmaktadır: Değer, yalnızca bir kayıttan / kayıt defterine değil , en azından çekirdeğin L1 önbelleğine, aynı sırayla okunacak / yazılacaktır . kodda okuma / yazma görünür .

Hayır, kesinlikle değil . Ve bu, MT güvenli kod amacıyla uçucuyu neredeyse işe yaramaz hale getirir.

Eğer öyleyse, L1 önbelleğindeki olayları sıralamak işbirliği yapabilen tipik CPU'da (anakart üzerinde çok çekirdekli veya çoklu CPU) yapmanız gereken tek şey olduğundan, birden çok iş parçacığı tarafından paylaşılan değişkenler için uçucu olabilir. C / C ++ veya Java çoklu iş parçacığının normal beklenen uygulamalarla normal bir şekilde uygulanmasını mümkün kılacak şekilde (yani, çoğu atomik veya içeriksiz muteks işlemi için büyük bir maliyet değildir).

Ama uçucu yok değil teoride ve uygulamada ya önbellekte herhangi garantili sipariş (veya "bellek görünürlük") sağlamak.

(Not: Aşağıdakiler standart belgelerin sağlam yorumlanmasına, standardın niyetine, tarihsel pratiğine ve derleyici yazarlarının beklentilerinin derinlemesine anlaşılmasına dayanmaktadır.Bu yaklaşım, gerçek kişilerin tarih, gerçek uygulamalar ve gerçek kişilerin beklentileri ve anlayışlarına dayanmaktadır. yıldız şartname yazımı olarak bilinmeyen ve birçok kez gözden geçirilmiş bir belgenin sözcüklerini ayrıştırmaktan çok daha güçlü ve güvenilir gerçek dünya.)

Pratikte, uçucu herhangi bir optimizasyon seviyesinde çalışan program için hata ayıklama bilgisini kullanma yeteneği olan hata ayıklama bilgisini ve hata ayıklama bilgisinin bu değişken nesneler için anlamlı olduğunu garanti eder:

  • ptraceuçucu nesneler içeren işlemlerden sonra sıra noktalarında anlamlı kırılma noktaları ayarlamak için (ptrace benzeri bir mekanizma) kullanabilirsiniz: tam olarak bu noktalarda kırabilirsiniz (bunun yalnızca birçok kırılma noktası herhangi bir şekilde ayarlamak istediğinizde işe yaradığını unutmayın. C / C ++ ifadesi, büyük ölçüde açılmamış bir döngüde olduğu gibi birçok farklı montaj başlangıç ​​ve bitiş noktalarına derlenebilir);
  • durdurulan bir iş parçacığı yürütülürken, kanonik temsillerine sahip olduklarından (ilgili türler için ABI'yi izleyerek) tüm uçucu nesnelerin değerini okuyabilirsiniz; uçucu olmayan bir yerel değişken atipik bir temsile sahip olabilir, örneğin. kaydırılmış bir gösterim: bir dizini dizine eklemek için kullanılan bir değişken, daha kolay dizine ekleme için tek tek nesnelerin boyutuyla çarpılabilir; ya da bir dizi elemanına bir işaretçi ile değiştirilebilir (değişkenin benzer şekilde dönüştürüldüğü tüm kullanımları olduğu sürece) (bir integralde dx'i du olarak değiştirmeyi düşünün);
  • bu nesneleri de değiştirebilirsiniz (bellek eşlemeleri izin verdiği sürece, sabit yaşam süresine sahip sabit kaliteye sahip uçucu nesneler yalnızca okunan bir bellek aralığında olabilir).

Pratikte katı ptrace yorumundan biraz daha fazla uçucu garanti: ayrıca, uçucu otomatik değişkenlerin, bir sicile tahsis edilmedikleri için yığın üzerinde bir adrese sahip olduklarını, ptrace manipülasyonlarını daha hassas hale getirecek bir kayıt tahsisini (derleyici değişkenlerin kayıtlara nasıl tahsis edildiğini açıklamak için hata ayıklama bilgisi çıktısı, ancak kayıt durumunu okumak ve değiştirmek bellek adreslerine erişmekten biraz daha fazla dahil)

Tüm program hata ayıklama kabiliyetinin, yani tüm değişkenleri en azından dizi noktalarında geçici olarak değerlendirdiğini, derleyicinin "sıfır optimizasyon" modu ile sağlandığını unutmayın, bu da aritmetik basitleştirmeler gibi önemsiz optimizasyonlar gerçekleştiren bir moddur (genellikle garanti tüm modlarda optimizasyon). Ancak uçucu olmayan optimizasyondan daha güçlüdür: x-xuçucu olmayan bir tamsayı için basitleştirilebilir, xancak uçucu bir nesne için basitleştirilemez .

Bu nedenle , bir sistem çağrısının derleyicisi tarafından kaynaktan ikiliye / montaja çeviri gibi, derleyici tarafından herhangi bir şekilde yeniden yorumlanamaz, değiştirilmez veya optimize edilmez gibi, derleneceği garanti edilen geçici araçlar . Kütüphane çağrılarının sistem çağrıları olabileceğini veya olmayabileceğini unutmayın. Birçok resmi sistem işlevi, aslında ince bir interpozisyon katmanı sunan ve genellikle sonunda çekirdeğe erteleyen kütüphane işlevidir. (Özellikle getpidçekirdeğe gitmeye gerek yoktur ve bilgileri içeren işletim sistemi tarafından sağlanan bir bellek konumunu iyi okuyabilir.)

Uçucu etkileşimler, gerçek makinenin dış dünyası ile olan etkileşimlerdir ve "soyut makineyi" takip etmelidir. Bunlar, program parçalarının diğer program parçalarıyla dahili etkileşimi değildir. Derleyici yalnızca bildiklerini, yani dahili program parçalarını akla getirebilir.

Geçici bir erişim için kod oluşturma, o bellek konumuyla en doğal etkileşimi izlemelidir: şaşırtıcı olmamalıdır. Bu, bazı geçici erişimlerin atomik olması beklendiği anlamına gelir : a'nınlong mimarideki temsilini okumanın veya yazmanın doğal yolu volatile longatomsa, derleyicinin üretmemesi gerektiği için a'nın okunması veya yazılmasının atomik olması beklenir. örneğin, uçucu nesnelere bayt bayt baytlarına erişmek için aptalca verimsiz kod .

Bunu mimariyi bilerek belirleyebilmelisiniz. Derleyici hakkında hiçbir şey bilmek zorunda değilsiniz, çünkü uçucu derleyicinin şeffaf olması gerektiği anlamına gelir .

Ancak, uçucu, belirli durumlarda bir bellek işlemi yapmak için en az optimize edilen için beklenen montajın emisyonunu zorlamaktan daha fazlasını yapmaz: uçucu semantik, genel durum semantiği anlamına gelir.

Genel durum, derleyicinin bir yapı hakkında hiçbir bilgisi olmadığında yaptığı şeydir: f.ex. dinamik dağıtma yoluyla bir lvalue üzerinde sanal bir işlevi çağırmak genel bir durumdur, derleme zamanında ifade tarafından belirtilen nesnenin türünü belirledikten sonra overrider'a doğrudan çağrı yapmak belirli bir durumdur. Derleyici her zaman tüm yapıları genel olarak ele alır ve ABI'yı izler.

Uçucu, iş parçacıklarını senkronize etmek veya "bellek görünürlüğü" sağlamak için özel bir şey yapmaz: uçucu yalnızca bir iş parçacığının yürütüldüğü ya da durdurulduğu bir iş parçasından, yani bir CPU çekirdeğinin içinden görülen soyut düzeyde garantiler sağlar :

  • uçucu olan, hangi bellek işlemlerinin ana RAM'e ulaştığı hakkında hiçbir şey söylemez (bu garantileri almak için montaj talimatları veya sistem çağrılarıyla belirli bellek önbellek türleri ayarlayabilirsiniz);
  • volatile, bellek işlemlerinin ne zaman önbellek düzeyinde (L1 bile değil) ne zaman yapılacağı konusunda herhangi bir garanti vermez .

Sadece ikinci nokta, uçlar arası iletişim sorunlarının çoğunda uçucunun yararlı olmadığı anlamına gelir; ilk nokta, CPU (lar) dışındaki donanım bileşenleri ile iletişimi içermeyen, ancak yine de bellek veri yolundaki herhangi bir programlama probleminde önemli değildir.

İş parçacığını çalıştıran çekirdeğin bakış açısından garantili davranış sağlayan uçucunun özelliği, iş parçacığının yürütme sırasının bakış açısından çalıştırılan, iş parçacığına iletilen eşzamansız sinyallerin kaynak kod düzenindeki işlemlere bakın anlamına gelir. .

İş parçacıklarınıza sinyal göndermeyi planlamıyorsanız (önceden kararlaştırılmış bir durdurma noktası bulunmayan, şu anda çalışan iş parçacıkları hakkındaki bilgilerin birleştirilmesi için son derece yararlı bir yaklaşım), uçucu sizin için değildir.


6

Ben uzman değilim, ama cppreference.com bana oldukça iyi bir bilgi gibi görünüyorvolatile . İşte özü:

Uçucu nitelikte tipte bir lvalue ifadesi ile yapılan her erişim (hem okuma hem de yazma), optimizasyon amacıyla gözlemlenebilir bir yan etki olarak kabul edilir ve soyut makinenin kurallarına (yani, tüm yazma işlemleri sonraki sıra noktasından bir süre önce). Bu, tek bir yürütme iş parçacığı içinde, uçucu erişimin, uçucu erişimden bir sıra noktası ile ayrılan, görünür başka bir yan etkiye göre optimize edilemeyeceği veya yeniden düzenlenemeyeceği anlamına gelir.

Ayrıca bazı kullanımlar sağlar:

Uçucu kullanımları

1) statik uçucu nesneler bellek eşlemeli G / Ç bağlantı noktalarını ve statik sabit uçucu nesneler gerçek zamanlı saat gibi bellek eşlemeli giriş bağlantı noktalarını modeller

2) sinyal işleyicileri ile iletişim için sig_atomic_t türündeki statik uçucu nesneler kullanılır.

3) setjmp makrosunun çağrılmasını içeren bir fonksiyonda yerel olan değişken değişkenler, longjmp dönüşlerinden sonra değerlerini koruyacağı garanti edilen tek yerel değişkenlerdir.

4) Ayrıca, uçucu değişkenler belirli optimizasyon biçimlerini devre dışı bırakmak için kullanılabilir, örneğin ölü depo eliminasyonunu veya mikrobenç işaretler için sabit katlamayı devre dışı bırakmak için.

Ve elbette, volatileiş parçacığı senkronizasyonu için yararlı olmadığından bahsediyor :

Uçucu değişkenlerin evreler arasındaki iletişim için uygun olmadığını unutmayın; atomisite, senkronizasyon veya bellek sıralaması sunmazlar. Senkronize edilmeyen veya iki senkronize edilmemiş iş parçacığındaki eşzamanlı modifikasyon olmadan başka bir evre tarafından değiştirilen uçucu bir değişkenin okuması, bir veri yarışı nedeniyle tanımlanmamış bir davranıştır.


2
Özellikle, (2) ve (3) taşınabilir kodlarla ilgilidir.
Nate Eldredge

2
@TED ​​Alan adına rağmen, bağlantı C ++ değil C hakkındaki bilgilere aittir
David Brown

@NateEldredge longjmpC ++ kodunda nadiren kullanabilirsiniz .
curiousguy

@DavidBrown C ve C ++, gözlemlenebilir bir SE'nin tanımına ve esas olarak aynı iplik ilkellerine sahiptir.
curiousguy

4

Her şeyden önce, tarihsel olarak volatileerişim ve benzeri anlamların farklı yorumlarıyla ilgili çeşitli hıçkırıklar vardı . Bu çalışmaya bakın: Uçucular Yanlış Derlenmiştir ve Bu Konuda Ne Yapmalı ?

Bu çalışmada belirtilen çeşitli sorunların yanı sıra, davranışları volatiletaşınabilir, bir yönü için tasarruf edin: bellek engelleri olarak hareket ettiklerinde . Bir bellek engeli, kodunuzun eşzamanlı olarak sıralanmamış yürütülmesini önlemek için orada bulunan bir mekanizmadır. volatileBellek bariyeri olarak kullanmak kesinlikle taşınabilir değildir.

C dilinin hafıza davranışını garanti edip etmediği volatilegörünüşte tartışmalı olsa da, kişisel olarak dilin açık olduğunu düşünüyorum. İlk olarak yan etkilerin resmi tanımına sahibiz, C17 5.1.2.3:

Bir volatilenesneye erişmek, bir nesneyi değiştirmek, bir dosyayı değiştirmek veya bu işlemlerden herhangi birini gerçekleştiren bir işlevi çağırmak , yürütme ortamının durumundaki değişiklikler olan yan etkilerdir .

Standart, sıralama terimini değerlendirme sırasını (yürütme) belirlemenin bir yolu olarak tanımlar. Tanım resmi ve hantaldır:

Daha önce sıralanan, tek bir iş parçacığı tarafından gerçekleştirilen değerlendirmeler arasında asimetrik, geçişli, çift yönlü bir ilişkidir ve bu değerlendirmeler arasında kısmi bir sıraya neden olur. A ve B'nin herhangi iki değerlendirmesi göz önüne alındığında, eğer A B'den önce sıralanırsa, A'nın yürütülmesi B'nin yürütülmesinden önce olacaktır (Tersine, A B'den önce sıralanırsa, B A'dan sonra sıralanır .) A sıralanmamışsa B'den önce veya sonra A ve B sıralanmamıştır . Değerlendirme A ve B olan belirsiz dizilenmiş bir önce ya da B da sonra dizilenmiştir zaman, ancak A) varlığı belirtilmemiş which.13 olan sekans alanına A ve B ifadelerinin değerlendirilmesi arasında, A ile ilişkili her bir değer hesaplaması ve yan etkinin, B ile ilişkili her bir değer hesaplaması ve yan etkisinden önce dizildiğini ima eder (Dizi noktalarının bir özeti Ek C'de verilmiştir).

Yukarıdakilerin TL; DR temel olarak Ayan etkiler içeren bir ifadeye sahip olmamız Bdurumunda B, bir sekanstan önce, daha sonra dizilenmesi durumunda yürütülmesi gerektiği şeklindedir A.

C kodu optimizasyonları bu bölüm aracılığıyla mümkün olur:

Soyut makinede, tüm ifadeler semantik tarafından belirtildiği şekilde değerlendirilir. Gerçek bir uygulamanın, değerinin kullanılmadığını ve gerekli hiçbir yan etkinin üretilmediğini (bir işlevi çağırmaktan veya geçici bir nesneye erişmekten kaynaklananlar da dahil olmak üzere) bir ifadenin bir kısmını değerlendirmesine gerek yoktur.

Bu, programın ifadeleri standardın başka bir yerde zorunlu kıldığı sırayla (değerlendirme sırası vb.) Değerlendirebileceği (yürütebileceği) anlamına gelir. Ancak kullanılmadığı sonucuna varılabiliyorsa bir değeri değerlendirmesi (yürütmesi) gerekmez. Örneğin, işlemin ifadeyi 0 * xdeğerlendirmesi xve değiştirmesi gerekmez 0.

Sürece bir değişken erişen bir yan etkidir. Durumda olduğunu Anlamı xolan volatile, bu olmalıdır değerlendirmek (yürütme) 0 * xsonucu her zaman 0. Optimizasyon olacak olsa izin verilmez.

Dahası, standart gözlemlenebilir davranıştan bahseder:

Uygun bir uygulamaya ilişkin en az gereksinimler şunlardır:

  • Uçucu nesnelere erişim kesinlikle soyut makinenin kurallarına göre değerlendirilir.
    / - / Bu programın gözlemlenebilir davranışıdır .

Yukarıdakilerin tümü göz önüne alındığında, uygun bir uygulama (derleyici + temel sistem) volatileyazılı C kaynağının anlambilimi aksini belirtiyorsa , nesnelerin erişimini sıralanmamış bir sırada yürütemez.

Bu, bu örnekte

volatile int x;
volatile int y;
z = x;
z = y;

Her iki atama ifadeleri gerekir değerlendirilmeli ve z = x; gereken önce değerlendirilir z = y;. Bu iki işlemi iki farklı sıra dışı çekirdeğe dış kaynak sağlayan çok işlemcili bir uygulama uygun değildir!

İkilem, derleyicilerin ön-getirme önbelleğe alma ve talimat boru hattı gibi şeyler hakkında pek bir şey yapamamasıdır, özellikle de bir işletim sisteminin üstünde çalışırken. Ve böylece derleyiciler bu sorunu programcılara teslim ederek, bellek engellerinin artık programcının sorumluluğu olduğunu söylüyorlar. C standardı, sorunun derleyici tarafından çözülmesi gerektiğini açıkça belirtirken.

Derleyici, sorunu çözmek zorunda değildir ve bu nedenle volatilebir bellek engeli olarak hareket etmek için taşınabilir değildir. Uygulama kalitesi konusu haline geldi.


@curiousguy Önemli değil.
Lundin

@curiousguy Elemesi olan veya olmayan bir tür tamsayı türü olduğu sürece önemli değil.
Lundin

Basit bir uçucu olmayan tamsayı ise, gereksiz yazımlar neden zgerçekten yürütülecek? (gibi z = x; z = y;) Değer bir sonraki ifadede silinecek.
curiousguy

@curiousguy Çünkü değişken değişkenlere yönelik okumalar , belirtilen sırayla, ne olursa olsun yürütülmelidir.
Lundin

O zaman zgerçekten iki kez atandı? "Okumaların yürütüldüğünü" nasıl biliyorsunuz?
curiousguy
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.