【发布时间】:2016-10-21 09:03:38
【问题描述】:
我有格式为 cccnnn 的 6 字节字符串,其中 c 是字符 A-Z (ASCII 65-90),n 是字符 0-9 (ASCII 48-57)。总共有 263 * 103 = 17,576,000 种不同的组合。
我想创建一个完美的哈希函数,将这种类型的每个字符串映射到一个整数索引,并且我希望它尽可能快。函数不一定要最小,但范围不能太大。两倍的组合数量可能是可以的,但最好不要超过这个数量,因为每个字符串都将映射到已经有 ~2MB 的位数组中的一个位。
我能想到的最明显,也是迄今为止最好的解决方案是将字符串解释为以 26 和 10 为基数的数字,并执行所需的乘法和减法运算以得到 [0, 17576000- 范围内的整数1]:
inline word hash1(unsigned char *buffer)
{
return (((((word) buffer[0] * 26 + buffer[1]) * 26
+ buffer[2]) * 10 + buffer[3]) * 10
+ buffer[4]) * 10 + buffer[5] - 45700328;
}
这里buffer[0-5]包含字符索引,word是uint64_t和45700328 = ((((65*26+65)*26+65)*10+48)*10+48)*10+48的typedef,它将字符转换为正确的基数而不是写(buffer[0] - 65) * 26等。(它节省了一些减法。)
我已经想到了改进这一点的方法。我的一个想法是使用相同的原理,但使用位移而不是乘法。我不得不混合字符的顺序来找到一个尽可能少的操作的解决方案。我发现乘以 260 和 10 只需要两个班次和一个加法,分别是 (x << 8) + (x << 2) 和 (x << 3) + (x << 1),我可以用它来分别计算表达式 ((x2*260+x1)*260+x0)*10+(x4*260+x3)*260+x5-47366978 中的每个乘法,其中 hi = buffer[i]。实现是:
inline word hash1(unsigned char *buffer)
{
word y0, y1, y2, y3, y4;
word x0 = buffer[0]; word x1 = buffer[1];
word x2 = buffer[2]; word x3 = buffer[3];
word x4 = buffer[4]; word x5 = buffer[5];
y0 = (x4 << 2) + (x4 << 8) + x3;
y1 = (y0 << 2) + (y0 << 8) + x5;
y2 = (x2 << 2) + (x2 << 8) + x1;
y3 = (y2 << 2) + (y2 << 8) + x0;
y4 = (y3 << 3) + (y3 << 1) + y1;
return y4 - 47366978;
}
不幸的是,hash2 比hash1 慢一点。这就是我没有好主意的地方。我当然可以尝试制作一个简单地移动每个字符的有效位的函数,将它们堆叠在一起,形成一个 227 位数,但这需要 16MB 向量 = 太大.
那么,无论是使用相同的原理改代码还是使用完全不同的原理,如何才能让我的哈希函数更快按照我在第一段中提到的要求呢?
【问题讨论】:
-
第一个不应该是
(((((buffer[0]-'A') * 26 + (buffer[1]-'A')) * 26 + (buffer[2]-'A')) * 10 + (buffer[3]-'0')) * 10 + (buffer[4]-'0')) * 10 + (buffer[5]-'0')吗? -
我将所有字符减法预先计算为 45700328 的一个减法,因此它们在这方面是等价的。我还需要进行
(word)转换,以便我们处理uint64_t值而不是unsigned char值,否则会由于大乘法而导致溢出。 -
如果您使用的是 x86,一个建议是将基数从 26 更改为 27,因为
lea指令可用于非常快速地乘以 3、5 或 9 -
@SJuan76 过早优化不好,但在这种情况下是合理的。
-
@SectoKia 我没说错。仅仅乘以 27 可能比乘以 26 快。看看反汇编,你会看到
标签: c string optimization hash-function