我正在尝试创建具有尽可能多的并行性(即CPU流水线)的8个手动编码快速哈希算法,这对于大小为1..8字节的固定长度密钥是合理的。我正在创建8个哈希函数,以便密钥大小是编译时常量。我正在研究常见的哈希算法,并尝试展开它们。与此同时,我想我会问周围是否有人有链接或知道如何做到这一点。
我们的哈希字符集通常只有0..9 A..Z SPC,偶尔会有二进制短或长。我知道1 char散列可能没用,所以我会这样做,所以我们的代码不会用那么小的散列键编译。完美并不像速度那么重要。这意味着,如果我们消除足够的CPU成本生成哈希,偶尔使用2次项目的桶就可以了。我已经对我们的代码进行了多年的广泛分析,所以如果我采取下一步使哈希算法更好/更快,我知道它将为我们的公司节省多少钱。
我已经可以做得比我们的应用程序当前使用的哈希函数更好,下面的UlHashOld(也可能会更改),但我认为在SO的帮助下我可以做得更好一些。例如,与UlHashOld相比,我的2个字符的哈希函数将最大桶数减少了一半,但它仍然有许多未填充的桶。如果想在获取字母数字键时获得两个char哈希以更好地分散其位。我用以下代码对此进行了测试:
template <int cb> class CFixLenKeyBase {
protected:
char m_ach[cb];
public:
CFixLenKeyBase(LPCSTR sz) { memcpy(m_ach, sz, cb); }
UL UlHashOld() {
UL n = 0;
for (LPCSTR sz = m_ach, szEnd = sz + cb; sz < szEnd; sz++)
n = n ^ (n >> 25) ^ (n << 7) ^ *sz;
return n;
}
};
template <int cb> class CFixLenKey : public CFixLenKeyBase<cb> {
public:
CFixLenKey(LPCSTR sz) : CFixLenKeyBase(sz) { }
};
template <> class CFixLenKey<1> : public CFixLenKeyBase<1> {
public:
CFixLenKey(LPCSTR sz) : CFixLenKeyBase(sz) { }
UL UlHash(void) const { return *m_ach; }
};
template <> class CFixLenKey<2> : public CFixLenKeyBase<2> {
public:
CFixLenKey(LPCSTR sz) : CFixLenKeyBase(sz) { }
UL UlHash(void) const {
auto a = *(WORD*)m_ach;
return b = (a >> 6) ^ a;
}
};
template <> class CFixLenKey<3> : public CFixLenKeyBase<3> {
public:
CFixLenKey(LPCSTR sz) : CFixLenKeyBase(sz) { }
UL UlHash(void) const {
UL a = (*(UL*)m_ach)&0xFFFFFF;
UL b = (a >> 14) ^ (a >> 6);
UL c = (a >> 11) ^ (a << 5);
UL d = (a >> 19) ^ (a << 9);
return d ^ c ^ b;
}
};
char a[36*36*36*36][4] = {0};
int main(int argc, char* argv[])
{
const int cBuckets = 256;
int aHisto[4][cBuckets] = { 0 };
int aHistoOld[4][cBuckets] = { 0 };
int aMax[4] = { 0 };
int cElem = 36*36*36*36;
memcpy(a[0], "0000", 4);
for (int iElem=1 ; iElem<cElem ; ++iElem) {
auto& b = a[iElem];
memcpy(b,a[iElem-1], 4);
int i = 3;
while (1) {
b[i]++;
if (b[i] == '9' + 1) {
b[i] = 'A';
} else if (b[i] == 'Z' + 1) {
b[i] = '0';
--i;
} else {
break;
}
}
}
int fMask = cBuckets - 1;
int ah[4] = {0}, ahOld[4] = {0}, aSame[4] = {0},aSameOld[4] = {0};
for (int iElem=0 ; iElem<cElem ; ++iElem) {
int h, hOld, n, cb;
cb = 0;
CFixLenKey<1> A(a[iElem]);
if ((h = A.UlHash()) == ah[cb] && memcmp(a+iElem, a+iElem-1,cb))
aSame[cb]++;
if ((hOld = A.UlHashOld()) == ahOld[cb] && memcmp(a+iElem, a+iElem-1,cb))
aSameOld[cb]++;
ahOld[cb] = hOld; ah[cb]=h;
aHisto[cb][h&fMask]++;
aHistoOld[cb][hOld&fMask]++;
cb = 1;
CFixLenKey<2> B(a[iElem]);
if ((h = B.UlHash()) == ah[cb] && memcmp(a+iElem, a+iElem-1,cb))
aSame[cb]++;
if ((hOld = B.UlHashOld()) == ahOld[cb] && memcmp(a+iElem, a+iElem-1,cb))
aSameOld[cb]++;
ahOld[cb] = hOld; ah[cb]=h;
aHisto[cb][h&fMask]++;
aHistoOld[cb][hOld&fMask]++;
cb = 2;
CFixLenKey<3> C(a[iElem]);
if ((h = C.UlHash()) == ah[cb] && memcmp(a+iElem, a+iElem-1,cb))
aSame[cb]++;
if ((hOld = C.UlHashOld()) == ahOld[cb] && memcmp(a+iElem, a+iElem-1,cb))
aSameOld[cb]++;
ahOld[cb] = hOld; ah[cb]=h;
aHisto[cb][h&fMask]++;
aHistoOld[cb][hOld&fMask]++;
}
for (int cb=0 ; cb<4 ; ++cb) {
int nMin=aHisto[cb][0], nMax = nMin;
for (int i=0 ; i<fMask ; ++i) {
if (aHisto[cb][i] < nMin)
nMin = aHisto[cb][i];
if (aHisto[cb][i] > nMax)
nMax = aHisto[cb][i];
}
int nMinOld=aHistoOld[cb][0], nMaxOld = nMinOld;
for (int i=0 ; i<fMask ; ++i) {
if (aHistoOld[cb][i] < nMinOld)
nMinOld = aHistoOld[cb][i];
if (aHistoOld[cb][i] > nMaxOld)
nMaxOld = aHistoOld[cb][i];
}
printf("%d %03d %03d %03d %03d Same: %04d %04d\n", cb, nMin, nMax, nMinOld, nMaxOld, aSame[cb], aSameOld[cb]);
//for (int i=0 ; i<fMask ; ++i) {
// printf("%03d ", aHisto[cb][i]);
// if ((i&31) == 31)
// printf("\n");
//}
printf("\n");
}
return 0;
}
答案 0 :(得分:0)
第一个答案涉及在尝试散列时经常遇到的一般性问题,它可能会让您有个先机。
第二个处理(除其他事项外)将哈希算法微调到手头的输入。即使你不确切地知道它会是什么样子,你可以在你的live / production函数中实现统计信息收集(例如,在添加一个哈希条目时重新加入#),并且有一个并行线程来测试索引大小的替代组合并重新散列常量,对所有输入值进行虚拟散列并比较得到的重新散列数。这样,即使在算法投入生产后,也可以不断微调算法。
我的哈希有一个转折,因为桶区域不与主索引区域分开。不是&#34;这是你应该实现散列的方式&#34;但是我发现一种优越的方法:逻辑和内部循环变得更加简单,更快,这就是哈希的全部内容。