Mengapa std :: atomic <T> :: is_lock_free () tidak statis dan juga constexpr?
9
Adakah yang bisa memberi tahu saya apakah std :: atomic :: is_lock_free () tidak statis dan juga constexpr? Setelah itu non-statis dan / atau sebagai non-constexpr tidak masuk akal bagi saya.
@ MaxLanghof Maksud Anda tidak semua instance akan disejajarkan dengan cara yang sama?
curiousguy
1
Mike, tidak, saya tidak sadar, tapi terima kasih untuk petunjuk ini; Ini sangat menolong bagi saya. Tapi saya bertanya pada diri sendiri mengapa ada keputusan antara is_lock_free () dan is_always_lock_free. Itu tidak mungkin karena atom yang tidak selaras, kata orang lain yang disarankan di sini, karena bahasa mendefinisikan akses yang tidak selaras memiliki perilaku yang tidak terdefinisi.
Semua tipe atom kecuali untuk std :: atomic_flag dapat diimplementasikan menggunakan mutex atau operasi penguncian lainnya, daripada menggunakan instruksi atom CPU bebas kunci. Jenis atom juga diperbolehkan bebas dari kunci, misalnya jika hanya akses memori yang disejajarkan secara alami atom pada arsitektur yang diberikan, objek yang tidak selaras dengan jenis yang sama harus menggunakan kunci.
Standar C ++ merekomendasikan (tetapi tidak mengharuskan) bahwa operasi atom bebas kunci juga bebas alamat, yaitu, cocok untuk komunikasi antara proses menggunakan memori bersama.
Seperti yang disebutkan oleh banyak orang lain, std::is_always_lock_freemungkin apa yang sebenarnya Anda cari.
Sunting: Untuk memperjelas, tipe objek C ++ memiliki nilai penyelarasan yang membatasi alamat instansnya hanya beberapa kelipatan kekuatan dua ( [basic.align]). Nilai-nilai penyelarasan ini didefinisikan untuk tipe dasar, dan tidak perlu sama dengan ukuran tipe. Mereka juga bisa lebih ketat daripada apa yang sebenarnya bisa didukung oleh perangkat keras.
Misalnya, x86 (sebagian besar) mendukung akses yang tidak selaras. Namun, Anda akan menemukan kebanyakan kompiler memiliki alignof(double) == sizeof(double) == 8untuk x86, karena akses yang tidak selaras memiliki sejumlah kelemahan (kecepatan, caching, atomicity ...). Tapi misalnya #pragma pack(1) struct X { char a; double b; };atau alignas(1) double x;memungkinkan Anda untuk memiliki "tidak selaras" double. Jadi ketika cppreference berbicara tentang "akses memori yang disejajarkan", itu mungkin dilakukan dalam hal penyelarasan alami dari tipe untuk perangkat keras, tidak menggunakan tipe C ++ dengan cara yang bertentangan dengan persyaratan penyelarasannya (yang akan menjadi UB).
32-bit x86 adalah contoh yang baik dari mana Anda menemukan ABI alignof(double)==4. Tetapi std::atomic<double>masih memiliki alignof() = 8bukannya memeriksa perataan saat runtime. Menggunakan struct dikemas yang di bawah-menyelaraskan atom istirahat ABI dan tidak didukung. (GCC untuk 32-bit x86 lebih suka memberikan keselarasan objek alami 8-byte, tetapi aturan struct-packing menimpanya dan hanya didasarkan pada alignof(T), mis. Pada Sistem i386 V. G ++ dulu memiliki bug di mana atomic<int64_t>di dalam struct mungkin bukan atom. karena hanya diasumsikan. GCC (untuk C bukan C ++) masih memiliki bug ini!)
Peter Cordes
2
Tetapi implementasi yang benar dari C ++ 20 std::atomic_ref<double>akan menolak doublesepenuhnya tidak selaras , atau akan memeriksa keselarasan pada saat runtime pada platform di mana itu legal untuk polos doubledan int64_tmenjadi kurang dari rata alami. (Karena atomic_ref<T>beroperasi pada objek yang dinyatakan sebagai dataran T, dan hanya memiliki penyelarasan minimum alignof(T)tanpa kesempatan untuk memberikan penyelarasan tambahan.)
Peter Cordes
2
Lihat gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 untuk bug libstdc ++ yang sekarang diperbaiki, dan gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 untuk bug C yang masih rusak, termasuk testcase ISO C11 murni yang menunjukkan sobek _Atomic int64_tsaat dikompilasi dengan arus gcc -m32. Pokoknya, maksud saya adalah bahwa kompiler nyata tidak mendukung atom yang tidak selaras, dan tidak melakukan pemeriksaan runtime (belum?), Jadi #pragma packatau __attribute__((packed))hanya akan mengarah pada non-atomisitas; objek masih akan melaporkan bahwa mereka adalah lock_free.
Peter Cordes
1
Tapi ya, tujuannya is_lock_free()adalah untuk memungkinkan implementasi untuk bekerja secara berbeda dari cara yang sebenarnya dilakukan; dengan pemeriksaan runtime berdasarkan penyelarasan aktual untuk menggunakan instruksi atom yang didukung HW atau menggunakan kunci.
is_lock_free tergantung pada sistem aktual dan tidak dapat ditentukan pada waktu kompilasi.
Penjelasan yang relevan:
Jenis atom juga diperbolehkan bebas dari kunci, misalnya, jika hanya akses memori yang disejajarkan secara alami adalah atom pada arsitektur yang diberikan, objek yang tidak selaras dengan jenis yang sama harus menggunakan kunci.
std::numeric_limits<int>::maxtergantung pada arsitektur, namun statis dan constexpr. Saya kira tidak ada yang salah dalam jawabannya, tetapi saya tidak membeli bagian pertama dari alasan
idclev 463035818
1
Tidak mendefinisikan bahasa akses yang tidak selaras memiliki perilaku yang tidak terdefinisi sehingga evaluasi penguncian-bebas atau tidak pada saat runtime akan menjadi omong kosong?
Bonita Montero
1
Tidak masuk akal untuk memutuskan antara akses yang selaras dan tidak selaras karena bahasa mendefinisikan yang terakhir sebagai perilaku yang tidak terdefinisi.
Bonita Montero
@BonitaMontero Ada arti "tidak selaras dalam C ++ objek alignment" dan "tidak selaras dalam apa yang disukai perangkat keras" pengertian. Itu tidak selalu sama, tetapi dalam praktiknya sering kali sama. Contoh Anda menunjukkan adalah salah satu contoh seperti mana kompilator tampaknya memiliki built-in asumsi bahwa dua adalah sama - yang hanya berarti bahwa is_lock_freeada gunanya pada compiler yang .
Max Langhof
1
Anda bisa sangat yakin bahwa atom akan memiliki keselarasan yang tepat jika ada persyaratan keselarasan.
Bonita Montero
1
Saya telah menginstal Visual Studio 2019 pada Windows-PC saya dan devenv ini juga memiliki kompiler ARMv8. ARMv8 memungkinkan akses yang tidak selaras, tetapi bandingkan dan tukar, tambah terkunci, dll. Diamanatkan untuk disejajarkan. Dan juga murni load / penyimpanan murni menggunakan ldpatau stp(load-pair atau store-pair dari register 32-bit) hanya dijamin atom ketika mereka secara alami selaras.
Jadi saya menulis sebuah program kecil untuk memeriksa apa yang dikembalikan is_lock_free () untuk penunjuk atom-arbitrary. Jadi, inilah kodenya:
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
Ini hanya returns truealias 1.
Implementasi ini memilih untuk digunakan alignof( atomic<int64_t> ) == 8sehingga setiap atomic<int64_t>selaras. Ini menghindari perlunya pemeriksaan keselarasan runtime pada setiap beban dan penyimpanan.
(Catatan editor: ini biasa; implementasi C ++ paling nyata bekerja dengan cara ini. Inilah mengapa std::is_always_lock_freesangat berguna: karena biasanya benar untuk jenis is_lock_free()yang pernah benar.)
Ya, sebagian besar implementasi memilih untuk memberi atomic<uint64_t>dan alignof() == 8karenanya mereka tidak perlu memeriksa perataan saat runtime. API lama ini memberi mereka pilihan untuk tidak melakukannya, tetapi pada HW saat ini, jauh lebih masuk akal untuk meminta penyelarasan (jika tidak, UB, mis. Non-atomisitas). Bahkan dalam kode 32-bit di mana int64_tmungkin hanya memiliki keselarasan 4-byte, atomic<int64_t>membutuhkan 8-byte. Lihat komentar saya pada jawaban lain
Peter Cordes
Dimasukkan ke dalam kata-kata yang berbeda: Jika seorang kompiler memilih untuk membuat alignofnilai untuk tipe dasar sama dengan penyelarasan "baik" dari perangkat keras, makais_lock_free akan selalu true(dan begitu juga is_always_lock_free). Kompiler Anda di sini melakukan hal ini. Tetapi API ada sehingga kompiler lain dapat melakukan hal yang berbeda.
Max Langhof
1
Anda dapat yakin bahwa jika bahasa tersebut mengatakan bahwa akses yang tidak selaras memiliki perilaku yang tidak terdefinisi, semua atom harus disejajarkan dengan benar. Tidak ada implementasi yang akan melakukan pemeriksaan runtime karena itu.
Bonita Montero
@BonitaMontero Ya, tapi tidak ada dalam bahasa yang melarang alignof(std::atomic<double>) == 1(jadi tidak akan ada "akses tidak selaras" dalam arti C ++, maka tidak ada UB), bahkan jika perangkat keras hanya dapat menjamin operasi atom bebas kunci untuk doublepada 4 atau Batas 8 byte. Compiler kemudian harus menggunakan kunci dalam kasus yang tidak selaras (dan mengembalikan nilai boolean yang sesuai is_lock_free, tergantung pada lokasi memori dari instance objek).
is_always_lock_free
?Jawaban:
Seperti yang dijelaskan pada cppreference :
Seperti yang disebutkan oleh banyak orang lain,
std::is_always_lock_free
mungkin apa yang sebenarnya Anda cari.Sunting: Untuk memperjelas, tipe objek C ++ memiliki nilai penyelarasan yang membatasi alamat instansnya hanya beberapa kelipatan kekuatan dua (
[basic.align]
). Nilai-nilai penyelarasan ini didefinisikan untuk tipe dasar, dan tidak perlu sama dengan ukuran tipe. Mereka juga bisa lebih ketat daripada apa yang sebenarnya bisa didukung oleh perangkat keras.Misalnya, x86 (sebagian besar) mendukung akses yang tidak selaras. Namun, Anda akan menemukan kebanyakan kompiler memiliki
alignof(double) == sizeof(double) == 8
untuk x86, karena akses yang tidak selaras memiliki sejumlah kelemahan (kecepatan, caching, atomicity ...). Tapi misalnya#pragma pack(1) struct X { char a; double b; };
ataualignas(1) double x;
memungkinkan Anda untuk memiliki "tidak selaras"double
. Jadi ketika cppreference berbicara tentang "akses memori yang disejajarkan", itu mungkin dilakukan dalam hal penyelarasan alami dari tipe untuk perangkat keras, tidak menggunakan tipe C ++ dengan cara yang bertentangan dengan persyaratan penyelarasannya (yang akan menjadi UB).Berikut ini informasi lebih lanjut: Apa efek aktual dari akses yang tidak selaras pada x86?
Silakan juga periksa komentar mendalam dari @Peter Cordes di bawah ini!
sumber
alignof(double)==4
. Tetapistd::atomic<double>
masih memilikialignof() = 8
bukannya memeriksa perataan saat runtime. Menggunakan struct dikemas yang di bawah-menyelaraskan atom istirahat ABI dan tidak didukung. (GCC untuk 32-bit x86 lebih suka memberikan keselarasan objek alami 8-byte, tetapi aturan struct-packing menimpanya dan hanya didasarkan padaalignof(T)
, mis. Pada Sistem i386 V. G ++ dulu memiliki bug di manaatomic<int64_t>
di dalam struct mungkin bukan atom. karena hanya diasumsikan. GCC (untuk C bukan C ++) masih memiliki bug ini!)std::atomic_ref<double>
akan menolakdouble
sepenuhnya tidak selaras , atau akan memeriksa keselarasan pada saat runtime pada platform di mana itu legal untuk polosdouble
danint64_t
menjadi kurang dari rata alami. (Karenaatomic_ref<T>
beroperasi pada objek yang dinyatakan sebagai dataranT
, dan hanya memiliki penyelarasan minimumalignof(T)
tanpa kesempatan untuk memberikan penyelarasan tambahan.)_Atomic int64_t
saat dikompilasi dengan arusgcc -m32
. Pokoknya, maksud saya adalah bahwa kompiler nyata tidak mendukung atom yang tidak selaras, dan tidak melakukan pemeriksaan runtime (belum?), Jadi#pragma pack
atau__attribute__((packed))
hanya akan mengarah pada non-atomisitas; objek masih akan melaporkan bahwa mereka adalahlock_free
.is_lock_free()
adalah untuk memungkinkan implementasi untuk bekerja secara berbeda dari cara yang sebenarnya dilakukan; dengan pemeriksaan runtime berdasarkan penyelarasan aktual untuk menggunakan instruksi atom yang didukung HW atau menggunakan kunci.Anda bisa menggunakan
std::is_always_lock_free
is_lock_free
tergantung pada sistem aktual dan tidak dapat ditentukan pada waktu kompilasi.Penjelasan yang relevan:
sumber
std::numeric_limits<int>::max
tergantung pada arsitektur, namun statis danconstexpr
. Saya kira tidak ada yang salah dalam jawabannya, tetapi saya tidak membeli bagian pertama dari alasanis_lock_free
ada gunanya pada compiler yang .Saya telah menginstal Visual Studio 2019 pada Windows-PC saya dan devenv ini juga memiliki kompiler ARMv8. ARMv8 memungkinkan akses yang tidak selaras, tetapi bandingkan dan tukar, tambah terkunci, dll. Diamanatkan untuk disejajarkan. Dan juga murni load / penyimpanan murni menggunakan
ldp
ataustp
(load-pair atau store-pair dari register 32-bit) hanya dijamin atom ketika mereka secara alami selaras.Jadi saya menulis sebuah program kecil untuk memeriksa apa yang dikembalikan is_lock_free () untuk penunjuk atom-arbitrary. Jadi, inilah kodenya:
Dan ini adalah pembongkaran isLockFreeAtomic
Ini hanya
returns true
alias1
.Implementasi ini memilih untuk digunakan
alignof( atomic<int64_t> ) == 8
sehingga setiapatomic<int64_t>
selaras. Ini menghindari perlunya pemeriksaan keselarasan runtime pada setiap beban dan penyimpanan.(Catatan editor: ini biasa; implementasi C ++ paling nyata bekerja dengan cara ini. Inilah mengapa
std::is_always_lock_free
sangat berguna: karena biasanya benar untuk jenisis_lock_free()
yang pernah benar.)sumber
atomic<uint64_t>
danalignof() == 8
karenanya mereka tidak perlu memeriksa perataan saat runtime. API lama ini memberi mereka pilihan untuk tidak melakukannya, tetapi pada HW saat ini, jauh lebih masuk akal untuk meminta penyelarasan (jika tidak, UB, mis. Non-atomisitas). Bahkan dalam kode 32-bit di manaint64_t
mungkin hanya memiliki keselarasan 4-byte,atomic<int64_t>
membutuhkan 8-byte. Lihat komentar saya pada jawaban lainalignof
nilai untuk tipe dasar sama dengan penyelarasan "baik" dari perangkat keras, makais_lock_free
akan selalutrue
(dan begitu jugais_always_lock_free
). Kompiler Anda di sini melakukan hal ini. Tetapi API ada sehingga kompiler lain dapat melakukan hal yang berbeda.alignof(std::atomic<double>) == 1
(jadi tidak akan ada "akses tidak selaras" dalam arti C ++, maka tidak ada UB), bahkan jika perangkat keras hanya dapat menjamin operasi atom bebas kunci untukdouble
pada 4 atau Batas 8 byte. Compiler kemudian harus menggunakan kunci dalam kasus yang tidak selaras (dan mengembalikan nilai boolean yang sesuaiis_lock_free
, tergantung pada lokasi memori dari instance objek).