Memcpy vs Memmove - 调试与发布

Via*_*rus 0 c++ debugging release visual-studio c++11

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

我打破了问题并找到了问题:调试模式优化(!注意优化已关闭!)memmpy memmove,它更快地执行.释放模式仍然使用memcpy(!note optimition on).

此问题在发布模式下减慢了我的多线程应用程序.:(

有人有什么想法?

#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: %d\n", 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: %d\n", clock() - start);
}
Run Code Online (Sandbox Code Playgroud)

Mge*_*etz 6

以下的答案必须是有效的VS2013 ONLY

我们在这里其实不仅仅是陌生人memcpy对比memmove.这是内在优化的一个例子,实际上减慢了事情.问题源于VS2013内联memcopy这样的事实:

; 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
Run Code Online (Sandbox Code Playgroud)

这个问题是我们正在进行未对齐的SSE加载和存储,这实际上比仅使用标准C代码要慢.我通过从visual studio中包含的源代码中获取CRT实现并进行了验证来验证这一点my_memcpy

作为一种确保缓存在所有这些过程中都很温暖的方法,我已经预先初始化了所有内容,data但结果告诉我们:

热身花了43毫秒
my_memcpy起来花了862ms
memmove起来花了676ms
memcpy花了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() << "ms\n";

    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() << "ms\n";

    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() << "ms\n";


    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() << "ms\n";
    std::cin.ignore();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

更新

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

更新2

Per Zoson在评论中似乎都非常依赖于架构.在我的CPU rep movsb上更快,但在较旧的CPU上,SSE或AVX实现可能更快.这是根据英特尔优化手册.对于未对齐的数据rep movsb,旧硬件可能会遭受高达25%的损失.然而,也就是说,似乎绝大多数案例和架构rep movsb平均会超过SSE或AVX实施.