Setelah membaca The JSR-133 Cookbook for Compiler Writers tentang implementasi volatile, terutama bagian "Interactions with Atomic Instructions" Saya berasumsi bahwa membaca variabel volatile tanpa memperbaruinya membutuhkan LoadLoad atau penghalang LoadStore. Lebih jauh ke bawah halaman saya melihat bahwa LoadLoad dan LoadStore secara efektif tidak ada operasi pada CPU X86. Apakah ini berarti bahwa operasi pembacaan volatile dapat dilakukan tanpa pembatalan cache eksplisit pada x86, dan secepat pembacaan variabel normal (dengan mengabaikan batasan pengurutan ulang volatile)?
Saya yakin saya tidak memahami ini dengan benar. Bisakah seseorang peduli untuk mencerahkan saya?
EDIT: Saya ingin tahu apakah ada perbedaan dalam lingkungan multi-prosesor. Pada sistem CPU tunggal, CPU mungkin melihat cache utasnya sendiri, seperti yang dinyatakan oleh John V., tetapi pada sistem multi CPU harus ada beberapa opsi konfigurasi ke CPU bahwa ini tidak cukup dan memori utama harus dipukul, membuat volatile lebih lambat pada sistem multi cpu, bukan?
PS: Dalam perjalanan saya untuk mempelajari lebih lanjut tentang ini, saya tersandung tentang artikel bagus berikut, dan karena pertanyaan ini mungkin menarik bagi orang lain, saya akan membagikan tautan saya di sini:
Jawaban:
Di Intel, pembacaan volatile yang tidak bisa dibantah cukup murah. Jika kita mempertimbangkan kasus sederhana berikut:
Menggunakan kemampuan Java 7 untuk mencetak kode assembly, metode run terlihat seperti ini:
Jika Anda melihat 2 referensi untuk getstatic, yang pertama melibatkan beban dari memori, yang kedua melewatkan beban karena nilainya digunakan kembali dari register yang sudah dimuat ke dalamnya (panjangnya 64 bit dan di laptop 32 bit saya itu menggunakan 2 register).
Jika kita membuat variabel l mudah menguap, perakitan yang dihasilkan berbeda.
Dalam hal ini, kedua referensi getstatic ke variabel l melibatkan beban dari memori, yaitu nilai tidak dapat disimpan dalam register di beberapa pembacaan volatil. Untuk memastikan bahwa ada atomic membaca nilai dibaca dari memori utama ke dalam register MMX
movsd 0x6fb7b2f0(%ebp),%xmm0
membuat operasi baca instruksi tunggal (dari contoh sebelumnya kita melihat bahwa nilai 64bit biasanya membutuhkan dua 32bit membaca pada sistem 32bit).Jadi biaya keseluruhan dari pembacaan volatil kira-kira akan setara dengan beban memori dan bisa semurah akses cache L1. Namun jika inti lain menulis ke variabel volatile, baris cache akan menjadi tidak valid sehingga membutuhkan memori utama atau mungkin akses cache L3. Biaya sebenarnya akan sangat bergantung pada arsitektur CPU. Bahkan antara Intel dan AMD, protokol koherensi cache berbeda.
sumber
Secara umum, pada kebanyakan prosesor modern, beban yang mudah menguap sebanding dengan beban normal. Sebuah penyimpanan yang mudah menguap sekitar 1/3 waktu montior-enter / monitor-exit. Ini terlihat pada sistem yang koheren cache.
Untuk menjawab pertanyaan OP, penulisan volatile mahal sedangkan read biasanya tidak.
Ya, terkadang saat memvalidasi bidang, CPU bahkan mungkin tidak mengenai memori utama, sebagai gantinya memata-matai cache utas lain dan mendapatkan nilainya dari sana (penjelasan yang sangat umum).
Namun, saya mendukung saran Neil bahwa jika Anda memiliki bidang yang diakses oleh beberapa utas, Anda harus membungkusnya sebagai AtomicReference. Sebagai AtomicReference, ia mengeksekusi throughput yang kira-kira sama untuk baca / tulis, tetapi juga lebih jelas bahwa bidang akan diakses dan dimodifikasi oleh banyak utas.
Edit untuk menjawab edit OP:
Koherensi cache adalah protokol yang sedikit rumit, tetapi singkatnya: CPU akan berbagi baris cache umum yang terpasang ke memori utama. Jika CPU memuat memori dan tidak ada CPU lain yang memilikinya, CPU akan menganggapnya sebagai nilai yang paling mutakhir. Jika CPU lain mencoba memuat lokasi memori yang sama, CPU yang sudah dimuat akan mengetahui hal ini dan benar-benar membagikan referensi yang di-cache ke CPU yang meminta - sekarang CPU permintaan memiliki salinan dari memori itu di cache CPU-nya. (Tidak pernah harus mencari di memori utama untuk referensi)
Ada lebih banyak protokol yang terlibat tetapi ini memberi gambaran tentang apa yang sedang terjadi. Juga untuk menjawab pertanyaan Anda yang lain, dengan tidak adanya banyak prosesor, baca / tulis volatil sebenarnya bisa lebih cepat daripada dengan banyak prosesor. Ada beberapa aplikasi yang sebenarnya akan berjalan lebih cepat secara bersamaan dengan satu CPU kemudian beberapa.
sumber
Dalam kata-kata Model Memori Java (seperti yang didefinisikan untuk Java 5+ di JSR 133), setiap operasi - baca atau tulis - pada
volatile
variabel membuat hubungan terjadi-sebelum sehubungan dengan operasi lain pada variabel yang sama. Ini berarti bahwa compiler dan JIT dipaksa untuk menghindari pengoptimalan tertentu seperti menata ulang instruksi dalam utas atau melakukan operasi hanya dalam cache lokal.Karena beberapa pengoptimalan tidak tersedia, kode yang dihasilkan harus lebih lambat dari sebelumnya, meskipun mungkin tidak terlalu banyak.
Namun demikian, Anda tidak boleh membuat variabel
volatile
kecuali Anda tahu bahwa itu akan diakses dari beberapa utas di luarsynchronized
blok. Bahkan kemudian Anda harus mempertimbangkan apakah volatile adalah pilihan terbaik versussynchronized
,AtomicReference
dan temannya,Lock
kelas eksplisit , dll.sumber
Mengakses variabel volatil dalam banyak hal mirip dengan membungkus akses ke variabel biasa dalam blok tersinkronisasi. Misalnya, akses ke variabel volatile mencegah CPU memesan ulang instruksi sebelum dan sesudah akses, dan ini biasanya memperlambat eksekusi (meskipun saya tidak bisa mengatakan seberapa banyak).
Secara lebih umum, pada sistem multi-prosesor, saya tidak melihat bagaimana akses ke variabel volatil dapat dilakukan tanpa penalti - harus ada beberapa cara untuk memastikan penulisan pada prosesor A akan disinkronkan dengan pembacaan pada prosesor B.
sumber