Я использую 8-байтовый сегмент общей памяти в PHP, используя функции shmop_*. Глядя на исходный код PHP, я вижу, что функции shmop_write() и shmop_read() используют memcpy() для заполнения/чтения этих 8 байт.
Интересно, достаточно ли на 64-битной Linux-машине memcpy() умен, чтобы скопировать все двойное слово за один раз (одну инструкцию), что делает операции чтения и записи эффективными атомарными.
Я не уверен, что эти сегменты общей памяти всегда выровнены по 64-битному принципу.
В качестве примера:
$shmop = shmop_open(ftok(__FILE__, 'R'), 'c', 0644, 8);
shmop_write($shmop, "abcdefgh", 0); // <= is this operation atomic
$a = shmop_read($shmop, 0, 8); // <= is this operation atomic
🤔 А знаете ли вы, что...
PHP широко используется для разработки систем управления контентом, таких как WordPress и Joomla.
Если memcpy
, о котором вы говорите, это функция C, сборка glibc x86-64 memcpy
for size=8 разветвляется на случай 8-15, который создает две полностью перекрывающиеся 8-байтовые копии. (https://codebrowser.dev/glibc/glibc/sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S.html#308).
Для больших размеров до 15 они будут перекрываться посередине на меньшее количество байтов. Так что shmop_write
могло случиться как до, так и после очередного магазина. Для shmop_read
имеет значение только второе прочтение; результат первого перезаписывается, поэтому он фактически похож на C++ std::atomic .load(acquire)
на x86-64.
Это деталь реализации, которая ничем не гарантирована. Однако выбор имеет смысл: если бы они использовали диапазон size=5-8, который либо частично перекрывался, либо не перекрывался вообще для двух 4-байтовых копий, 8-байтовая перезагрузка результата привела бы к остановке пересылки в хранилище, поскольку он перекрывается. два отдельных магазина.
Я не знаю, что делает MUSL, другие библиотеки C или другие архитектуры.
Другие архитектуры с эффективными невыровненными загрузками/сохранениями (например, AArch64) могут делать что-то подобное для размеров, меньших 2x целочисленного регистра. Но это не гарантировано. Если бы он использовал эту стратегию, это было бы похоже на .load(relaxed)
, поскольку AArch64 слабо упорядочен.
Это, конечно, предполагает, что общая память достаточно выровнена, чтобы простые загрузки/сохранения были атомарными на целевой машине. (Локальная переменная, из/в которую вы копируете, не имеет значения, поскольку она не является общей. Итак, как отметил Бармар в комментариях, "abcdefgh"
может быть не выровнена, но это не имеет значения, потому что вам не нужен атомарный доступ к ней. , только для хранения этих 8 байт из регистра, как только они туда попадут.)
В процессорах Intel это означает, что 8 байт общей памяти не должны охватывать границу строки кэша, но могут быть смещены в любом месте внутри 64-байтового блока. В AMD он должен быть выровнен по 8 байтам. (На более поздних процессорах AMD смещенное выравнивание в пределах 16-, 32- или даже 64-байтового блока по-прежнему будет давать атомарные загрузки/сохранения.) См. Почему целочисленное присваивание естественно выровненной переменной является атомарным на x86?
В других архитектурах для обеспечения гарантий атомарности часто требуется естественное выравнивание, так что это лучший вариант.