Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/140.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么在我的测试程序中,从字符缓冲区读取一个int时,`std::copy`5x(!)比`memcpy`慢?_C++_Performance_X86 64_Benchmarking_Compiler Optimization - Fatal编程技术网

C++ 为什么在我的测试程序中,从字符缓冲区读取一个int时,`std::copy`5x(!)比`memcpy`慢?

C++ 为什么在我的测试程序中,从字符缓冲区读取一个int时,`std::copy`5x(!)比`memcpy`慢?,c++,performance,x86-64,benchmarking,compiler-optimization,C++,Performance,X86 64,Benchmarking,Compiler Optimization,这是我发布此程序的地方的后续内容: #include <algorithm> #include <cstdlib> #include <cstdio> #include <cstring> #include <ctime> #include <iomanip> #include <iostream> #include <vector> #include <chrono> class St

这是我发布此程序的地方的后续内容:

#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;

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

在Linux上的结果类似


有人知道为什么吗?

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

这不是我得到的结果:

> 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

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

copy是用memcpy实现的,但显然gcc优化器无法确定data+sizeof(int)-data就是sizeof(int)。因此基准测试称为memcpy

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

顺便说一句,我认为这个测试或多或少是没有意义的。更合理的现实测试可能是创建一个实际的
向量src
和一个
int[N]dst
,然后将
memcpy(dst,src.data(),sizeof(int)*src.size())
std::copy(src.begin(),src.end(),&dst)进行比较

我同意开发更有意义的基准测试,因此我使用
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;
}
结果都很有可比性!当我在向量中更改整数类型(例如,更改为
long
)时,我在所有测试用例中都得到了可比较的时间


除非我的基准重写被破坏,否则看起来您自己的基准没有执行有效的比较。嗯

根据汇编程序的输出,
test\u memcpy

movl    (%r15), %r15d
movl    $4, %edx
movq    %r15, %rsi
leaq    16(%rsp), %rdi
call    memcpy
测试标准副本

movl    (%r15), %r15d
movl    $4, %edx
movq    %r15, %rsi
leaq    16(%rsp), %rdi
call    memcpy
如您所见,
std::copy
成功地识别出它可以使用
memcpy
复制数据,但由于某些原因,没有发生进一步的内联-这就是性能差异的原因

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

movl    (%r14,%rbx), %ebp

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


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

$g++-Ofast a.cpp/a、 出去
演员:24毫秒
memcpy:47毫秒
记忆移动:24毫秒
标准:拷贝:24毫秒
(柜台号码:1787289600)
$g++-O3 a.cpp/a、 出去
演员:24毫秒
memcpy:24毫秒
记忆移动:24毫秒
标准:拷贝:47毫秒
(柜台号码:1787289600)
$g++--版本
g++(Ubuntu 9.2.1-9ubuntu2)9.2.1 20191008
请注意当使用
-O3
-Ofast
编译时,
复制
memcpy
结果是如何交换的。而且
memmove
也不比这两者慢

clang
中,结果更简单:

$clang++-O3 a.cpp/a、 出去
演员:26毫秒
memcpy:26毫秒
记忆移动:26毫秒
标准:拷贝:26毫秒
(柜台号码:1787289600)
$clang++-Ofast a.cpp/a、 出去
演员:26毫秒
memcpy:26毫秒
记忆移动:26毫秒
标准:拷贝:26毫秒
(柜台号码:1787289600)
$clang++--版本
clang版本9.0.0-2(标签/发布号900/最终版)

<代码> Prf>代码>结果:

因为最后一个问题只涉及标准C++,为什么不提供一个可移植的例子?在<代码>添加MeMaTest测试中可能也很有趣。我认为函数和模板是对这个基准的完全重写。只需编写3个循环。使用gcc v4.6.2和
-O3-S-masm=intel
memcpy
测试归结为单个
add edx、[esi+ecx*4]
,而
std::copy
使用较慢的
rep movsb
。我想这就是你要求它复制字符的结果。。。(Clang似乎以相同的方式编译两个版本。)@StackedCrooked:一般来说,这是首选方法,因为它适用于任何范围内的任何迭代器类型
memcpy
适用于标准布局类型和非重叠范围。原则上,编译器可以推断您的
std::copy
调用可以作为对
memcpy
的调用来实现;但是编译器编写人员也有很多其他的事情要担心,同样会注意到性能下降的人也会愿意接受简单的修复,所以这可能不是最优先考虑的。相当于
std::copy
的C是
memmove
-1
std::copy
是一个函数模板,因此在提供指针参数时不使用非指针迭代器。此外,它可以而且应该专门用于复制吊舱的情况。但是,当程序员使用
memcpy
而不是
memmove
时,程序员表示他/她知道内存区域没有重叠,而且恐怕无法将其声明为
std::copy
。因此,<代码> STD::复制应该与 MeMexs/Cuff>相同,并且<<代码> MeMCPY 稍微快一点。至少在一些流行的实现中,它目前不是