在优化我的连接四个游戏引擎期间,我达到了一个点,在这一点上,进一步的改进只能是最小的,因为以下代码示例中的指令TableEntry te = mTable[idx + i]
使用了大部分CPU时间
TableEntry getTableEntry(unsigned __int64 lock)
{
int idx = (lock & 0xFFFFF) * BUCKETSIZE;
for (int i = 0; i < BUCKETSIZE; i++)
{
TableEntry te = mTable[idx + i]; // bottleneck, about 35% of CPU usage
if (te.height == NOTSET || lock == te.lock)
return te;
}
return TableEntry();
}
哈希表mTable
定义为std::vector<TableEntry>
,大约有4.2密耳。托管(约64 MB)。我试图通过在没有速度提升的情况下将表格分配给vector
来替换new
。
我怀疑随机访问内存(因为Zobrist Hashing功能)可能很昂贵,但真的那么多?你有改进功能的建议吗?
谢谢!
修改: BUCKETSIZE
的值为4.它用作collision strategy。一个TableEntry的大小是16字节,结构如下所示:
struct TableEntry
{ // Old New
unsigned __int64 lock; // 8 8
enum { VALID, UBOUND, LBOUND }flag; // 4 4
short score; // 4 2
char move; // 4 1
char height; // 4 1
// -------
// 24 16 Bytes
TableEntry() : lock(0LL), flag(VALID), score(0), move(0), height(-127) {}
};
摘要:此功能最初需要39秒。在进行jdehaan建议的更改后,该功能现在需要33秒(程序在100秒后停止)。它更好,但我认为Konrad Rudolph是正确的,并且缓慢失误的主要原因是缓慢失效。
答案 0 :(得分:5)
您正在复制表条目,将TableEntry&
用作类型。对于底部的默认值,静态默认值TableEntry()
也可以。我想这就是你失去很多时间的地方。
const TableEntry& getTableEntry(unsigned __int64 lock)
{
int idx = (lock & 0xFFFFF) * BUCKETSIZE;
for (int i = 0; i < BUCKETSIZE; i++)
{
// hopefuly now less than 35% of CPU usage :-)
const TableEntry& te = mTable[idx + i];
if (te.height == NOTSET || lock == te.lock)
return te;
}
return DEFAULT_TABLE_ENTRY;
}
答案 1 :(得分:4)
表条目有多大?我怀疑这是副本,而不是内存查找是昂贵的。
如果由于缓存命中它们是连续的,则内存访问会更快,但看起来你正在这样做。
答案 2 :(得分:2)
关于复制TableEntry
的观点是有效的。但是让我们来看看这个问题:
我怀疑随机访问内存(...)可能很昂贵,但真的那么多?
总之,是。
使用您的大小数组进行随机内存访问是缓存杀手。它会产生大量缓存未命中,比缓存中的内存访问速度慢up to three orders of magnitude。三个数量级 - 这是1000倍。
另一方面,它实际上看起来好像你按顺序使用了很多数组元素,即使你使用哈希生成起始点。这反映了缓存未命中理论,除非您的 BUCKETSIZE
很小并且代码会经常从外部使用不同的lock
值进行调用。
答案 3 :(得分:2)
之前我已经看到了哈希表的这个确切问题。问题是对哈希表的连续随机访问触摸了表所使用的所有内存(主阵列和所有元素)。如果这相对于您的缓存大小很大,您将会捶打。这表现为您遇到的确切问题:由于内存停滞,首先引用新内存的指令似乎成本非常高。
在我研究的情况下,另一个问题是哈希表代表了密钥空间的一小部分。应用于绝大多数密钥的“默认”值(类似于您所谓的DEFAULT_TABLE_ENTRY
)因此似乎像哈希表一样没有被大量使用。问题是尽管默认条目避免了许多插入,但搜索的连续动作一遍又一遍地触摸缓存的每个元素(并且以随机顺序)。在那种情况下,我能够从散列数据中移动值以与相关结构一起使用。它占用了更多的整体空间,因为即使具有默认值的键也必须显式存储默认值,但是引用的位置得到了极大的改善,并且性能提升很大。
答案 4 :(得分:0)
使用指针
TableEntry* getTableEntry(unsigned __int64 lock) {
int idx = (lock & 0xFFFFF) * BUCKETSIZE;
TableEntry* max = &mTable[idx + BUCKETSIZE];
for (TableEntry* te = &mTable[idx]; te < max; te++)
{
if (te->height == NOTSET || lock == te->lock)
return te;
}
return DEFAULT_TABLE_ENTRY; }