TLDR:我忘了启用编译器优化.启用优化后,性能(几乎)相同.
原帖
从二进制数据读取整数时,我注意到memcpy比铸造解决方案慢.
版本1:reinterpret_cast,由于潜在的对齐问题而臭,但也更快(?)
int get_int_v1(const char * data) { return *reinterpret_cast<const int*>(data); }
版本2:memcpy,正确且稍慢:
int get_int_v2(const char * data) { int result; memcpy(&result,data,sizeof(result)); return result; }
供将来参考,代码是:
#include <cstdlib> #include <cstdio> #include <cstring> #include <ctime> #include <iostream> #include <vector> #include <sys/time.h> double get_current_time() { timeval tv; gettimeofday(&tv,NULL); return double (tv.tv_sec) + 0.000001 * tv.tv_usec; } int get_int_v1(const char * data) { return *reinterpret_cast<const int*>(data); } int get_int_v2(const char * data) { int result; memcpy(&result,sizeof(result)); return result; } const unsigned iterations = 200 * 1000 * 1000; double test_v1(const char * c,unsigned & prevent_optimization) { double start = get_current_time(); for (unsigned i = 0; i != iterations; ++i) { prevent_optimization += get_int_v1(c); } return get_current_time() - start; } double test_v2(const char * c,unsigned & prevent_optimization) { double start = get_current_time(); for (unsigned i = 0; i != iterations; ++i) { prevent_optimization += get_int_v2(c); } return get_current_time() - start; } int main() { srand(time(0)); // Initialize data std::vector<int> numbers(1000); for (std::vector<int>::size_type i = 0; i != numbers.size(); ++i) { numbers[i] = i; } // Repeat benchmark 4 times. for (unsigned i = 0; i != 4; ++i) { unsigned p = 0; std::vector<int>::size_type index = rand() % numbers.size(); const char * c = reinterpret_cast<const char *>(&numbers[index]); std::cout << "v1: " << test_v1(c,p) << std::endl; std::cout << "v2: " << test_v2(c,p) << std::endl << std::endl; } }
结果是:
v1: 0.176457 v2: 0.557588 v1: 0.17654 v2: 0.220581 v1: 0.176826 v2: 0.22012 v1: 0.176131 v2: 0.220633
我的问题是:
>我的基准是否正确?
>如果是,那么为什么v2(使用memcpy)会变慢?由于两个版本都返回了数据的副本,我认为应该没有性能差异.
>如何实施正确,快速的解决方案?
更新
我很傻,忘了考虑Ideone不执行编译器优化.我还稍微调整了一下代码并提出了以下内容:
#include <algorithm> #include <cstdlib> #include <cstdio> #include <cstring> #include <ctime> #include <iomanip> #include <iostream> #include <vector> #include <sys/time.h> double get_current_time() { timeval tv; gettimeofday(&tv,NULL); return double (tv.tv_sec) + 0.000001 * tv.tv_usec; } 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,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 }; std::vector<int> get_random_numbers() { std::vector<int> numbers(container_size); for (std::vector<int>::size_type i = 0; i != numbers.size(); ++i) { numbers[i] = rand(); } return numbers; } std::vector<int> get_random_indices() { std::vector<int> numbers(container_size); for (std::vector<int>::size_type i = 0; i != numbers.size(); ++i) { numbers[i] = i; } std::random_shuffle(numbers.begin(),numbers.end()); return numbers; } template<typename Function> unsigned benchmark(const Function & f,unsigned & counter) { std::vector<int> container = get_random_numbers(); std::vector<int> indices = get_random_indices(); double start = get_current_time(); for (unsigned iter = 0; iter != iterations; ++iter) { for (unsigned i = 0; i != container.size(); ++i) { counter += f(reinterpret_cast<const char*>(&container[indices[i]])); } } return unsigned(0.5 + 1000.0 * (get_current_time() - start)); } 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 << "std::copy: " << benchmark(test_std_copy(),counter) << " ms" << std::endl; std::cout << "(counter: " << counter << ")" << std::endl << std::endl; }
结果现在几乎相等(std :: copy除外,因为某些原因速度较慢):
g++ -o test -O0 -Wall -Werror -Wextra -pedantic-errors main.cpp cast: 56 ms memcpy: 60 ms std::copy: 290 ms (counter: 2854155632) g++ -o test -O1 -Wall -Werror -Wextra -pedantic-errors main.cpp cast: 9 ms memcpy: 14 ms std::copy: 20 ms (counter: 3524665968) g++ -o test -O2 -Wall -Werror -Wextra -pedantic-errors main.cpp cast: 4 ms memcpy: 5 ms std::copy: 20 ms (counter: 2590914608) g++ -o test -O3 -Wall -Werror -Wextra -pedantic-errors main.cpp cast: 4 ms memcpy: 5 ms std::copy: 18 ms (counter: 2590914608)
解决方法
您需要查看发出的代码.显然,优化器“应该”能够将memcpy转换为单个可能未对齐的int大小读入返回值,但是如果你看到不同的时间,那么我认为x86意味着它没有.
在我的机器上,使用gcc和-O2我总是得到0.09.使用-O3我总是得到0(我没有检查它是否比时间粒度更快,或者优化器已经删除了所有代码).
如此可能,答案只是你没有使用正确的编译器标志(或者ideone没有).
在可能未对齐的读取需要来自对齐读取的不同指令的体系结构中,reinterpret_cast可以发出对齐的读取,而memcpy可能必须发出未对齐的读取(取决于函数的调用方式 – 在这种情况下,数据是事实上已经对齐,但我不知道编译器在什么条件下可以证明这一点.在那种情况下,我希望reinterpret_cast代码可以比memcpy更快,但当然在有人传入未对齐指针的情况下它将是不正确的.