gpt4 book ai didi

c# - 更快的字符串 GetHashCode(例如使用多核或 GPU)

转载 作者:太空狗 更新时间:2023-10-29 17:55:43 27 4
gpt4 key购买 nike

根据 http://www.codeguru.com/forum/showthread.php?t=463663 ,C#在3.5中的getHashCode函数实现为:

public override unsafe int GetHashCode()
{
fixed (char* str = ((char*) this))
{
char* chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*) chPtr;
for (int i = this.Length; i > 0; i -= 4)
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
if (i <= 2)
{
break;
}
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
numPtr += 2;
}
return (num + (num2 * 0x5d588b65));
}
}

我很好奇是否有人能想出一个返回相同结果但速度更快的函数。增加主应用程序的整体启动和资源开销是可以的。要求一次性初始化(每次应用程序执行,而不是每次调用或每个字符串)是可以的。

请注意,与 Microsoft 不同的是,考虑因素包括“以这种方式进行操作会使其他一切变慢,而且成本会使这种方法变得愚蠢!”可以忽略,因此即使假设 Microsoft 是完美的,也可以通过做一些“愚蠢的事情”来打败它。

这纯粹是出于好奇,不会在实际代码中使用。

我想到的想法示例:

  • 使用多核(独立计算 num2 和 num)
  • 使用GPU

最佳答案

使函数执行得更快的一种方法是将特殊情况考虑在内。具有可变大小输入的函数具有基于大小的特殊情况。

只有当并行的成本降低时,并行才有意义小于增益,对于这种计算,很可能字符串必须相当大才能克服成本 fork 一个并行线程。但是实现起来并不难;基本上你需要对此进行测试。长度超过经验确定阈值,然后 fork 多个线程来计算对子字符串进行散列,最后一步将子散列组合成最后的哈希。实现留给读者。

现代处理器也有SIMD指令,可以处理一条指令中的 32(或 64)字节。这会让你以一二处理 32(16 位字符) block 中的字符串每个 block 的 SIMD 指令;然后将 64 字节的结果折叠成最后一个哈希码。这可能非常快对于任何合理大小的字符串。这个的实现来自 C# 更难,因为人们不期望虚拟机提供对 SIMD 指令的简单(或便携)访问你需要的。实现也留给了读者。编辑:另一个答案表明 Mono 系统确实提供SIMD 指令访问。

话虽如此,展示的特定实现非常愚蠢。关键观察是循环在每次迭代中检查两次限制。可以通过提前检查结束条件案例来解决这个问题,并执行一个执行正确迭代次数的循环。通过使用可以做得更好 Duffs device跳入 N 次迭代的展开循环。这摆脱了N-1 次迭代的循环限制检查开销。那个修改将非常容易,并且肯定值得努力实现。

编辑:您还可以结合 SIMD 思想和循环展开思想,以便在一些 SIMD 指令中处理许多 8/16 字符 block 。

对于不能跳入循环的语言,可以做等同于Duff 的设备通过简单地剥掉最初的外壳。射门如何使用循环剥离方法重新编码原始代码如下:

    public override unsafe int GetHashCode()
{
fixed (char* str = ((char*) this))
{
const int N=3; // a power of two controlling number of loop iterations
char* chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*) chPtr;
count = this.length;
unrolled_iterations = count >> (N+1); // could be 0 and that's OK
for (int i = unrolled_iterations; i > 0; i--)
{
// repeat 2**N times
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[2];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[3]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[4];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[5]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[6];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[7]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[8];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[9]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[10];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[11]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[12];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[13]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[14];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[15]; }
numPtr += 16;
}
if (count & ((1<<N)-1))
{
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[2];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[3]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[4];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[5]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[6];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[7]; }
numPtr += 8;
}
if (count & ((1<<(N-1))-1))
{
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1]; }
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[2];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[3]; }
numPtr += 4;
}
if (count & ((1<<(N-2)-1))
{
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1]; }
numPtr += 2;
}
// repeat N times and finally:
if { count & (1) }
{
{ num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
// numPtr += 1;
}

return (num + (num2 * 0x5d588b65));
}
}

我没有编译或测试这段代码,但思路是对的。这取决于编译器进行合理的常量折叠和地址算法。

我试图对此进行编码以保留原始的确切哈希值,但恕我直言,这并不是真正的要求。如果它不使用,它会更简单,更快一点num/num2 特技,但只是为每个字符更新了 num。


作为静态函数的更正版本(由 Brian 编写):

    public static unsafe int GetHashCodeIra(string x)
{
fixed (char* str = x.ToCharArray())
{
const int N = 2; // a power of two controlling number of loop iterations
char* chPtr = str;
int num = 0x15051505;
int num2 = num;
int* numPtr = (int*)chPtr;
int count = (x.Length+1) / 2;
int unrolled_iterations = count >> (N+1); // could be 0 and that's OK
for (int i = unrolled_iterations; i > 0; i--)
{ // repeat 2**N times
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
}
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[2];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[3];
}
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[4];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[5];
}
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[6];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[7];
}
numPtr += 8;
}
if (0 != (count & ((1 << N) )))
{
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
}
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[2];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[3];
}
numPtr += 4;
}
if (0 != (count & ((1 << (N - 1) ))))
{
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
num2 = (((num2 << 5) + num2) + (num2 >> 0x1b)) ^ numPtr[1];
}
numPtr += 2;
}
// repeat N times and finally:
if (1 == (count & 1))
{
{
num = (((num << 5) + num) + (num >> 0x1b)) ^ numPtr[0];
// numPtr += 1;
}
}

return (num + (num2 * 0x5d588b65));
}
}

关于c# - 更快的字符串 GetHashCode(例如使用多核或 GPU),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1650395/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com