如何将 6 字节无符号整数快速复制到内存区域
How to quickly replicate a 6-byte unsigned integer into a memory region?
我需要将一个 6 字节的整数值复制到内存区域中,从它的开头开始并尽快。如果硬件支持这样的操作,我想使用它(我现在使用 x64 处理器,编译器是 GCC 4.6.3(。
memset
不适合该作业,因为它只能复制字节。std::fill
也不是很好,因为我甚至无法定义迭代器,在内存区域中的 6 个字节宽度的位置之间跳转。
所以,我想要一个函数:
void myMemset(void* ptr, uint64_t value, uint8_t width, size_t num)
这看起来像memset
,但width
还有一个额外的参数来定义要复制的value
中的字节数。如果这样的东西可以用C++表达,那就更好了。
我已经知道明显的myMemset
实现,它会调用循环memcpy
,最后一个参数(要复制的字节(等于width
。我也知道,我可以定义一个大小为 6 * 8 = 48
字节的临时内存区域,用 6 字节整数填充它,然后将其memcpy
到目标区域。
我们能做得更好吗?
@Mark赎金评论
:复制 6个字节,然后复制 6、12、24、48、96 等。
void memcpy6(void *dest, const void *src, size_t n /* number of 6 byte blocks */) {
if (n-- == 0) {
return;
}
memcpy(dest, src, 6);
size_t width = 1;
while (n >= width) {
memcpy(&((char *) dest)[width * 6], dest, width * 6);
n -= width;
width <<= 1; // double w
}
if (n > 0) {
memcpy(&((char *) dest)[width * 6], dest, n * 6);
}
}
优化:n
和width
比例缩小6。
[编辑]
更正的目标@SchighSchagh
新增演员(char *)
确定 CPU 支持的最有效写入大小;然后找到可以平均除以 6 和该写入大小的最小数字,并调用该"块大小"。
现在将内存区域拆分为该大小的块。每个块将是相同的,所有写入都将正确对齐(假设内存区域本身已正确对齐(。
例如,如果 CPU 支持的最有效写入大小为 4 字节(例如古代 80486(,则"块大小"将为 12 字节。您将设置 3 个通用寄存器,每个块执行 3 个存储。
再举一个例子,如果CPU支持的最有效写入大小是16字节(例如SSE(,那么"块的大小"将是48字节。您将设置 3 个 SSE 寄存器,每个块执行 3 个存储。
此外,我建议将内存区域的大小向上舍入,以确保它是块大小的倍数(带有一些"非严格必要"的填充(。一些不必要的写入比填充"部分块"的代码成本更低。
第二种最有效的方法可能是使用内存副本(但不是memcpy()
或memmove()
(。在这种情况下,您将写入最初的 6 个字节(或 12 字节或 48 字节或其他字节(,然后从(例如(&area[0]
复制到&area[6]
(从最低到最高(,直到到达末尾。为此memmove()
将不起作用,因为它会注意到该区域重叠并从最高到最低工作;并且memcpy()
将不起作用,因为它假定源和目标不重叠;因此,您必须创建自己的内存副本以适应。这样做的主要问题是内存访问次数增加了一倍 - "读取和写入"比"单独写入"慢。
如果您的Num
足够大,您可以尝试使用 AVX 矢量指令,该指令一次将处理 32 个字节(_mm256_load_si256
/_mm256_store_si256
或其未对齐的变体(。
不是 6 的倍数,因此您必须首先使用短 memcpy 或 32/64 位移动将 6 字节模式复制 16 次。
ABCDEF
ABCDEF|ABCDEF
ABCD EFAB CDEF|ABCD EFAB CDEF
ABCDEFAB CDEFABCD EFABCDEF|ABCDEFAB CDEFABCD EFABCDE
ABCDEFABCDEFABCD EFABCDEFABCDEFAB CDEFABCDEFABCDEF|ABCDEFABCDEFABCD EFABCDEFABCDEFAB CDEFABCDEFABCDEF
您还将以简短的记忆结束。
尝试__movsq
内部函数(仅限 x64;在汇编中,rep movsq
(,一次移动 8 个字节,具有合适的重复因子,并将目标地址设置为源后 6 个字节。检查重叠地址是否得到巧妙处理。
写入 8 个字节。
在 64 位机器上,生成的代码当然可以在 8 字节写入中很好地运行。 在处理了一些设置问题后,在一个紧密循环中,每次写入大约 num
次写入 8 个字节。 假设适用 - 请参阅代码。
// assume little endian
void myMemset(void* ptr, uint64_t value, uint8_t width, size_t num) {
assert(width > 0 && width <= 8);
uint64_t *ptr64 = (uint64_t *) ptr;
// # to stop early to prevent writing past array end
static const unsigned stop_early[8 + 1] = { 0, 8, 3, 2, 1, 1, 1, 1, 0 };
size_t se = stop_early[width];
if (num > se) {
num -= se;
// assume no bus-fault with 64-bit write @ `ptr64, ptr64+1, ... ptr64+7`
while (num > 0) { // tight loop
num--;
*ptr64 = value;
ptr64 = (uint64_t *) ((char *) ptr64 + width);
}
ptr = ptr64;
num = se;
}
// Cope with last few writes
while (num-- > 0) {
memcpy(ptr, &value, width);
ptr = (char *) ptr + width;
}
}
进一步的优化包括一次写入 2 个块width == 3 or 4
,width == 2
时一次写入 4 个块,一次写入 8 个块width == 1
。
- C++尝试深度复制唯一指针时出现内存访问冲突
- 犰狳C++ - 从常量内存初始化只读矩阵而不复制
- 具有 STL 向量类型成员的类的复制内存
- 复制内存给出分段错误
- 如何将矩阵的行随机复制到内存中的另一个矩阵的过程并行化?
- C++:误解内存地址和指针的复制值
- C#中等价的C++内存复制
- 坚持将双指针管理的内存复制到二维数组
- OpenCL 将字符从全局内存复制到本地内存
- 避免在禁用复制的 POD 类型的内存上使用"-Wclass-memaccess"
- 如何正确复制内存缓冲区?
- numpy.i 是将实际指针发送到C++,还是复制内存?
- Google模拟:如何在模拟参数中从指针中复制内存
- 如何在不复制内存的情况下复制Zeromq中的消息
- 从偏移位置开始复制内存
- 更有效地使用fork()和写时复制内存共享
- 复制内存块
- C++树类:构造函数/复制/内存泄漏
- 如何在c++中通过复制内存来序列化对象
- CUDA无法运行/复制内存