16位对象数组的memcpy可以在两者之间中断吗
Can memcpy of array of 16-bit objects be interrupted in between
全球数据:
uint16_t global_buffer[128];
线程 1:
uint16_t local_buffer[128];
while(true)
{
...
if(data_ready)
memcpy(global_buffer, local_buffer, sizeof(uint16_t)*128);
}
线程 2:
void timer_handler()
{
uint16_t value = global_buffer[10];
//do something with value
}
我的问题是这样做是否安全?我的意思是,是否保证 value
将获得旧值或新值(如果线程 1 memcpy() 被上下文切换中断)?
在更新 16 位值的一个字节而不是第二个字节后,memcpy 是否有可能被中断。在那种情况下,value
将是垃圾。
如果 memcpy 操作只在偶数字节的块之间被中断,我认为这是安全的。
平台:仅限 x86 和 x86-64(实际上仅限 Intel i7 处理器或更新版本)
OS: Linux
编译器:gcc
这将取决于 memcpy()
的实施 - 没有任何保证。即使您知道该实现使它安全,依赖它仍然是不明智的,因此在所有版本和平台上都可能会重复使用此代码或模式。
您可以使用您知道是原子的单词副本来实现您自己的逐字 16 位副本。如何做到这一点需要一个新问题。
除非您在单核 VM 上 运行 否则中断并不真正相关。在具有多核 CPU 的普通系统上,两个线程可以同时在不同的内核上 运行。这就是为什么我们有 C++ std::atomic<>
和 C _Atomic
,它们对像 int
.
这样的单个变量很有用
这取决于您的 memcpy 实现。任何不可怕的都不会做任何单字节复制,所有 16 位 loads/stores 实际上将是更大的 loads/stores 的一部分(或者可能是 rep movsb
微码的内部) .很难想象一个明智的编译器(不是 DeathStation 9000)会如何选择内联一个可能导致跨 uint16_t 边界撕裂的副本。
但如果您不手动执行此操作(例如使用 AVX 内在函数),它 是 几乎不可能一些奇怪的优化可以让编译器执行一个字节 load/store.
对于像普通库将用于小尺寸的 SIMD 实现,它归结为 Per-element atomicity of vector load/store and gather/scatter? - 恼人的是,没有来自主要 x86 供应商(AMD 或英特尔)的正式保证。不过,几乎可以肯定它是安全的,尤其是如果整个向量本身是对齐的(因此没有缓存行拆分或页面拆分)。使用 alignas(64) uint16_t global_buffer[128];
将是确保这一点的好方法。
如果您的总副本大小不是矢量宽度的倍数,重叠副本仍然不会在 一个 uint16_t
内引入撕裂。与前 8 个 uint16_t 和后 8 个 uint16_t 一样,副本大小从 8(完全重叠)到 16(无重叠)数组元素。
顺便说一句,这基本上就是 glibc memcpy
为小副本所做的。一个 4 到 7 字节的 memcpy 是用两个 4 字节加载和 4 字节存储完成的,32 .. 63 字节是用 2x 32 字节向量完成的。 (2 个完全重叠避免了稍后读取时的存储转发停顿,而不是两个非重叠的一半。上端实际上可能让它达到 64 个字节,带有一对全尺寸 AVX 向量。)
全球数据:
uint16_t global_buffer[128];
线程 1:
uint16_t local_buffer[128];
while(true)
{
...
if(data_ready)
memcpy(global_buffer, local_buffer, sizeof(uint16_t)*128);
}
线程 2:
void timer_handler()
{
uint16_t value = global_buffer[10];
//do something with value
}
我的问题是这样做是否安全?我的意思是,是否保证 value
将获得旧值或新值(如果线程 1 memcpy() 被上下文切换中断)?
在更新 16 位值的一个字节而不是第二个字节后,memcpy 是否有可能被中断。在那种情况下,value
将是垃圾。
如果 memcpy 操作只在偶数字节的块之间被中断,我认为这是安全的。
平台:仅限 x86 和 x86-64(实际上仅限 Intel i7 处理器或更新版本)
OS: Linux
编译器:gcc
这将取决于 memcpy()
的实施 - 没有任何保证。即使您知道该实现使它安全,依赖它仍然是不明智的,因此在所有版本和平台上都可能会重复使用此代码或模式。
您可以使用您知道是原子的单词副本来实现您自己的逐字 16 位副本。如何做到这一点需要一个新问题。
除非您在单核 VM 上 运行 否则中断并不真正相关。在具有多核 CPU 的普通系统上,两个线程可以同时在不同的内核上 运行。这就是为什么我们有 C++ std::atomic<>
和 C _Atomic
,它们对像 int
.
这取决于您的 memcpy 实现。任何不可怕的都不会做任何单字节复制,所有 16 位 loads/stores 实际上将是更大的 loads/stores 的一部分(或者可能是 rep movsb
微码的内部) .很难想象一个明智的编译器(不是 DeathStation 9000)会如何选择内联一个可能导致跨 uint16_t 边界撕裂的副本。
但如果您不手动执行此操作(例如使用 AVX 内在函数),它 是 几乎不可能一些奇怪的优化可以让编译器执行一个字节 load/store.
对于像普通库将用于小尺寸的 SIMD 实现,它归结为 Per-element atomicity of vector load/store and gather/scatter? - 恼人的是,没有来自主要 x86 供应商(AMD 或英特尔)的正式保证。不过,几乎可以肯定它是安全的,尤其是如果整个向量本身是对齐的(因此没有缓存行拆分或页面拆分)。使用 alignas(64) uint16_t global_buffer[128];
将是确保这一点的好方法。
如果您的总副本大小不是矢量宽度的倍数,重叠副本仍然不会在 一个 uint16_t
内引入撕裂。与前 8 个 uint16_t 和后 8 个 uint16_t 一样,副本大小从 8(完全重叠)到 16(无重叠)数组元素。
顺便说一句,这基本上就是 glibc memcpy
为小副本所做的。一个 4 到 7 字节的 memcpy 是用两个 4 字节加载和 4 字节存储完成的,32 .. 63 字节是用 2x 32 字节向量完成的。 (2 个完全重叠避免了稍后读取时的存储转发停顿,而不是两个非重叠的一半。上端实际上可能让它达到 64 个字节,带有一对全尺寸 AVX 向量。)