Pertanyaan Apakah kata kunci C ++ yang mudah menguap memperkenalkan pagar memori?


aku mengerti itu volatile menginformasikan kepada compiler bahwa nilai dapat diubah, tetapi untuk mencapai fungsi ini, apakah kompilator perlu memperkenalkan pagar memori untuk membuatnya berfungsi?

Dari pemahaman saya, urutan operasi pada objek yang mudah menguap tidak dapat diurutkan ulang dan harus dipertahankan. Ini sepertinya menyiratkan beberapa pagar ingatan yang diperlukan dan bahwa tidak ada cara untuk mengatasi hal ini. Apakah saya benar mengatakan ini?


Ada diskusi yang menarik di pertanyaan terkait ini

Jonathan Wakely menulis:

... Akses ke variabel volatil yang berbeda tidak dapat diurutkan ulang oleh   kompilator selama mereka muncul dalam ekspresi penuh terpisah ... benar   volatil itu tidak berguna untuk keamanan benang, tetapi bukan karena alasan dia   memberi. Ini bukan karena kompilator mungkin mengatur ulang akses ke   objek yang mudah menguap, tetapi karena CPU mungkin mengatur ulang mereka. Atom   operasi dan penghalang memori mencegah compiler dan CPU dari   penataan kembali

Yang David Schwartz balasan di komentar:

... Tidak ada perbedaan, dari sudut pandang standar C ++,   antara kompiler melakukan sesuatu dan memancarkan kompilator   instruksi yang menyebabkan perangkat keras melakukan sesuatu. Jika CPU mungkin   menyusun ulang akses ke volatil, maka standar tidak mengharuskan hal itu   pesanan mereka dipertahankan. ...

... Standar C ++ tidak membuat perbedaan apa pun tentang apa   penataan kembali. Dan Anda tidak dapat membantah bahwa CPU dapat mengatur ulang mereka dengan tidak   efek yang dapat diamati sehingga tidak apa-apa - standar C ++ mendefinisikan mereka   memesan sebagai diamati. Compiler telah sesuai dengan standar C ++   platform jika menghasilkan kode yang membuat platform melakukan apa   standar membutuhkan. Jika standar membutuhkan akses ke volatile tidak   diurutkan ulang, kemudian platform yang ditata ulang mereka tidak sesuai. ...

Maksud saya adalah bahwa jika standar C ++ melarang kompilator dari   reordering akses ke volatil yang berbeda, pada teori bahwa   urutan akses tersebut adalah bagian dari perilaku yang dapat diamati program,   maka itu juga mengharuskan compiler untuk memancarkan kode yang melarang CPU   dari melakukannya. Standar tidak membedakan antara apa   kompilator tidak dan apa yang membuat kode kompilator membuat CPU.

Yang menghasilkan dua pertanyaan: Apakah salah satu dari mereka "benar"? Apa yang sebenarnya dilakukan oleh implementasi sebenarnya?


75
2017-10-10 19:51


asal


Jawaban:


Daripada menjelaskan apa volatile tidak, izinkan saya untuk menjelaskan kapan Anda harus menggunakan volatile.

  • Ketika berada di dalam pengatur sinyal. Karena menulis ke a volatile variabel adalah satu-satunya hal standar yang memungkinkan Anda untuk melakukannya dari dalam penangan sinyal. Karena C ++ 11 dapat Anda gunakan std::atomic untuk tujuan itu, tetapi hanya jika atom itu bebas-lock.
  • Saat berurusan dengan setjmp  menurut Intel.
  • Ketika berhadapan langsung dengan perangkat keras dan Anda ingin memastikan bahwa kompiler tidak mengoptimalkan Anda membaca atau menulis pergi.

Sebagai contoh:

volatile int *foo = some_memory_mapped_device;
while (*foo)
    ; // wait until *foo turns false

Tanpa volatile specifier, compiler diperbolehkan untuk sepenuhnya mengoptimalkan loop. Itu volatile spekter memberitahu compiler yang mungkin tidak menganggap bahwa 2 bacaan berikutnya mengembalikan nilai yang sama.

Perhatikan itu volatile tidak ada hubungannya dengan benang. Contoh di atas tidak berfungsi jika ada penulisan untaian yang berbeda *foo karena tidak ada operasi pengambilalihan yang terlibat.

Dalam semua kasus lain, penggunaan volatile harus dianggap non-portabel dan tidak lulus review kode lagi kecuali ketika berhadapan dengan pre-C ++ 11 compiler dan ekstensi compiler (seperti msvc /volatile:ms switch, yang diaktifkan secara default di bawah X86 / I64).


45
2017-10-10 21:44



Apakah kata kunci C ++ yang mudah menguap memperkenalkan pagar memori?

Compiler C ++ yang sesuai dengan spesifikasi tidak diperlukan untuk memperkenalkan pagar memori. Kompilator khusus Anda mungkin; arahkan pertanyaan Anda ke penulis kompilator Anda.

Fungsi "volatile" di C ++ tidak ada hubungannya dengan threading. Ingat, tujuan "volatile" adalah menonaktifkan optimisasi kompilator sehingga membaca dari register yang berubah karena kondisi eksogen tidak dioptimalkan. Apakah alamat memori yang ditulis oleh thread yang berbeda pada CPU yang berbeda merupakan register yang berubah karena kondisi eksogen? Tidak. Sekali lagi, jika beberapa penulis kompilator memiliki terpilih untuk menangani alamat memori yang ditulis oleh berbagai utas pada CPU yang berbeda seolah-olah mereka register berubah karena kondisi eksogen, itulah bisnis mereka; mereka tidak diharuskan untuk melakukannya. Mereka juga tidak diperlukan - bahkan jika itu memperkenalkan pagar memori - untuk, misalnya, memastikan itu setiap benang melihat a konsisten memesan membaca dan menulis yang mudah menguap.

Bahkan, volatile tidak berguna untuk melakukan threading pada C / C ++. Praktik terbaik adalah menghindarinya.

Selain itu: pagar memori merupakan detail implementasi dari arsitektur prosesor tertentu. Dalam C #, di mana volatile secara eksplisit aku s dirancang untuk multithreading, spesifikasi tidak mengatakan bahwa setengah pagar akan diperkenalkan, karena program mungkin berjalan pada arsitektur yang tidak memiliki pagar di tempat pertama. Sebaliknya, sekali lagi, spesifikasi memastikan (sangat lemah) jaminan tentang pengoptimalan apa yang akan dihindari oleh compiler, runtime dan CPU untuk meletakkan batasan tertentu (sangat lemah) tentang bagaimana beberapa efek samping akan dipesan. Pada praktiknya, pengoptimalan ini dihilangkan dengan menggunakan setengah pagar, tetapi itu merupakan detail implementasi yang dapat berubah di masa depan.

Kenyataan bahwa Anda peduli semantik volatile dalam bahasa apa pun karena mereka terkait dengan multithreading menunjukkan bahwa Anda berpikir tentang berbagi memori di seluruh thread. Pertimbangkan untuk tidak melakukan hal itu. Itu membuat program Anda jauh lebih sulit untuk dipahami dan jauh lebih mungkin untuk mengandung bug yang halus, mustahil-untuk-mereproduksi.


20
2017-10-11 13:39



Apa yang David hadapi adalah fakta bahwa standar c ++ menetapkan perilaku beberapa utas yang berinteraksi hanya dalam situasi tertentu dan semua yang lain menghasilkan perilaku yang tidak terdefinisi. Kondisi balapan yang melibatkan setidaknya satu penulisan tidak terdefinisi jika Anda tidak menggunakan variabel atom.

Akibatnya compiler dengan sempurna dalam haknya untuk membatalkan instruksi sinkronisasi karena cpu Anda hanya akan melihat perbedaan dalam program yang menunjukkan perilaku tidak terdefinisi karena sinkronisasi yang hilang.


12
2017-10-10 22:36



Pertama-tama, C + + standar tidak menjamin hambatan memori yang diperlukan untuk benar memesan baca / tulis yang non atom. volatil variabel direkomendasikan untuk digunakan dengan MMIO, penanganan sinyal, dll. Pada sebagian besar implementasi volatil tidak berguna untuk multi-threading dan umumnya tidak disarankan.

Mengenai implementasi akses yang mudah menguap, ini adalah pilihan kompilator.

Ini artikel, menggambarkan gcc perilaku menunjukkan bahwa Anda tidak dapat menggunakan objek yang mudah menguap sebagai penghalang memori untuk memesan urutan menulis ke memori yang mudah menguap.

Mengenai icc perilaku saya menemukan ini sumber mengatakan juga bahwa volatile tidak menjamin pemesanan akses memori.

Microsoft VS2013 compiler memiliki perilaku yang berbeda. Ini dokumentasi menjelaskan bagaimana volatilitas memberlakukan Release / Acquire semantik dan memungkinkan objek yang mudah menguap untuk digunakan dalam kunci / rilis pada aplikasi multi-berulir.

Aspek lain yang perlu dipertimbangkan adalah bahwa kompiler yang sama mungkin memiliki perilaku yang berbeda. berubah-ubah tergantung pada arsitektur perangkat keras yang ditargetkan. Ini pos mengenai kompiler MSVS 2013 dengan jelas menyatakan spesifikasi kompilasi dengan volatile untuk platform ARM.

Jadi jawaban saya untuk:

Apakah kata kunci C ++ yang mudah menguap memperkenalkan pagar memori?

akan menjadi: Tidak dijamin, mungkin tidak tetapi beberapa kompiler mungkin melakukannya. Anda seharusnya tidak bergantung pada fakta bahwa itu terjadi.


12
2017-10-10 19:55



Compiler hanya menyisipkan pagar memori pada arsitektur Itanium, sejauh yang saya tahu.

Itu volatile kata kunci benar-benar paling baik digunakan untuk perubahan asinkron, misalnya penangan sinyal dan register yang dipetakan-memori; biasanya alat yang salah digunakan untuk pemrograman multithread.


7
2017-10-10 19:55



Itu tergantung pada compiler mana "compiler" itu. Visual C ++ melakukannya, sejak 2005. Tetapi Standar tidak memerlukannya, jadi beberapa kompiler lain tidak.


6
2017-10-10 20:03



Ini sebagian besar dari memori, dan berdasarkan pra-C ++ 11, tanpa utas. Tapi Setelah berpartisipasi dalam diskusi tentang threading di komite, saya bisa mengatakan itu tidak pernah ada maksud dari komite itu volatile bisa digunakan untuk sinkronisasi antar utas. Microsoft mengusulkannya, tetapi proposal tidak membawa.

Spesifikasi utama dari volatile adalah bahwa akses ke volatil merupakan suatu "Perilaku yang dapat diamati", sama seperti IO. Dengan cara yang sama compiler tidak bisa menyusun ulang atau menghapus IO tertentu, tidak dapat menyusun ulang atau menghapus akses ke a objek yang mudah menguap (atau lebih tepatnya, mengakses melalui ekspresi nilai dengan jenis kualifikasi yang mudah menguap). Maksud asli dari volatile itu, pada kenyataannya, untuk mendukung memori yang dipetakan IO. "Masalah" dengan ini, bagaimanapun, adalah itu implementasi mendefinisikan apa yang merupakan "akses yang mudah berubah". Dan banyak lagi compiler mengimplementasikannya seolah-olah definisi adalah "instruksi yang membaca atau menulis ke memori telah dieksekusi ". Yang legal, meskipun tidak berguna definisi, jika implementasi menentukannya. (Saya belum menemukan yang sebenarnya spesifikasi untuk compiler apa pun.)

Boleh dibilang (dan itu argumen yang saya terima), ini melanggar maksud dari standar, karena kecuali perangkat keras mengenali alamat sebagai pemetaan memori IO, dan menghambat penataan kembali apa pun, dll., Anda bahkan tidak dapat menggunakan volatile untuk memori memetakan IO, setidaknya pada arsitektur Sparc atau Intel. Tidak pernah kurang, tidak ada satupun para comiler yang telah saya lihat (Sun CC, g ++ dan MSC) melakukan keluaran pagar atau membar instruksi. (Tentang waktu Microsoft mengusulkan memperluas aturan untuk volatileSaya pikir beberapa kompiler mereka mengimplementasikan proposal mereka, dan melakukannya memancarkan instruksi pagar untuk akses yang mudah menguap. Saya belum memverifikasi apa yang baru-baru ini terjadi kompiler lakukan, tetapi itu tidak akan mengejutkan saya jika itu tergantung pada beberapa kompilator pilihan. Versi yang saya periksa — saya rasa itu VS6.0 — tidak memancarkan pagar, bagaimanapun.)


5
2017-10-12 11:30



Tidak perlu. Volatile bukanlah sinkronisasi primitif. Itu hanya menonaktifkan pengoptimalan, yaitu Anda mendapatkan urutan pembacaan dan penulisan yang dapat diprediksi dalam utas dengan urutan yang sama seperti yang ditentukan oleh mesin abstrak. Tetapi membaca dan menulis di thread yang berbeda tidak memiliki urutan di tempat pertama, tidak masuk akal untuk berbicara tentang melestarikan atau tidak melestarikan pesanan mereka. Urutan antara theads dapat dibentuk oleh primitif sinkronisasi, Anda mendapatkan UB tanpa mereka.

Sedikit penjelasan tentang hambatan memori. CPU tipikal memiliki beberapa level akses memori. Ada pipa memori, beberapa level cache, lalu RAM dll.

Instruksi Membar menyiram pipa. Mereka tidak mengubah urutan di mana membaca dan menulis dieksekusi, itu hanya memaksa yang luar biasa untuk dieksekusi pada saat tertentu. Ini berguna untuk program multithread, tetapi tidak sebaliknya.

Cache biasanya secara otomatis koheren antar CPU. Jika seseorang ingin memastikan cache sinkron dengan RAM, diperlukan penyiraman cache. Ini sangat berbeda dengan yang membar.


5
2017-10-13 00:27