繁体   English   中英

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 处理器或更新版本)
操作系统:Linux
编译器:gcc

这将取决于memcpy()的实现 - 没有保证。 即使您知道该实现使这变得安全,仍然依赖它是不明智的,因此在所有版本和平台上,此代码或模式可能会被重用。

您可以使用您知道是原子的单词副本来实现自己的逐字 16 位副本。 如何做到这一点值得提出一个新问题。

除非您在单核 VM 上运行中断,否则中断并不真正相关。 在具有多核 CPU 的普通系统上,两个线程可以同时在不同的内核上运行。 这就是为什么我们有 C++ std::atomic<>和 C _Atomic这对于像int这样的单个变量很有用。


这取决于您的 memcpy 实现。 任何不可怕的都不会做任何单字节副本,所有 16 位加载/存储实际上将是更大加载/存储的一部分(或者可能是rep movsb微码的内部结构)。 很难想象一个明智的编译器(不是 DeathStation 9000)会如何选择内联一个可能会导致 uint16_t 边界撕裂的副本。

但是,如果您不手动执行(例如,使用 AVX 内在函数),那么一些奇怪优化几乎不可能让编译器执行字节加载/存储。

对于像普通库这样的 SIMD 实现将用于小尺寸,它归结为矢量加载/存储和收集/分散的每元素原子性? - 令人讨厌的是,主要的 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 个完全重叠避免了稍后读取时存储转发的停顿,而不是两个不重叠的一半。上端实际上可能让它 go 最多 64 个字节,带有一对全尺寸 AVX 向量。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM