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; }
Ideone のベンチマークがあります。
今後の参考のために、コードは次のとおりです。
#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, data, 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, 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
};
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)