假设经过源缓冲区和目标缓冲区的字节具有可读性和可写性,则 memcpy 速度更快

Max*_*dov 5 c c++ simd memcpy allocator

假设我们想要将n数据字节从void* src复制到void* dst。众所周知,标准库的实现经过memcpy严格优化,可以使用依赖于平台的向量化指令和各种其他技巧来尽可能快地执行复制。

现在假设p后面的数据字节src + n是可读的并且p后面的数据字节dst + n是可写的。另外,假设在 中写入任意垃圾也是可以的[dst + n, dst + n + p)

显然,这些假设扩大了我们可能采取的行动的范围,可能导致更快的 memcpy。例如,我们可以在少量未对齐的 128 位指令(加载 + 存储)中复制少于 16 个尾随字节的某些部分。也许这种额外的假设还允许使用其他技巧。

     01234 .....            n
src: abcdabcdabcdabcdabcdabcGARBAGEGA
            v              v 
dst: ______(actl dst)________(wrtbl)_
     |    block1    ||    block2    |
Run Code Online (Sandbox Code Playgroud)

请注意,当您需要在分配的容量足以容纳p+ 总字符串大小字节的缓冲区内附加字符串序列时,这些假设实际上相当实用。例如,以下例程可能发生在数据库内部的某个位置:

给你一个二进制字符串char* dictionary和一个整数数组 int* offsets,它是字典中偏移量的单调序列;这两个变量表示从磁盘获取的字符串字典。您还有一个整数数组,int* indices指示必须将字典字符串写入输出缓冲区的顺序 char* buffer

使用上述技术,您可以安全地编写每个新字符串,而不关心其右侧的垃圾,因为它将被要附加的下一个字符串覆盖。

问题是:

  1. 这种技术有开源实现吗?实现最佳实现显然需要花费大量时间进行(依赖于平台的)调整,因此在不考虑现有实现的情况下编写此类代码似乎不是一个好主意。
  2. 为什么分配后 15 个字节的可读性不是现代分配器的功能?如果内存分配器可以在其内部执行的每个 mmap 中再分配一个统一化的内存页,那么它将以零成本有效地提供所需的可读性,而无需更改程序代码。

最后评论:这个想法并不新鲜;例如,它出现在ClickHouse的源代码中。尽管如此,他们还是实现了自己的自定义模板 POD 数组来处理此类分配。

Ale*_*iev 1

如果您实现向量化memchrmemcmp,您只在其中读取,则可以将向量与自然边界对齐,并且对于第一个/最后一个不完整向量屏蔽填充。通过以自然对齐的块进行处理,您永远不会在同一读取中命中下一页,因此读取未分配的数据应该是安全的,即使未分配额外的空间。

显然这是向量化的唯一方法memchr,因为如果结果早于实际结束找到,则该函数应该准备在count参数大于实际范围时工作。参考:

该函数的行为就好像它按顺序读取字符一样,并在找到匹配字符后立即停止:如果 ptr 指向的数组小于 count,但在数组中找到匹配项,则该行为是明确定义的


对于写入,我认为不可能进行任何优化,除非矢量化指令支持屏蔽写入。

假设您有一个由用户在某个任意索引处分隔的数组,并且单独的线程将不同的数据 memcpy 到各个部分中。如果memcpy写入超出范围,就会出现数据竞争。