C++ 改进值散列
目前我有以下几点:C++ 改进值散列,c++,optimization,C++,Optimization,目前我有以下几点: class Transform { int N; // set in other functions std::unordered_map<int,float> cache; float Wn(int n) { std::unordered_map<int,float>::const_iterator got = cache.find(n); if(got == cache.end()
class Transform
{
int N; // set in other functions
std::unordered_map<int,float> cache;
float Wn(int n)
{
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
return cache[n] = sin((M_PI / (2 * N)) * (n + 0.5f));
return cache[n];
}
类转换
{
int N;//在其他函数中设置
std::无序映射缓存;
浮点数Wn(整数n)
{
std::无序映射::常量迭代器get=cache.find(n);
if(get==cache.end())
返回缓存[n]=sin((μPI/(2*n))*(n+0.5f));
返回缓存[n];
}
由于函数
Wn
被多次调用,并且只有n
参数发生了更改,因此我尝试对其进行缓存。我的问题是,在许多情况下,函数需要的时间比没有缓存的情况长,有时甚至长25%。有没有办法对此进行优化?假设输入整数通常在某个小范围内,只需使用a将数组作为缓存。即使某些值不能缓存,也比哈希更有效。假设输入整数通常在某个小范围内,只需使用数组作为缓存。即使某些值不能缓存,也比哈希更有效。我仔细查看了您的代码,以及可以做些什么来改进我喜欢它
下面是函数wN()的3个带注释的版本:
在这种情况下,这并不重要(优化者负责冗余查找),有一种更符合习惯的方法来编写Wn()
首先是原文:
float original_cached_wN(int n)
{
// compute hash and search
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
// recompute hash
// search again
// default construct
// overwrite
return cache[n] = compute_wN(n);
// recompute hash
// search again
// default construct
// overwrite
return cache[n];
}
最后,只需计算Wn而无需缓存:
float compute_wN(int n) const
{
return sin((M_PI / (2 * N)) * (n + 0.5f));
}
下面是一个测试程序,可以查看由以下3种方法生成的编译源代码:
#include <iostream>
#include <cmath>
#include <unordered_map>
#include <sstream>
#include <vector>
class Transform
{
int N;
std::unordered_map<int,float> cache;
public:
Transform(int N) : N(N) {}
float original_cached_wN(int n)
{
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
return cache[n] = compute_wN(n);
return cache[n];
}
float improved_cached_wN(int n)
{
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
{
got = cache.emplace(n, compute_wN(n)).first;
}
return got->second;
}
float compute_wN(int n) const
{
return sin((M_PI / (2 * N)) * (n + 0.5f));
}
};
int main()
{
using namespace std;
// this is to defeat the optimiser
// and prefent compile-time evaluation of Wn
std::istringstream ss ("5 4 6 7");
int N = 10, n1 = 0, n2 = 1, n3 = 2;
ss >> N >> n1 >> n2 >> n3;
Transform t1(N);
std::vector<float> v = {
t1.original_cached_wN(n1),
t1.improved_cached_wN(n2),
t1.compute_wN(n3)
};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(cout, ", "));
std::cout << std::endl;
return 0;
}
查看编译后的输出,在我看来,搜索和更新映射的成本实际上超过了计算W(n)
以下是apple clang 7在使用选项编译后为compute\u wN()
发出的代码-O3-march=native
movl (%rdi), %eax
addl %eax, %eax
vcvtsi2sdl %eax, %xmm0, %xmm0
vmovsd LCPI2_0(%rip), %xmm1 ## xmm1 = mem[0],zero
vdivsd %xmm0, %xmm1, %xmm0
vcvtsi2ssl %r15d, %xmm0, %xmm1
vaddss LCPI2_1(%rip), %xmm1, %xmm1
vcvtss2sd %xmm1, %xmm1, %xmm1
vmulsd %xmm0, %xmm1, %xmm0
callq _sin
老实说,与地图操作相比,它的代码要少得多。我仔细研究了您的代码,以及可以做些什么来改进它 下面是函数wN()的3个带注释的版本: 在这种情况下,这并不重要(优化者负责冗余查找),有一种更符合习惯的方法来编写
Wn()
首先是原文:
float original_cached_wN(int n)
{
// compute hash and search
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
// recompute hash
// search again
// default construct
// overwrite
return cache[n] = compute_wN(n);
// recompute hash
// search again
// default construct
// overwrite
return cache[n];
}
最后,只需计算Wn而无需缓存:
float compute_wN(int n) const
{
return sin((M_PI / (2 * N)) * (n + 0.5f));
}
下面是一个测试程序,可以查看由以下3种方法生成的编译源代码:
#include <iostream>
#include <cmath>
#include <unordered_map>
#include <sstream>
#include <vector>
class Transform
{
int N;
std::unordered_map<int,float> cache;
public:
Transform(int N) : N(N) {}
float original_cached_wN(int n)
{
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
return cache[n] = compute_wN(n);
return cache[n];
}
float improved_cached_wN(int n)
{
std::unordered_map<int,float>::const_iterator got = cache.find(n);
if(got == cache.end())
{
got = cache.emplace(n, compute_wN(n)).first;
}
return got->second;
}
float compute_wN(int n) const
{
return sin((M_PI / (2 * N)) * (n + 0.5f));
}
};
int main()
{
using namespace std;
// this is to defeat the optimiser
// and prefent compile-time evaluation of Wn
std::istringstream ss ("5 4 6 7");
int N = 10, n1 = 0, n2 = 1, n3 = 2;
ss >> N >> n1 >> n2 >> n3;
Transform t1(N);
std::vector<float> v = {
t1.original_cached_wN(n1),
t1.improved_cached_wN(n2),
t1.compute_wN(n3)
};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(cout, ", "));
std::cout << std::endl;
return 0;
}
查看编译后的输出,在我看来,搜索和更新映射的成本实际上超过了计算W(n)
以下是apple clang 7在使用选项编译后为compute\u wN()
发出的代码-O3-march=native
movl (%rdi), %eax
addl %eax, %eax
vcvtsi2sdl %eax, %xmm0, %xmm0
vmovsd LCPI2_0(%rip), %xmm1 ## xmm1 = mem[0],zero
vdivsd %xmm0, %xmm1, %xmm0
vcvtsi2ssl %r15d, %xmm0, %xmm1
vaddss LCPI2_1(%rip), %xmm1, %xmm1
vcvtss2sd %xmm1, %xmm1, %xmm1
vmulsd %xmm0, %xmm1, %xmm0
callq _sin
老实说,这比映射操作的代码要少得多。实际上它们可以在
SHORT\u MIN
到SHORT\u MAX
的范围内,而且有几个根本不会出现,所以我会消耗不必要的RAM。另外,为了检查值是否已经计算过,我需要另一个布尔数组,增加内存,采用这种表格形式e用于早期的电脑游戏,预先计算了所有可能的值,然后只是查找。@如果一个表中有2^16个条目真的没有那么多,那么您最好预先计算所有的值。2^16只有65k,几乎什么都没有。@Tofife:您不需要布尔数组来知道计算了哪些值。只需在数组中填入al即可启动时的L65536值,或者如果您不喜欢,用NAN填充它,并将其用作未缓存输入的标记。实际上,它们可以在SHORT_MIN
到SHORT_MAX
的范围内,并且一些值根本不会出现,因此我将消耗不必要的RAM。此外,要检查该值是否已经计算过,我还需要另一个boolean数组,增加内存。这种形式的表用于早期的电脑游戏,预先计算所有可能的值,然后再查找。@如果一个包含2^16个条目的表真的没有那么多,那么您最好预先计算它的所有值2^16只有65k,几乎什么都没有。@toffe:您不需要布尔数组来知道是哪个值已经计算了h个。只需在启动时用所有65536个值填充数组,或者如果您不喜欢,用NAN填充数组,并将其用作未缓存输入的标记。它调用sin
,但您似乎忽略了这一点。@harold我想您必须对代码进行基准测试,以了解计算正弦是否更为昂贵在现实中,nsive比(例如)搜索不在缓存中的地图更有效。是的,但我的意思是,你的结论是它“代码更少”…也许吧,但它有一个调用,所以得出的结论真的很奇怪。它可能隐藏了任意多的代码。@harold从内存中一个64位的奔腾需要大约350个周期来计算一个sin。它会这样做,而不需要接触任何内存。它调用sin
,不过,你似乎忽略了这一点。@harold我想你必须对代码进行加密,以了解计算正弦在现实中是否比(例如)搜索不在缓存中的地图更昂贵。是的,但我的意思是,你的结论是它“代码更少”…也许吧,但它有一个调用,所以得出的结论真的很奇怪。它可能隐藏了任意多的代码。@harold从内存中读取64位奔腾大约需要350个周期来计算一个sin。它不需要触摸任何内存就可以做到这一点。