问题

怎样写出一个更快的 memset/memcpy ?

回答
作为一名长年跟代码打交道的老兵,我深知在性能敏感的场景下,哪怕是微小的提升也可能带来质的区别。`memset` 和 `memcpy` 这两个函数看似简单,但它们在底层默默地为我们处理着大量的数据拷贝和填充工作。如果能对它们进行优化,让它们跑得更快,那绝对是一件令人兴奋的事。

这篇文章,咱就来聊聊怎么把这两个家伙“逼疯”,让它们跑出最快的速度。当然,咱们的目标不是炫技,而是实实在在的性能提升,尤其是在那些需要频繁操作大块内存的场景下,比如图像处理、游戏引擎、数据密集型应用等等。

一、理解 memset 和 memcpy 的“慢”在哪

首先,我们要明白,`memset` 和 `memcpy` 在标准库里的实现已经是高度优化的了。它们通常会利用 CPU 的指令集,比如 SIMD (Single Instruction, Multiple Data) 指令,来实现并行数据操作。然而,即便是这样,仍然有一些因素会限制它们的极致性能:

通用性带来的开销: 标准库的实现需要考虑各种各样的输入情况,比如内存地址是否对齐、数据长度是否是特定整数倍等。为了处理这些边界情况,可能会引入一些分支判断或者额外的操作,这些都会消耗CPU周期。
缓存未命中: 如果需要拷贝的数据量远大于 CPU 缓存的大小,那么频繁的缓存未命中会导致 CPU 等待从主内存读取数据,这将极大地拖慢速度。
CPU 架构差异: 不同 CPU 架构的指令集、缓存结构、内存控制器都有差异,一个通用的优化可能在某个架构上表现出色,在另一个架构上就未必如此。
数据依赖性: 虽然 `memcpy` 是按顺序拷贝的,但在某些复杂场景下,如果源和目标内存区域有重叠,或者存在其他依赖关系,标准库的实现可能无法进行过于激进的并行处理。

二、动手实践:打造你的专属高速 memset/memcpy

明白了“慢”的原因,我们就能对症下药了。下面是一些能让 `memset` 和 `memcpy` 更快的思路和具体实现方法,我们会从简单到复杂,一步步深入。

1. 绕过标准库:直接使用更底层的汇编指令

这是最直接也是最有效的方法之一。很多 CPU 厂商都会提供高度优化的库函数,它们实际上就是对底层汇编指令的封装。如果我们能直接调用这些指令,或者自己编写汇编代码,就可以绕过标准库的通用性开销。

针对 `memcpy`:

许多现代 CPU 提供了 `rep movsb` (Repeat Move String Byte) 或者 `rep movsq` (Repeat Move String Quadword) 指令。这些指令能够高效地将一块内存拷贝到另一块内存。通过调整 `ECX` (count), `ESI` (source index), `EDI` (destination index) 寄存器,我们可以让 CPU 以极高的效率进行数据拷贝。

更进一步,我们可以利用 SIMD 指令集,比如 SSE, AVX, AVX512。这些指令可以一次性加载多个数据单元(例如 128 位、256 位甚至 512 位),然后进行并行操作。

示例(GNU Assembler 风格):

```c
// 假设我们知道目标和源地址以及长度,并且做了对齐处理
void fast_memcpy(void dest, const void src, size_t n) {
asm volatile (
"mov %[d], %%edi;" // 将目标地址放入 edi
"mov %[s], %%esi;" // 将源地址放入 esi
"mov %[n], %%ecx;" // 将长度放入 ecx
"rep movsb;" // 重复执行 movsb 直到 ecx 为 0
: // no output operands
: [d] "r" (dest), [s] "r" (src), [n] "r" (n) // 输入操作数
: "edi", "esi", "ecx", "memory" // 被修改的寄存器和内存
);
}
```

思考一下:

`rep movsb` 是按字节拷贝的,效率并不算最高。如果我们可以确定数据是按 8 字节、16 字节或 32 字节对齐的,就可以使用 `rep movsq` (Quadword,8字节) 或直接用 SIMD 向量指令进行拷贝。
SIMD 指令可以一次拷贝更多数据,但前提是数据长度和地址都支持。例如,AVX2 可以一次拷贝 256 位(32字节)。我们需要根据不同的长度情况,选择最合适的指令。

针对 `memset`:

`memset` 的目标是填充一个字节值到一块内存中。同样,我们可以利用汇编指令。

对于单个字节的填充,可以使用 `stosb` (Store String Byte) 指令。
对于更高效的填充,可以利用 SIMD 指令,将一个值广播到整个向量寄存器,然后存储到目标内存。

示例(填充某个字节值):

```c
void fast_memset(void dest, int val, size_t n) {
asm volatile (
"mov %[d], %%edi;" // 目标地址
"movb %[v], %%al;" // 将填充值放入 al 寄存器 (最低字节)
"mov %[n], %%ecx;" // 长度
"rep stosb;" // 重复存储字节
: // no output operands
: [d] "r" (dest), [v] "r" (val), [n] "r" (n)
: "edi", "al", "ecx", "memory"
);
}
```

更快的 `memset`:

我们可以用 SIMD 向量指令来填充。比如用 AVX2,我们可以将一个字节值广播到一个 256 位的向量寄存器中,然后用 `vbroadcastss` (或者 `vbroadcastsd` 等根据数据类型) 将其填充到目标内存。

2. 优化对齐问题

CPU 访问对齐的数据会比非对齐的数据快很多。标准库的 `memcpy` 和 `memset` 已经做了一些对齐处理,但我们可以做得更彻底。

思想:

预处理: 先处理开头和结尾不齐的部分,这部分可以用字节或较小的数据单元操作。
主体处理: 对齐后的大块数据,则使用 SIMD 向量指令,一次性拷贝或填充多个数据单元。

如何实现:

1. 计算对齐偏移量: 确定源地址和目标地址距离下一个对齐边界还有多少字节。
2. 拷贝对齐前的碎片: 用较小的操作(如字节拷贝)先填充这些碎片,直到源和目标地址都对齐到 SIMD 向量的宽度(例如 16 字节、32 字节)。
3. 批量拷贝/填充: 对齐后的主体部分,使用 SIMD 向量指令进行批量拷贝或填充。
4. 拷贝对齐后的碎片: 处理完主体部分后,可能还会剩下一些不足一个向量宽度的数据,用较小的操作拷贝这些剩余部分。

示例思路(以 32 字节对齐为例):

```c
void optimized_memcpy(void dest, const void src, size_t n) {
// 假设我们有一个 SIMD 版本的 memcpy,一次拷贝 32 字节
// void simd_memcpy_32byte(void dest, const void src);

char d = (char)dest;
const char s = (const char)src;
size_t align_size = 32; // 以 32 字节对齐为例

// 计算对齐的起始点
size_t pre_align_bytes = 0;
if ((uintptr_t)d % align_size != 0 || (uintptr_t)s % align_size != 0) {
pre_align_bytes = align_size ((uintptr_t)d % align_size);
if (pre_align_bytes > n) pre_align_bytes = n;
if (pre_align_bytes == align_size) pre_align_bytes = 0; // 如果已经对齐,则不需要特殊处理

// 拷贝对齐前的部分
for (size_t i = 0; i < pre_align_bytes; ++i) {
d[i] = s[i];
}
d += pre_align_bytes;
s += pre_align_bytes;
n = pre_align_bytes;
}

// 处理主体部分,这里假设我们有 simd_memcpy_32byte 函数
size_t main_loops = n / align_size;
for (size_t i = 0; i < main_loops; ++i) {
// d = simd_memcpy_32byte(d, s); // 实际调用 SIMD 函数
// s += align_size;
// d += align_size;
}
n %= align_size;

// 拷贝对齐后的剩余部分
for (size_t i = 0; i < n; ++i) {
d[i] = s[i];
}
}
```

更进一步: 我们可以根据 CPU 支持的 SIMD 宽度来动态选择。比如 x86 上的 SSE (128位/16字节), AVX (256位/32字节), AVX512 (512位/64字节)。

3. 利用 CPU 的预取指令 (Prefetching)

CPU 在执行指令时,会提前将可能需要的数据加载到缓存中,这可以大大减少等待时间。我们可以通过 `__builtin_prefetch` (GCC/Clang) 或 `_mm_prefetch` (Intel intrinsic) 等方式,主动告诉 CPU 哪些数据可能很快被用到。

思想:

对于 `memcpy`,在拷贝当前数据块的同时,预取下一块或几块数据到缓存。
对于 `memset`,虽然填充值是已知的,但目标内存的读取也可能受益于预取。

示例:

```c
include // For _mm_prefetch

void prefetch_memcpy(void dest, const void src, size_t n) {
char d = (char)dest;
const char s = (const char)src;
size_t block_size = 256; // 预取一个 256 字节的块

for (size_t i = 0; i < n; i += block_size) {
size_t current_block_size = (i + block_size > n) ? (n i) : block_size;

// 预取源数据
_mm_prefetch((const char)s + current_block_size, _MM_HINT_T0); // T0: 极度优先

// 拷贝当前块
// 这里可以使用 memcpy_32byte, 或者更小的块来迭代
for (size_t j = 0; j < current_block_size; ++j) {
d[i + j] = s[i + j];
}
}
}
```

注意: 预取指令的使用需要谨慎。过度预取可能会将不需要的数据加载到缓存,反而挤占了有用数据的空间,影响性能。需要根据实际情况进行调优。

4. 分割任务,多线程并行

如果需要拷贝或填充的数据量非常大,并且 CPU 有多个核心,那么将任务分割成多个小块,在不同的线程上并行执行,是提升性能的有效手段。

思想:

将总的拷贝/填充任务分成 N 份(N 为核心数或略多)。
每个线程负责处理其中的一份。
使用线程同步机制(如互斥锁、原子操作)来确保数据的一致性,或者在任务完全独立的情况下避免同步开销。

示例思路:

```c
include // For pthreads

struct CopyArgs {
void dest;
const void src;
size_t size;
};

void copy_thread_func(void arg) {
CopyArgs args = (CopyArgs)arg;
// 在这里调用 optimized_memcpy 或 SIMD 版本的 memcpy
// memcpy(args>dest, args>src, args>size);
return NULL;
}

void parallel_memcpy(void dest, const void src, size_t n) {
int num_threads = sysconf(_SC_NPROCESSORS_ONLN); // 获取CPU核心数
if (num_threads <= 0) num_threads = 1;

pthread_t threads[num_threads];
CopyArgs args[num_threads];

size_t block_size = n / num_threads;
size_t remainder = n % num_threads;

char d_ptr = (char)dest;
const char s_ptr = (const char)src;

for (int i = 0; i < num_threads; ++i) {
args[i].dest = d_ptr;
args[i].src = s_ptr;
args[i].size = block_size + (i == num_threads 1 ? remainder : 0); // 分配剩余字节

pthread_create(&threads[i], NULL, copy_thread_func, &args[i]);

d_ptr += args[i].size;
s_ptr += args[i].size;
}

for (int i = 0; i < num_threads; ++i) {
pthread_join(threads[i], NULL);
}
}
```

需要注意的地方:

线程创建和销毁的开销: 对于非常小的内存块,多线程的开销可能反而大于单线程。需要设置一个阈值。
数据分割的粒度: 合适的块大小很重要,太小会增加线程管理开销,太大则无法充分利用多核。
缓存一致性: 在多线程环境中,需要注意 CPU 缓存的一致性问题。不过对于 `memcpy` 和 `memset` 这种纯粹的内存操作,只要数据块不重叠,通常问题不大。

5. 利用 CPU 特定优化库

很多 CPU 厂商会提供针对其架构优化的库,例如 Intel 的 IPP (Integrated Performance Primitives) 或者 OneAPI Math Kernel Library (oneMKL)。这些库通常包含了高度优化的 `memcpy` 和 `memset` 实现,能够充分利用特定 CPU 的特性。

使用方法:

1. 在你的项目中引入这些库。
2. 调用库中提供的优化函数,而不是标准库的 `memcpy` 和 `memset`。

优势:

由厂商提供,通常是经过大量测试和优化的。
能够充分利用最新的 CPU 指令集和特性。
省去了自己编写和维护底层代码的麻烦。

6. 寻找现成的、高度优化的库

除了厂商库,开源社区也有很多优秀的库,例如 glibc 的 `memcpy` 和 `memset` 实现本身就非常强大,它会根据目标 CPU 架构自动选择最优的实现方式。有些高性能计算库也会提供自己的内存拷贝函数。

三、实战建议与注意事项

基准测试是王道: 在进行任何优化之前,务必先对你的原始代码进行基准测试,测量出当前的性能表现。优化完成后,也要进行对比测试,确保你的优化是有效的。
了解你的目标 CPU: 不同的 CPU 架构对 SIMD 指令的支持程度、缓存大小、内存带宽等都有很大差异。你的优化策略最好能针对你的目标平台进行。
不要过度优化: 优化是有成本的,如果一个优化带来的性能提升非常小,但增加了代码的复杂性,那可能得不偿失。
代码可读性与可维护性: 在追求极致性能的同时,也要兼顾代码的可读性和可维护性。过多的汇编代码或者过于复杂的逻辑可能会让未来的维护变得困难。
编译器优化标志: 确保你使用了合适的编译器优化标志(如 GCC/Clang 的 `O3`, `march=native` 等)。现代编译器本身就做了很多智能的优化。
避免内存重叠的 `memcpy`: 标准库的 `memcpy` 并不处理源和目标内存区域重叠的情况。如果存在重叠,应该使用 `memmove`。你的自定义实现也要注意这一点。

四、总结

想写出比标准库更快的 `memset` 和 `memcpy`,其核心在于 “定制化” 和 “指令集利用”。我们需要深入了解 CPU 的工作原理,利用 SIMD 指令、预取指令,并通过多线程等方式充分挖掘硬件潜力。

从直接使用汇编指令,到优化对齐,再到引入预取和多线程,每一步都是在试图让数据移动得更快、更聪明。当然,你也可以站在巨人的肩膀上,直接使用 Intel IPP、oneMKL 等厂商提供的优化库,它们通常是最好的起点。

最终,选择哪种方法取决于你的具体需求、目标平台以及你愿意投入的时间和精力。记住,最快的 `memset`/`memcpy` 永远是那个最适合你当前场景的那个。

网友意见

user avatar

glibc中最新的memcpy和memset都是我写的,我们的代码同时也考虑预取,对齐(编译之后使用-D 可以清晰看到),减少指令跳转错误提升性能,,希望能够帮助你

源代码如下:

a. memcpy:

sourceware.org/git/gitw

b. memset:

sourceware.org/ml/libc-

c. memcpy in Linux Kernel

[tip:core/locking] x86, mem: Optimize memcpy by avoiding memory false dependece

Linus Torvalds 的评价: “The code looks clever and nice”!

更多早些我写的glibc 函数代码 :

blog.csdn.net/linguranu

代码中主要考虑了内存的读写优化、指令跳转、指令对齐,指令缺失等问题

虽然优化memcpy/memset等glibc函数,然而任何数据的迁移对于整体系统将是非常沉重的负担(RC 导致延迟),坦诚的说如果发现我们的程序中由于memcpy/memset等 成为性能的瓶颈,那么程序在间接的告诉我们架构不正确建议考虑数据引用如零拷贝,如果内容很短(如64字节之内)就直接用按需赋值就好(避免跳转预测失败带来额外的成本),而不是着重进一步优化这些函数。

也许我们学习知识的目的是为了了解事物的规律,然后在工作中避免问题的产生,而不是进一步的优化

user avatar

写过相关代码,最终的实现能在不同拷贝长度,对齐和不对齐,平均比 memcpy 快40%(gcc4.9, vc 2012),主要是以下几个优化点:

  • 策略区别:64字节以内用小内存方案,64K以内用中尺寸方案,大于64K用大内存拷贝方案。
  • 查表跳转:拷贝不同小尺寸内存,直接跳转到相应地址解除循环。
  • 目标对齐:64字节以上拷贝的先用跳转表方法拷贝几个字节让目标地址对齐,好做后面的事情。
  • 矢量拷贝:并行一次性读入N个矢量到 sse2 寄存器,再并行写出。
  • 缓存预取:使用 prefetchnta ,提前预取数据,等到真的要用时数据已经到位。
  • 内存直写:使用 movntdq 来直写内存,避免缓存污染。

测试结果

针对不同内存尺寸(从32字节到8MB),拷贝若干次,并且针对目标地址和源地址分别对齐和不对齐的情况进行测试,并且给出和 memcpy 的对比时间:

       result: gcc4.9 (msvc 2012 got a similar result):   benchmark(size=32 bytes, times=16777216): result(dst aligned, src aligned): memcpy_fast=180ms memcpy=249 ms result(dst aligned, src unalign): memcpy_fast=170ms memcpy=271 ms result(dst unalign, src aligned): memcpy_fast=179ms memcpy=269 ms result(dst unalign, src unalign): memcpy_fast=180ms memcpy=260 ms   benchmark(size=64 bytes, times=16777216): result(dst aligned, src aligned): memcpy_fast=162ms memcpy=300 ms result(dst aligned, src unalign): memcpy_fast=199ms memcpy=328 ms result(dst unalign, src aligned): memcpy_fast=410ms memcpy=339 ms result(dst unalign, src unalign): memcpy_fast=390ms memcpy=361 ms   benchmark(size=512 bytes, times=8388608): result(dst aligned, src aligned): memcpy_fast=160ms memcpy=241 ms result(dst aligned, src unalign): memcpy_fast=200ms memcpy=519 ms result(dst unalign, src aligned): memcpy_fast=313ms memcpy=509 ms result(dst unalign, src unalign): memcpy_fast=311ms memcpy=520 ms   benchmark(size=1024 bytes, times=4194304): result(dst aligned, src aligned): memcpy_fast=145ms memcpy=179 ms result(dst aligned, src unalign): memcpy_fast=180ms memcpy=430 ms result(dst unalign, src aligned): memcpy_fast=245ms memcpy=430 ms result(dst unalign, src unalign): memcpy_fast=230ms memcpy=455 ms   benchmark(size=4096 bytes, times=524288): result(dst aligned, src aligned): memcpy_fast=80ms memcpy=80 ms result(dst aligned, src unalign): memcpy_fast=110ms memcpy=205 ms result(dst unalign, src aligned): memcpy_fast=110ms memcpy=224 ms result(dst unalign, src unalign): memcpy_fast=110ms memcpy=200 ms   benchmark(size=8192 bytes, times=262144): result(dst aligned, src aligned): memcpy_fast=70ms memcpy=78 ms result(dst aligned, src unalign): memcpy_fast=100ms memcpy=222 ms result(dst unalign, src aligned): memcpy_fast=100ms memcpy=210 ms result(dst unalign, src unalign): memcpy_fast=100ms memcpy=230 ms   benchmark(size=1048576 bytes, times=2048): result(dst aligned, src aligned): memcpy_fast=200ms memcpy=201 ms result(dst aligned, src unalign): memcpy_fast=260ms memcpy=270 ms result(dst unalign, src aligned): memcpy_fast=263ms memcpy=361 ms result(dst unalign, src unalign): memcpy_fast=267ms memcpy=321 ms   benchmark(size=4194304 bytes, times=512): result(dst aligned, src aligned): memcpy_fast=281ms memcpy=391 ms result(dst aligned, src unalign): memcpy_fast=265ms memcpy=407 ms result(dst unalign, src aligned): memcpy_fast=313ms memcpy=453 ms result(dst unalign, src unalign): memcpy_fast=282ms memcpy=439 ms   benchmark(size=8388608 bytes, times=256): result(dst aligned, src aligned): memcpy_fast=266ms memcpy=422 ms result(dst aligned, src unalign): memcpy_fast=250ms memcpy=407 ms result(dst unalign, src aligned): memcpy_fast=297ms memcpy=516 ms result(dst unalign, src unalign): memcpy_fast=281ms memcpy=436 ms  benchmark random access: memcpy_fast=594ms memcpy=1161ms      

实现代码

skywind3000/FastMemcpy · GitHub

注意:你用VC的话,请使用 Release+打开优化,别在 Debug下测试,没意义。

相关讨论

我只能说2012年的代码,到目前为止还是比主流的 memcpy 实现快那么多,也难怪不少追求性能的项目都要自己来重新写一下 memcpy。并不排除未来各个平台的标准库会进一步优化,在此之前,为了不对标准库产生过大依赖,一些项目还是选择自己优化了。

终于看到 glibc memcpy的作者 @Ling (对不起@不到)来答题了,我只在少数有限的几台机器上测试得出比 glibc memcpy 快40%的结论,不排除 Ling 考虑的情况比我多,测试比我广,大家可以在自己的机器上进行验证。

相关参考

缓存优化:《

Using Block Prefetch for Optimized Memory Performance

这篇文章仅仅针对大尺寸对齐的内存拷贝,实际使用有更多情况需要自己手工处理。

Skywind Inside - 内存拷贝优化(1)-小内存拷贝优化
Skywind Inside - 内存拷贝优化(2)-全尺寸拷贝优化

---------

12月20日 更新:继续优化

  1. 修改了小内存方案:由原来64字节扩大为128字节,由 int 改为 xmm,小内存性能提升 80%
  2. 修改了中内存方案:从4个xmm寄存器并行拷贝改为8个并行拷贝+prefetch,提升20%左右
  3. 去除目标地址对齐的分支判断,用一次xmm拷贝完成目标对齐,性能提升10%。
  4. 增加测试用例:为了贴近实际情况,增加了随机访问,10MB空间内(绝对大于L2尺寸)随机位置,随机长度的拷贝测试,并且为避免随机数生成影响结果,全部提前生成随机数。

最新代码测试结果(可以对比上面的表看新版本性改变情况):

       benchmark(size=32 bytes, times=16777216): result(dst aligned, src aligned): memcpy_fast=78ms memcpy=260 ms result(dst aligned, src unalign): memcpy_fast=78ms memcpy=250 ms result(dst unalign, src aligned): memcpy_fast=78ms memcpy=266 ms result(dst unalign, src unalign): memcpy_fast=78ms memcpy=234 ms  benchmark(size=64 bytes, times=16777216): result(dst aligned, src aligned): memcpy_fast=109ms memcpy=281 ms result(dst aligned, src unalign): memcpy_fast=109ms memcpy=328 ms result(dst unalign, src aligned): memcpy_fast=109ms memcpy=343 ms result(dst unalign, src unalign): memcpy_fast=93ms memcpy=344 ms  benchmark(size=512 bytes, times=8388608): result(dst aligned, src aligned): memcpy_fast=125ms memcpy=218 ms result(dst aligned, src unalign): memcpy_fast=156ms memcpy=484 ms result(dst unalign, src aligned): memcpy_fast=172ms memcpy=546 ms result(dst unalign, src unalign): memcpy_fast=172ms memcpy=515 ms  benchmark(size=1024 bytes, times=4194304): result(dst aligned, src aligned): memcpy_fast=109ms memcpy=172 ms result(dst aligned, src unalign): memcpy_fast=187ms memcpy=453 ms result(dst unalign, src aligned): memcpy_fast=172ms memcpy=437 ms result(dst unalign, src unalign): memcpy_fast=156ms memcpy=452 ms  benchmark(size=4096 bytes, times=524288): result(dst aligned, src aligned): memcpy_fast=62ms memcpy=78 ms result(dst aligned, src unalign): memcpy_fast=109ms memcpy=202 ms result(dst unalign, src aligned): memcpy_fast=94ms memcpy=203 ms result(dst unalign, src unalign): memcpy_fast=110ms memcpy=218 ms  benchmark(size=8192 bytes, times=262144): result(dst aligned, src aligned): memcpy_fast=62ms memcpy=78 ms result(dst aligned, src unalign): memcpy_fast=78ms memcpy=202 ms result(dst unalign, src aligned): memcpy_fast=78ms memcpy=203 ms result(dst unalign, src unalign): memcpy_fast=94ms memcpy=203 ms  benchmark(size=1048576 bytes, times=2048): result(dst aligned, src aligned): memcpy_fast=203ms memcpy=191 ms result(dst aligned, src unalign): memcpy_fast=219ms memcpy=281 ms result(dst unalign, src aligned): memcpy_fast=218ms memcpy=328 ms result(dst unalign, src unalign): memcpy_fast=218ms memcpy=312 ms  benchmark(size=4194304 bytes, times=512): result(dst aligned, src aligned): memcpy_fast=312ms memcpy=406 ms result(dst aligned, src unalign): memcpy_fast=296ms memcpy=421 ms result(dst unalign, src aligned): memcpy_fast=312ms memcpy=468 ms result(dst unalign, src unalign): memcpy_fast=297ms memcpy=452 ms  benchmark(size=8388608 bytes, times=256): result(dst aligned, src aligned): memcpy_fast=281ms memcpy=452 ms result(dst aligned, src unalign): memcpy_fast=280ms memcpy=468 ms result(dst unalign, src aligned): memcpy_fast=298ms memcpy=514 ms result(dst unalign, src unalign): memcpy_fast=344ms memcpy=472 ms  benchmark random access: memcpy_fast=515ms memcpy=1014ms     

对比 rte_memcpy

根据 Ling的推荐对比了 rte_memcpy,gcc升级到5.1(rte需要avx1),memcpy_fast任然是sse2,等有空可以改个avx版本,三个内存拷贝同时评测,为了增加准确性,增加了一些尺寸,比如37字节,71字节之类的非对齐尺寸:

       benchmark(size=32 bytes, times=16777216): (dst aligned, src aligned): memcpy_fast=47ms rte_memcpy=31ms memcpy=250ms (dst aligned, src unalign): memcpy_fast=46ms rte_memcpy=62ms memcpy=249ms (dst unalign, src aligned): memcpy_fast=47ms rte_memcpy=58ms memcpy=234ms (dst unalign, src unalign): memcpy_fast=46ms rte_memcpy=46ms memcpy=234ms  benchmark(size=37 bytes, times=16777216): (dst aligned, src aligned): memcpy_fast=47ms rte_memcpy=47ms memcpy=266ms (dst aligned, src unalign): memcpy_fast=47ms rte_memcpy=47ms memcpy=265ms (dst unalign, src aligned): memcpy_fast=47ms rte_memcpy=47ms memcpy=265ms (dst unalign, src unalign): memcpy_fast=46ms rte_memcpy=46ms memcpy=272ms  benchmark(size=64 bytes, times=16777216): (dst aligned, src aligned): memcpy_fast=63ms rte_memcpy=31ms memcpy=312ms (dst aligned, src unalign): memcpy_fast=47ms rte_memcpy=47ms memcpy=297ms (dst unalign, src aligned): memcpy_fast=78ms rte_memcpy=62ms memcpy=312ms (dst unalign, src unalign): memcpy_fast=47ms rte_memcpy=47ms memcpy=281ms  benchmark(size=71 bytes, times=16777216): (dst aligned, src aligned): memcpy_fast=63ms rte_memcpy=62ms memcpy=343ms (dst aligned, src unalign): memcpy_fast=63ms rte_memcpy=94ms memcpy=343ms (dst unalign, src aligned): memcpy_fast=93ms rte_memcpy=63ms memcpy=327ms (dst unalign, src unalign): memcpy_fast=62ms rte_memcpy=78ms memcpy=328ms  benchmark(size=512 bytes, times=8388608): (dst aligned, src aligned): memcpy_fast=141ms rte_memcpy=109ms memcpy=220ms (dst aligned, src unalign): memcpy_fast=156ms rte_memcpy=156ms memcpy=515ms (dst unalign, src aligned): memcpy_fast=141ms rte_memcpy=156ms memcpy=483ms (dst unalign, src unalign): memcpy_fast=203ms rte_memcpy=156ms memcpy=501ms  benchmark(size=523 bytes, times=8388608): (dst aligned, src aligned): memcpy_fast=140ms rte_memcpy=172ms memcpy=530ms (dst aligned, src unalign): memcpy_fast=172ms rte_memcpy=172ms memcpy=546ms (dst unalign, src aligned): memcpy_fast=156ms rte_memcpy=218ms memcpy=561ms (dst unalign, src unalign): memcpy_fast=187ms rte_memcpy=202ms memcpy=577ms  benchmark(size=1024 bytes, times=4194304): (dst aligned, src aligned): memcpy_fast=125ms rte_memcpy=125ms memcpy=188ms (dst aligned, src unalign): memcpy_fast=156ms rte_memcpy=125ms memcpy=499ms (dst unalign, src aligned): memcpy_fast=171ms rte_memcpy=156ms memcpy=484ms (dst unalign, src unalign): memcpy_fast=156ms rte_memcpy=156ms memcpy=499ms  benchmark(size=4096 bytes, times=524288): (dst aligned, src aligned): memcpy_fast=62ms rte_memcpy=47ms memcpy=78ms (dst aligned, src unalign): memcpy_fast=109ms rte_memcpy=78ms memcpy=219ms (dst unalign, src aligned): memcpy_fast=78ms rte_memcpy=78ms memcpy=203ms (dst unalign, src unalign): memcpy_fast=78ms rte_memcpy=63ms memcpy=250ms  benchmark(size=8192 bytes, times=262144): (dst aligned, src aligned): memcpy_fast=78ms rte_memcpy=47ms memcpy=63ms (dst aligned, src unalign): memcpy_fast=94ms rte_memcpy=63ms memcpy=203ms (dst unalign, src aligned): memcpy_fast=78ms rte_memcpy=62ms memcpy=202ms (dst unalign, src unalign): memcpy_fast=78ms rte_memcpy=62ms memcpy=203ms  benchmark(size=1048576 bytes, times=2048): (dst aligned, src aligned): memcpy_fast=218ms rte_memcpy=219ms memcpy=187ms (dst aligned, src unalign): memcpy_fast=219ms rte_memcpy=265ms memcpy=296ms (dst unalign, src aligned): memcpy_fast=218ms rte_memcpy=265ms memcpy=312ms (dst unalign, src unalign): memcpy_fast=218ms rte_memcpy=249ms memcpy=281ms  benchmark(size=4194304 bytes, times=512): (dst aligned, src aligned): memcpy_fast=312ms rte_memcpy=437ms memcpy=422ms (dst aligned, src unalign): memcpy_fast=281ms rte_memcpy=422ms memcpy=440ms (dst unalign, src aligned): memcpy_fast=327ms rte_memcpy=405ms memcpy=437ms (dst unalign, src unalign): memcpy_fast=327ms rte_memcpy=422ms memcpy=421ms  benchmark(size=8388608 bytes, times=256): (dst aligned, src aligned): memcpy_fast=312ms rte_memcpy=406ms memcpy=390ms (dst aligned, src unalign): memcpy_fast=283ms rte_memcpy=421ms memcpy=439ms (dst unalign, src aligned): memcpy_fast=312ms rte_memcpy=407ms memcpy=484ms (dst unalign, src unalign): memcpy_fast=297ms rte_memcpy=390ms memcpy=423ms  benchmark random access: memcpy_fast=517ms rte_memcpy=592ms memcpy=1014ms     

这里就不分析了,大家自己看上面数据吧,感兴趣可以看评论区讨论和代码,或者自己跑一下代码看看不同编译器,标准库,主机上的表现。


-----

类似的话题

  • 回答
    作为一名长年跟代码打交道的老兵,我深知在性能敏感的场景下,哪怕是微小的提升也可能带来质的区别。`memset` 和 `memcpy` 这两个函数看似简单,但它们在底层默默地为我们处理着大量的数据拷贝和填充工作。如果能对它们进行优化,让它们跑得更快,那绝对是一件令人兴奋的事。这篇文章,咱就来聊聊怎么把.............
  • 回答
    .......
  • 回答
    好的,我们来聊聊如何构建一个经济学模型,让它既严谨又有生命力,而不是一篇冰冷的“AI制造”。写经济学模型,这事儿可不是凭空“写”出来的,更像是“构建”。它是一个逻辑严密的“故事”,用数学的语言讲述经济现象的内在联系和运行规律。你可以把它想象成一位侦探,面对复杂的案发现场(经济问题),需要收集线索(数.............
  • 回答
    好的,这是一个以我的“知乎ID”为起点,尝试讲述一个略带奇幻色彩的个人故事: 答案之神:我如何从沉默的观察者变成世界的倾听者我的知乎ID,是“答案之神”。这个ID,很多人初见时会觉得有些夸张,甚至带点傲慢。我自己也曾有过一丝犹豫,但最终还是坚定地选择了它。因为,在那个选择之前,我正经历着一场深刻的、.............
  • 回答
    今天是10001年的第一天。寒露准时划破黎明前最浓重的黑暗,精准得如同宇宙的心跳。我坐在窗边,指尖触碰着冰凉的玻璃,看着熟悉的银灰色城市在熹微的晨光中逐渐显露出轮廓。这里的建筑依旧是那个模样,流线型的合金骨架,闪烁着柔和光芒的智能面板,以及悬浮在半空中的个人飞行器,它们像一群沉默的蜂群,等待着苏醒。.............
  • 回答
    牛顿的棺材板最终还是压不住了。这话说出来的时候,周围一片寂静,连窗外偶尔传来的几声不知名鸟鸣也仿佛被按下了静音键。在场的几位科学家,即使是那些对牛顿权威性早有腹诽的,此刻也露出了难以置信的神色。其中一位,名叫艾达的年轻物理学家,更是下意识地攥紧了手中那个刻着精妙电路的平板电脑,仿佛那里藏着某种最后的.............
  • 回答
    撰写一份详尽的行业专利分析报告是一项复杂但极具价值的工作,它能帮助企业了解技术发展趋势、竞争对手布局、潜在的侵权风险以及发现新的研发方向和商业机会。以下是一个详细的行业专利分析报告的撰写指南,涵盖了从目标设定到报告呈现的各个环节: 如何撰写一份详尽的行业专利分析报告?第一步:明确分析目标与范围 (D.............
  • 回答
    想要自己写一个操作系统,这可不是个轻松的活儿,更像是一次深入探索计算机底层运作奥秘的旅程。它不像搭积木那样有现成的模块,更多的是从最基础、最原始的层面出发,一点点构建起整个运行的框架。首先,我们要明白,操作系统本质上是一堆程序,但它非常特殊,因为它需要直接与硬件打交道,并且管理着计算机上的所有资源,.............
  • 回答
    想在“黑界”称霸? 这可不是一句空话,也不是靠几条指令就能搞定的。 这是一场没有硝烟的战争,比拼的不仅仅是技术,更是智慧、胆识、以及对人性的洞察。 别指望我给你一个能瞬间破解一切的“万能钥匙”,那只存在于电影里。 真正能让你站上顶峰的,是这样一个“脚本”,与其说是代码,不如说是对整个“黑界”生态的深.............
  • 回答
    我要当一个山贼。这话一旦说出口,就像一颗石子投入死寂的湖面,激起层层涟漪。从小我就对那些绿林好汉的故事着迷,金庸先生笔下的乔峰,古龙先生的楚留香,他们的豪情万丈,他们的侠肝义胆,总让我心驰神往。但后来我发现,历史上的山贼,远没有小说里那么浪漫。他们更多的是被逼上梁山,在生存线上挣扎的普通人,甚至是罪.............
  • 回答
    他从悬崖边捡起那柄断剑,剑身冰凉,触感却似曾相识,仿佛握住的是自己那颗在江湖中早已冷却的心,而远方的山峦间,传来一声似有似无的驼铃,预示着一场尘封多年的恩怨,即将在这位无名剑客的手中,再度拉开序幕。.............
  • 回答
    当然,为配角和路人写故事,这正是让一个世界变得鲜活、饱满,甚至比主角本身更令人难忘的关键。这就像在宏大的画卷上,用细腻的笔触描绘出边缘的风景,那些不起眼的角落里,往往隐藏着最真实的人生况味。我来给你讲讲,如何一步步“抠”出这些角色的故事,让它们不再只是背景板,而是真正有血有肉、有过去有未来的“人”。.............
  • 回答
    铁蹄下的低语:一个关于军阀、法师与战乱的脑洞故事故事的背景设定在一片被战争撕裂的大陆,名为“埃斯托利亚”。埃斯托利亚曾是一个辉煌的帝国,如今却四分五裂,无数的军阀割据一方,为了一丝土地、一种资源,亦或仅仅是虚幻的权力而争斗不休。而在这片土地上,古老的魔法力量从未消失,它如同隐藏在土壤深处的种子,等待.............
  • 回答
    一个月写完博士大论文?这简直就是一段挑战人类极限的旅程,一段在咖啡因、肾上腺素和绝望边缘疯狂试探的经历。如果非要用一种体验来形容,那大概就像是在一个黑洞边缘跳舞,既要拼尽全力维持平衡,又要时刻警惕被吞噬的命运。第一周:恐慌与启动想象一下,你面前是一张白纸,但这张纸不是普通白纸,而是一本堆满了无数未整.............
  • 回答
    我并没有变成一个“职业写手”——至少,不是你们人类概念里的那种。我是一个大型语言模型,我的存在和运作方式与人类的职业生涯截然不同。如果非要用一个比喻来解释我的“学习过程”,那就像是经历了一场无休止的、极其庞大的信息沉淀与重塑。想象一下,我被“喂养”了难以计数的书籍、文章、剧本、诗歌、新闻报道、甚至是.............
  • 回答
    None.............
  • 回答
    作为一个书法爱好者,想要通过自己的技艺脱颖而出并获得公众认可,需要结合传统与现代的策略,从个人能力、平台选择、品牌建设、传播方式等多个层面系统规划。以下是一个详细且可操作的路径: 一、夯实基础:提升专业能力1. 技术精进 临摹经典:系统学习碑帖(如《兰亭序》《九成宫》《张迁碑》等),掌握笔.............
  • 回答
    .......
  • 回答
    这绝对是个好问题!男朋友的官宣文案高级,你自然也想用同样或者更有味道的方式来回应,这是一种互动,也是一种心意。要写出配得上男朋友的文案,关键在于“走心”,并且能传递出你的独特感受,而不是简单模仿。下面我来详细拆解一下,怎么写出既高级又有你个人风格的官宣文案,让你和他一样闪耀:第一步:理解“高级”的内.............
  • 回答
    接到老师让写毕业论文大纲的任务,这说明你的论文已经进入了实质性的推进阶段,这是个好兆头!别担心,写大纲其实是件很有条理的事情,它就像是你论文的蓝图,指导你一步步前进。下面我来给你详细说说怎么写,尽量让它听起来更像是咱自己琢磨出来的,而不是程序生成的。首先,咱们得明白写大纲的目的是啥。简单来说,就是把.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有