Memcpy vs Memmove - 调试与发布

Memcpy vs Memmove - Debug vs Release

本文关键字:调试 vs Memmove Memcpy      更新时间:2023-10-16

我的x64多线程应用程序的行为非常奇怪。调试模式下的执行时间比发布模式下快。

我分解了问题并发现了问题:调试方式优化(!请注意,优化已关闭!Memcpy到Memmove,它的速度更快。释放模式仍然使用memcpy(!注意优化已打开)。

此问题会减慢我的多线程应用程序在发布模式下的速度。 :(

有人知道吗?

#include <time.h>
#include <iostream>
#define T_SIZE 1024*1024*2
int main()
{
    clock_t start, end;
    char data[T_SIZE];
    char store[100][T_SIZE];
    start = clock();
    for (int i = 0; i < 4000; i++) {
        memcpy(store[i % 100], data, T_SIZE);
    }
    // Debug > Release Time 1040 < 1620
    printf("memcpy: %dn", clock() - start);
    start = clock();
    for (int i = 0; i < 4000; i++) {
        memmove(store[i % 100], data, T_SIZE);
    }
    // Debug > Release Time 1040 > 923
    printf("memmove: %dn", clock() - start);
}

以下答案仅适用于VS2013

我们这里所拥有的实际上比memcpy更奇怪。 memmove .这是内在优化实际上减慢速度的情况。该问题源于VS2013内联内存拷贝的事实,如下所示:

; 73   :        memcpy(store[i % 100], data, sizeof(data));
    mov eax, 1374389535             ; 51eb851fH
    mul esi
    shr edx, 5
    imul    eax, edx, 100               ; 00000064H
    mov ecx, esi
    sub ecx, eax
    movsxd  rcx, ecx
    shl rcx, 21
    add rcx, r14
    mov rdx, r13
    mov r8d, 16384              ; 00004000H
    npad    12
    $LL413@wmain:
    movups  xmm0, XMMWORD PTR [rdx]
    movups  XMMWORD PTR [rcx], xmm0
    movups  xmm1, XMMWORD PTR [rdx+16]
    movups  XMMWORD PTR [rcx+16], xmm1
    movups  xmm0, XMMWORD PTR [rdx+32]
    movups  XMMWORD PTR [rcx+32], xmm0
    movups  xmm1, XMMWORD PTR [rdx+48]
    movups  XMMWORD PTR [rcx+48], xmm1
    movups  xmm0, XMMWORD PTR [rdx+64]
    movups  XMMWORD PTR [rcx+64], xmm0
    movups  xmm1, XMMWORD PTR [rdx+80]
    movups  XMMWORD PTR [rcx+80], xmm1
    movups  xmm0, XMMWORD PTR [rdx+96]
    movups  XMMWORD PTR [rcx+96], xmm0
    lea rcx, QWORD PTR [rcx+128]
    movups  xmm1, XMMWORD PTR [rdx+112]
    movups  XMMWORD PTR [rcx-16], xmm1
    lea rdx, QWORD PTR [rdx+128]
    dec r8
    jne SHORT $LL413@wmain

这样做的问题是我们正在执行未对齐的 SSE 加载和存储,这实际上比仅使用标准 C 代码慢。我通过从Visual Studio中包含的源代码中获取CRT实现并制作my_memcpy来验证这一点

为了确保缓存在所有这些过程中是温暖的,我已经预先初始化了所有data但结果很能说明问题:

预热耗时 43 毫秒
my_memcpy 上行耗时 862ms
内存移动上移耗时 676 毫秒
内存上升花了1329ms

那么为什么memmove更快呢?因为它不会尝试事先优化,因为它必须假设数据可以重叠。

对于那些好奇的人,这是我的完整代码:

#include <cstdlib>
#include <cstring>
#include <chrono>
#include <iostream>
#include <random>
#include <functional>
#include <limits>
namespace {
    const auto t_size = 1024ULL * 1024ULL * 2ULL;
    __declspec(align(16 )) char data[t_size];
    __declspec(align(16 )) char store[100][t_size];
    void * __cdecl my_memcpy(
        void * dst,
        const void * src,
        size_t count
        )
    {
        void * ret = dst;
        /*
        * copy from lower addresses to higher addresses
        */
        while (count--) {
            *(char *)dst = *(char *)src;
            dst = (char *)dst + 1;
            src = (char *)src + 1;
        }
        return(ret);
    }
}
int wmain(int argc, wchar_t* argv[])
{
    using namespace std::chrono;
    std::mt19937 rd{ std::random_device()() };
    std::uniform_int_distribution<short> dist(std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
    auto random = std::bind(dist, rd);
    auto start = steady_clock::now();
    // warms up the cache and initializes
    for (int i = 0; i < t_size; ++i)
            data[i] = static_cast<char>(random());
    auto stop = steady_clock::now();
    std::cout << "Warm up took " << duration_cast<milliseconds>(stop - start).count() << "msn";
    start = steady_clock::now();
    for (int i = 0; i < 4000; ++i)
        my_memcpy(store[i % 100], data, sizeof(data));
    stop = steady_clock::now();
    std::cout << "my_memcpy took " << duration_cast<milliseconds>(stop - start).count() << "msn";
    start = steady_clock::now();
    for (int i = 0; i < 4000; ++i)
        memmove(store[i % 100], data, sizeof(data));
    stop = steady_clock::now();
    std::cout << "memmove took " << duration_cast<milliseconds>(stop - start).count() << "msn";

    start = steady_clock::now();
    for (int i = 0; i < 4000; ++i)
        memcpy(store[i % 100], data, sizeof(data));
    stop = steady_clock::now();
    std::cout << "memcpy took " << duration_cast<milliseconds>(stop - start).count() << "msn";
    std::cin.ignore();
    return 0;
}

更新

在调试时,我发现编译器确实检测到我从 CRT 复制的代码是memcpy的,但它将其链接到 CRT 本身中的非内部版本,该版本使用 rep movs 而不是上面的大量 SSE 循环。似乎问题只出在内部版本上。

更新 2

根据评论中的Z玻色子,这似乎都非常依赖于架构。在我的CPU上,rep movsb更快,但在较旧的CPU上,SSE或AVX实现有可能更快。这是根据英特尔优化手册。对于未对齐的数据,rep movsb在旧硬件上可能会受到高达 25% 的损失。然而,话虽如此,对于绝大多数情况和架构,rep movsb似乎平均会击败SSE或AVX实现。

想法:调用memmove,因为它对您的情况来说是最快的。