Memcpy vs Memmove - 调试与发布

时间:2014-11-16 10:58:12

标签: c++ visual-studio debugging c++11 release

我的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);
}

2 个答案:

答案 0 :(得分:6)

以下答案适用于VS2013

我们这里的内容实际上比memcpymemmove更为奇怪。内在优化的一个案例实际上减慢了事情。问题源于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

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

来验证这一点

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

  

热身时间为43毫秒   my_memcpy上涨了862ms
  memmove up花了676ms
  memcpy up花了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;
}

更新

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

更新2

评论中的Per Z boson似乎这完全取决于架构。在我的CPU rep movs上更快,但在较旧的CPU上,SSE或AVX实现可能更快。这是Intel Optimization Manual。对于未对齐的数据,rep movsb可能会对旧硬件造成高达25%的损失。然而,也就是说,似乎绝大多数案例和架构rep movsb平均会超过SSE或AVX实施。

答案 1 :(得分:0)

想法:致电memmove,因为它对您来说最快。