随机内存访问很昂贵?

时间:2011-02-16 21:52:27

标签: c++ optimization memory hash

在优化我的连接四个游戏引擎期间,我达到了一个点,在这一点上,进一步的改进只能是最小的,因为以下代码示例中的指令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是正确的,并且缓慢失误的主要原因是缓慢失效。

5 个答案:

答案 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; }