15

这是我发布此程序的问题的后续行动:

#include <algorithm>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <vector>
#include <chrono>

class Stopwatch
{
public:
    typedef std::chrono::high_resolution_clock Clock;

    //! Constructor starts the stopwatch
    Stopwatch() : mStart(Clock::now())
    {
    }

    //! Returns elapsed number of seconds in decimal form.
    double elapsed()
    {
        return 1.0 * (Clock::now() - mStart).count() / Clock::period::den;
    }

    Clock::time_point mStart;
};

struct test_cast
{
    int operator()(const char * data) const
    {
        return *((int*)data);
    }
};

struct test_memcpy
{
    int operator()(const char * data) const
    {
        int result;
        memcpy(&result, data, sizeof(result));
        return result;
    }
};

struct test_memmove
{
    int operator()(const char * data) const
    {
        int result;
        memmove(&result, data, sizeof(result));
        return result;
    }
};

struct test_std_copy
{
    int operator()(const char * data) const
    {
        int result;
        std::copy(data, data + sizeof(int), reinterpret_cast<char *>(&result));
        return result;
    }
};

enum
{
    iterations = 2000,
    container_size = 2000
};

//! Returns a list of integers in binary form.
std::vector<char> get_binary_data()
{
    std::vector<char> bytes(sizeof(int) * container_size);
    for (std::vector<int>::size_type i = 0; i != bytes.size(); i += sizeof(int))
    {
        memcpy(&bytes[i], &i, sizeof(i));
    }
    return bytes;
}

template<typename Function>
unsigned benchmark(const Function & function, unsigned & counter)
{
    std::vector<char> binary_data = get_binary_data();
    Stopwatch sw;
    for (unsigned iter = 0; iter != iterations; ++iter)
    {
        for (unsigned i = 0; i != binary_data.size(); i += 4)
        {
            const char * c = reinterpret_cast<const char*>(&binary_data[i]);
            counter += function(c);
        }
    }
    return unsigned(0.5 + 1000.0 * sw.elapsed());
}

int main()
{
    srand(time(0));
    unsigned counter = 0;

    std::cout << "cast:      " << benchmark(test_cast(),     counter) << " ms" << std::endl;
    std::cout << "memcpy:    " << benchmark(test_memcpy(),   counter) << " ms" << std::endl;
    std::cout << "memmove:   " << benchmark(test_memmove(),  counter) << " ms" << std::endl;
    std::cout << "std::copy: " << benchmark(test_std_copy(), counter) << " ms" << std::endl;
    std::cout << "(counter:  " << counter << ")" << std::endl << std::endl;

}

我注意到由于某种原因,std::copy它的性能比 memcpy 差得多。在我使用 gcc 4.7 的 Mac 上,输出看起来像这样。

g++ -o test -std=c++0x -O0 -Wall -Werror -Wextra -pedantic-errors main.cpp
cast:      41 ms
memcpy:    46 ms
memmove:   53 ms
std::copy: 211 ms
(counter:  3838457856)

g++ -o test -std=c++0x -O1 -Wall -Werror -Wextra -pedantic-errors main.cpp
cast:      8 ms
memcpy:    7 ms
memmove:   8 ms
std::copy: 19 ms
(counter:  3838457856)

g++ -o test -std=c++0x -O2 -Wall -Werror -Wextra -pedantic-errors main.cpp
cast:      3 ms
memcpy:    2 ms
memmove:   3 ms
std::copy: 27 ms
(counter:  3838457856)

g++ -o test -std=c++0x -O3 -Wall -Werror -Wextra -pedantic-errors main.cpp
cast:      2 ms
memcpy:    2 ms
memmove:   3 ms
std::copy: 16 ms
(counter:  3838457856)

如您所见,即使使用-O3它也比 memcpy 慢 5 倍(!)。

Linux 上的结果类似。

有谁知道为什么?

4

6 回答 6

10

我同意@rici关于开发更有意义的基准的评论,因此我将您的测试重写为使用memcpy(),memmove()和赋值运算符std::copy()对两个向量的复制进行基准测试:std::vector

#include <algorithm>
#include <iostream>
#include <vector>
#include <chrono>
#include <random>
#include <cstring>
#include <cassert>

typedef std::vector<int> vector_type;

void test_memcpy(vector_type & destv, vector_type const & srcv)
{
    vector_type::pointer       const dest = destv.data();
    vector_type::const_pointer const src  = srcv.data();

    std::memcpy(dest, src, srcv.size() * sizeof(vector_type::value_type));
}

void test_memmove(vector_type & destv, vector_type const & srcv)
{
    vector_type::pointer       const dest = destv.data();
    vector_type::const_pointer const src  = srcv.data();

    std::memmove(dest, src, srcv.size() * sizeof(vector_type::value_type));
}

void test_std_copy(vector_type & dest, vector_type const & src)
{
    std::copy(src.begin(), src.end(), dest.begin());
}

void test_assignment(vector_type & dest, vector_type const & src)
{
    dest = src;
}

auto
benchmark(std::function<void(vector_type &, vector_type const &)> copy_func)
    ->decltype(std::chrono::milliseconds().count())
{
    std::random_device rd;
    std::mt19937 generator(rd());
    std::uniform_int_distribution<vector_type::value_type> distribution;

    static vector_type::size_type const num_elems = 2000;

    vector_type dest(num_elems);
    vector_type src(num_elems);

    // Fill the source and destination vectors with random data.
    for (vector_type::size_type i = 0; i < num_elems; ++i) {
        src.push_back(distribution(generator));
        dest.push_back(distribution(generator));
    }

    static int const iterations = 50000;

    std::chrono::time_point<std::chrono::system_clock> start, end;

    start = std::chrono::system_clock::now();

    for (int i = 0; i != iterations; ++i)
        copy_func(dest, src);

    end = std::chrono::system_clock::now();

    assert(src == dest);

    return
        std::chrono::duration_cast<std::chrono::milliseconds>(
            end - start).count();
}

int main()
{
    std::cout
        << "memcpy:     " << benchmark(test_memcpy)     << " ms" << std::endl
        << "memmove:    " << benchmark(test_memmove)    << " ms" << std::endl
        << "std::copy:  " << benchmark(test_std_copy)   << " ms" << std::endl
        << "assignment: " << benchmark(test_assignment) << " ms" << std::endl
        << std::endl;
}

我只是为了好玩而使用 C++11 有点过火了。

以下是我在使用 g++ 4.6.3 的 64 位 Ubuntu 机器上得到的结果:

$ g++ -O3 -std=c++0x foo.cpp ; ./a.out 
memcpy:     33 ms
memmove:    33 ms
std::copy:  33 ms
assignment: 34 ms

结果都相当可比!当我在向量中更改整数类型时,我在所有测试用例中都得到了可比较的时间,例如to 。long long

除非我的基准重写被破坏,否则您自己的基准似乎没有进行有效的比较。!

于 2012-10-29T23:41:43.577 回答
8

在我看来,答案是 gcc 可以优化这些对 memmove 和 memcpy 的特定调用,但不能优化 std::copy。gcc 知道 memmove 和 memcpy 的语义,在这种情况下可以利用已知大小 (sizeof(int)) 的事实将调用转换为单个 mov 指令。

std::copy 是根据 memcpy 实现的,但显然 gcc 优化器无法弄清楚 data + sizeof(int) - data 恰好是 sizeof(int)。所以基准调用 memcpy。

我通过调用 gcc-S并快速浏览输出得到了所有这些;我很容易弄错了,但我看到的似乎与你的测量结果一致。

顺便说一句,我认为测试或多或少毫无意义。一个更合理的真实世界测试可能是创建一个实际vector<int> src的 和int[N] dst,然后与 进行memcpy(dst, src.data(), sizeof(int)*src.size())比较std::copy(src.begin(), src.end(), &dst)

于 2012-10-29T21:16:18.980 回答
3

memcpy并且std::copy每个都有它们的用途,std::copy应该(正如下面的 Cheers 所指出的)和 memmove 一样慢,因为不能保证内存区域会重叠。这意味着您可以非常轻松地复制非连续区域(因为它支持迭代器)(想想像链表等稀疏分配的结构......甚至是实现迭代器的自定义类/结构)。memcpy仅在连续的原因上工作,因此可以进行大量优化。

于 2012-10-29T19:41:46.563 回答
3

那不是我得到的结果:

> g++ -O3 XX.cpp 
> ./a.out
cast:      5 ms
memcpy:    4 ms
std::copy: 3 ms
(counter:  1264720400)

Hardware: 2GHz Intel Core i7
Memory:   8G 1333 MHz DDR3
OS:       Max OS X 10.7.5
Compiler: i686-apple-darwin11-llvm-g++-4.2 (GCC) 4.2.1

在 Linux 机器上,我得到不同的结果:

> g++ -std=c++0x -O3 XX.cpp 
> ./a.out 
cast:      3 ms
memcpy:    4 ms
std::copy: 21 ms
(counter:  731359744)


Hardware:  Intel(R) Xeon(R) CPU E5-2670 0 @ 2.60GHz
Memory:    61363780 kB
OS:        Linux ip-10-58-154-83 3.2.0-29-virtual #46-Ubuntu SMP
Compiler:  g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
于 2012-10-29T20:16:46.787 回答
1

根据G++ 4.8.1的汇编器输出,test_memcpy

movl    (%r15), %r15d

test_std_copy

movl    $4, %edx
movq    %r15, %rsi
leaq    16(%rsp), %rdi
call    memcpy

如您所见,std::copy成功识别出它可以使用 复制数据memcpy,但由于某种原因没有发生进一步的内联 - 这就是性能差异的原因。

顺便说一句,Clang 3.4为这两种情况生成了相同的代码:

movl    (%r14,%rbx), %ebp
于 2013-11-10T03:10:19.807 回答
0

编辑:我把这个答案留作参考,gcc 的奇怪时间似乎是“代码对齐”的产物(见评论)


我正要说这是当时 gcc 4 中的一个实现故障,但它可能比这更复杂。我的结果是(计数器使用 20000/20000):

$ g++ -Ofast a.cpp; ./a.out
cast:      24 ms
memcpy:    47 ms
memmove:   24 ms
std::copy: 24 ms
(counter:  1787289600)

$ g++ -O3 a.cpp; ./a.out
cast:      24 ms
memcpy:    24 ms
memmove:   24 ms
std::copy: 47 ms
(counter:  1787289600)
$ g++ --version
g++ (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008

注意使用copymemcpy编译时如何交换-O3结果-Ofastmemmove也不比两者都慢。

结果clang更简单:

$ clang++ -O3 a.cpp; ./a.out
cast:      26 ms
memcpy:    26 ms
memmove:   26 ms
std::copy: 26 ms
(counter:  1787289600)

$ clang++ -Ofast a.cpp; ./a.out
cast:      26 ms
memcpy:    26 ms
memmove:   26 ms
std::copy: 26 ms
(counter:  1787289600)
$ clang++ --version
clang version 9.0.0-2 (tags/RELEASE_900/final)

perf结果:https ://pastebin.com/BZCZiAWQ

于 2020-02-20T03:36:59.683 回答