面试题答案
一键面试性能差异分析
memcpy
:memcpy
是标准库函数,通常由编译器厂商高度优化,针对不同的硬件平台和 CPU 指令集进行了定制。例如,对于支持 SIMD(单指令多数据)指令集(如 SSE、AVX 等)的 CPU,memcpy
可能会利用这些指令一次性复制多个字节,大大提高复制效率。- 它在处理大块内存复制时,能够充分利用 CPU 的缓存机制,通过合理的预取和缓存命中策略,减少内存访问的延迟。
my_memcpy
:- 逐字节复制的方式每次只复制一个字节,在 CPU 执行指令方面,会产生较多的指令开销,因为每次复制都需要一条指令。
- 这种方式不能充分利用 CPU 的缓存优势,由于每次只访问一个字节,缓存命中率较低,特别是在大块内存复制时,频繁的内存访问会导致较大的延迟。
优化思路及代码示例
-
优化思路:
- 按字(word)复制:现代 CPU 通常以字(如 4 字节或 8 字节,取决于 CPU 架构)为单位进行内存访问效率更高。可以先按字复制,最后处理剩余不足一个字的字节。
- 利用缓存预取:在复制之前,可以使用编译器特定的预取指令(如
_mm_prefetch
用于 x86 架构)来提前将数据预取到缓存中,提高缓存命中率。 - SIMD 优化:如果目标平台支持 SIMD 指令集,可以利用这些指令一次性复制多个字节。不过,这需要更复杂的代码编写,并且要考虑不同 SIMD 指令集的兼容性。
-
按字复制优化后的代码示例:
#include <cstddef>
#include <immintrin.h> // 用于缓存预取,需要支持 SSE 指令集的编译器
void* my_memcpy(void* dest, const void* src, size_t count) {
char* d = static_cast<char*>(dest);
const char* s = static_cast<const char*>(src);
// 按 8 字节(64 位平台常见字大小)对齐复制
size_t alignedCount = count & ~size_t(7);
for (size_t i = 0; i < alignedCount; i += 8) {
// 缓存预取
_mm_prefetch(s + i + 64, _MM_HINT_T0);
*(reinterpret_cast<unsigned long long*>(d + i)) = *(reinterpret_cast<const unsigned long long*>(s + i));
}
// 处理剩余字节
for (size_t i = alignedCount; i < count; ++i) {
d[i] = s[i];
}
return dest;
}
在上述代码中,首先按 8 字节对齐进行复制,同时利用 _mm_prefetch
指令进行缓存预取(假设编译器支持 SSE 指令集),最后处理剩余不足 8 字节的部分。这样优化后,my_memcpy
的性能可以更接近标准库的 memcpy
。
需要注意的是,不同编译器对特定指令的支持可能不同,实际应用中需要根据目标编译器和平台进行调整。同时,在进行 SIMD 优化时,代码会变得更加复杂,并且要充分考虑兼容性问题。