Xxhash: XXH128

创建于 2017-06-08  ·  33评论  ·  资料来源: Cyan4973/xxHash

嗨,你有创建 128 位版本(16 字节用于生成哈希)的计划吗?

最有用的评论

是的

所有33条评论

是的

太好了,所以您需要 2 天或 2 周才能完成?

可能不止于此。

很难确定日期,因为其他活动会妨碍您。

我想我可以在即将到来的夏天找到一些时间来处理它。

谢谢,我真的很高兴听到这个消息

@Cyan4973 有什么进展吗? :)

哎呀,我今年夏天 xxHash 的时间段就在其他优先事项面前消失了。
下一个时段是 10 月 / 11 月。

@Cyan4973 有什么进展吗? :)

有点,但在消极的一面。
我在 11 月 / 12 月的时间范围内测试了几个变体,并且基本上将它们排除在外。
质量太低,或者速度很快降到xxh64以下。

桌面上只剩下几个选项。
现在,我还需要另一个时间框架来处理这些最终候选人......

请避免无耻插件

一个分布良好的 128 位散列可以只使用完整状态 v0、v1、v2、v3? 或者还有更多?

是的,
这本质上是@JCash提案,
使用 XXH64 “core”,只需修改 finalization 阶段,即可生成 128 位输出。

有用。
只是我希望找到一个比 xxh64 运行得更快的变体。
这种变体本质上需要矢量化指令,以更快地消耗数据。
但是指令集相当有限,因此很难同时保持质量和速度。

毕竟这可能是一个坏主意,而@JCash方法由于更直接,可能被证明是更好的方法。

如前所述,在放弃基于矢量化的散列之前,我仍然想尝试一些想法。

这个 128 位哈希的用例是什么? 首先,我想降低冲突的概率,但我知道这对于 64 位哈希来说已经很低了,转向 128 位不会有效率,因为它会占用双倍的存储空间并且增益非常低。

我错过了什么吗?

@Bramzor如果您正在散列大量流量(数十亿个密钥)并且抗碰撞性是高优先级,那么它会成为一个考虑因素。 计算不需要很昂贵,但存储也是一个问题。 收益更高,因为哈希容量的每次增加都是指数级的。 这就是为什么 256 位散列在加密中被认为是牢不可破的。

@bryc 64 位和 128 位或更高的碰撞概率有什么想法/比较吗? 我正在考虑使用 xxhash 对大量数据进行“指纹识别”。 尽管在我的特定用例中误报不会是一个大问题。

https://stackoverflow.com/a/14210379/646947上显示的表格很有趣,可以回答这个问题。

正如@bryc 所说,当有数百万个条目要存储时,128 位变得很有用,并且用户没有适当的冲突解决机制,因此希望显着减少单个冲突发生的机会。

我们可以接受 JCash 对 XXH128 代码的拉取请求以计算 128 位哈希吗? 我不想为此使用未接受的拉取请求,因为如果稍后可能会出现不同的 XXH128 版本,它可能会导致问题。 但是,如果我们可以接受这个拉取请求作为有效的(第一个)XXH128 版本,这不会发生吗? 如果需要,如果有更好/更快的方法,以后总是可以添加 XXH128v2 对吗?

即使我最终选择了类似于@JCash提案的解决方案,
当我一次又一次的矢量化尝试失败时,这变得更有可能,
这将是一个不同的版本。

原因是,从那以后我们学到了很多,
这种学习的好处可以用来改进@JCash提案。

主要有两个方面的改进:

  • 更友好的 32 位 cpu 设计
  • 短输入更有效的快捷方式

第二个相当重要。

@Bramzor如果您不需要 xxHash 框架的任何特定功能,您可以使用成熟的 128 位哈希之一:SpookyHash、MurmurHash3 或 HighwayHash

@Cyan4973我认为我的 zzHash 80% 是作为具有 128 位输出的 x86 哈希完成的。 不幸的是,我没有完成它。 剩下的事情是:

  • 我已经开始添加两个级别的分块,因此即使在 GPU 上也可以有效地计算它。 所有其他哈希都是严格顺序的
  • 终结函数。 您认为它应该尽可能快,以便与现有的小密钥上的 32/64 位哈希竞争。 现在我认为 128 位哈希通常不需要太多速度,因为它们用于长期数据存储(并且 SSD IOPS 远低于我们可以提供的哈希/秒),并且 128 位哈希通常在256+ 位输入,其中 zzHash 已经非常具有竞争力。 所以我认为应该恢复完整的最终确定代码,即使它使 zzHash 在 1-16 字节输入上比 xxHash32/64 慢

80% 是指 80% 的哈希公式设计,不包括使用增量哈希等实际实现所需的工作量。

zzHash 速度为 2.5 bpc,而 xxHash32 为 2 bpc,xxHash64 为 4 bpc。 zzHash-wide,使用 x64 命令,为 5.5 bpc,可以提供高达 512 位的结果,但还没有任何具体的设计。

@青色4973

更友好的 32 位 cpu 设计

:open_mouth: 那将如何运作? 那真是太好了!

我尝试使用 32 位 XXH32 代码在 JavaScript 中生成 XXH128(代码类似于 C ),它仍然很快,但很可能是低质量哈希(不能真正使用 smhasher,但似乎 v3-v4 有更多碰撞)。 所以我也想把它移植到 JS ;)。

@Bulat-Ziganshin:
我完全可以拥有基于 zzHash 版本的 XXH128。
我也确信 XXH128 至少需要和 XXH64 一样快,
否则会被用户视为回归。
另外,创建 XXH64 的衍生物相当容易,它只输出 128 位,这在本质上是@JCash建议的。 所以这就是竞争。

不要太担心小输入,它是一组不同的优化,必须处理不同的代码路径。 我可以解决这个问题。

那将如何运作?

这有点模糊,但可以从考虑 32 位常量而不是 64 位常量开始,
这允许使用像imul这样的指令,它可以从两个 32 位输入产生一个 64 位结果。 在 32 位模式下和通过诸如AVX2之类的矢量指令进行模拟基本上更友好。
对公式的传播质量有一个副作用,但必须仔细权衡。

在引入shiftrotl操作时,需要牢记类似的注意事项。
请注意,我并不是说 32 位系统运行速度与 64 位系统一样快:为此,只需要 32 位指令(如 XXH32),这会降低 64 位性能。

@Cyan4973我开发 m/t 友好哈希的想法怎么样? 你想把它包含在 xxh 中吗? 如果你不买它,我可以跳过它的开发,不过,作为完美主义者,我希望看到它实现。

这个想法是:当输入长于 4KB 时,它被分成 4KB 的块,我们计算每个块的 160 位原始哈希值,就像我们对小于 4KB 的输入所做的那样。 然后我们对这个 160 位值流执行散列。 这允许使用多达 4K/20 = 200 个线程进行散列,使整体计算速度提高 200 倍。 对于大于 800 KB 的输入,我们可以以相同的方式执行第二层,允许使用看起来适合现代 GPU 的多达 40K 线程。

由于用于计算原始 160 位哈希的算法对于较小的输入(小于 4KB)和较大输入的第一个块是相同的,因此我们不需要在增量实现中缓冲数据。 我们只需要为每个添加的散列层存储(在散列状态结构中)额外的 160 位当前原始散列。

我喜欢这个主意。

请注意,虽然当前的哈希速度已经相当高,
这意味着,我怀疑我们可以通过使用 40 个内核获得 40 倍的提升,
因为无论如何内存带宽都会受到限制。

但即使是更有限的 2x/4x 提升仍然不错。

线程同步不是免费的。
对于这样的吞吐量,它实际上是占主导地位的,
因此您可能需要考虑一种不需要每 4K 同步的方案。

过早的优化是万恶之源......

话虽如此,我只能为自己说话,但我认为大多数人都会有相同的看法:XXH64 的速度已经足够好(例如,我只用它来散列一个小字符串),因此,人们会对相同的性能感到满意,但作为 128 位哈希。 为什么人们会使用 XXH128,那是因为他们喜欢使用他们已经熟悉的库。 目前的风险是 XXH128 需要一些时间才能到达所有不同的语言,这样可能会“迫使”人们使用其他散列库。 我可以继续使用 XXH64 一段时间,但在某个时候,无论如何我都需要 128 位,如果它不会很快出现,我将不得不放弃 XXH(我真的很喜欢)进行其他一些哈希。

如果 XXHash 想成为最好的(最快的)128 位哈希,我可以尊重这一点,但我会建议人们(和我自己)去寻找“可行的”并且得到普遍支持的东西。

我的 2 美分。

@Cyan4973你是对的,我们的目标是让内存黑白成为唯一的限制,因为计算非常简单。

在 Nvidia GPU 上,处理 zzHash (2ADD+MUL+ROL) 中每 4 个输入字节的命令需要大约 10 个周期,因此每个线程的速度为 0.5-1 GB/s。 即使有 200 个线程,我们也无法填满 Tesla V100 的 900 GB/s 内存。 所以我们要么进一步增加块大小,要么添加第二层分块。 通过两层,我们可以提供多达 40K 线程的工作,这比填充内存 b/w 所需的多 20-40 倍。 随着时间的推移,GPU 获得更多的 ALU,因此每个 ALU 速度的内存 b/w 比率增加,但这个储备应该足够未来 20 年使用。

为了填充 V100 的内存 b/w 我们需要大约 1K 线程,因此哈希计算将从 4 MB 输入大小开始受内存限制。

在 GPU 上,启动一个 warp(32 个线程)的成本与调用一个子程序的成本差不多,即它只需要复制参数和初始化局部变量,因此与处理 32*4K 字节的成本相比,这个成本可以忽略不计。 OTOH,通过将每 4 KB 的输入压缩成 20 个字节,然后再次进行哈希处理,我们为大于 4 KB 的输入增加了额外的 20B/4KB = 0.5% 的工作量,这又是一个微不足道的代价。

这就是为什么我选择 4 KB 块大小,相应地,200*4KB=800KB 第二层块大小。

实际上,在桌面 CPU/操作系统上同步 4 KB 块的效率很低。 但我们不需要这样做。 CPU 实现中的每个线程都可以处理包含多个块的更大块,然后 1) 将每个块的 160 位原始哈希存储到内存中以供以后处理,或者 2) 完全处理 800 KB 块,计算它们的 160 位哈希以供下一次处理哈希层。

OTOH,32/64 KB 块大小对于桌面 CPU 来说会更有效(没有 0.5% 的开销,要存储的即时数据减少 8-16 倍),但它会增加 V100 的最小有效处理输入大小,从上述 4 MB到 32-64 MB。

我刚刚推出了这个算法的第一个版本(只有一个额外的层)。 主哈希函数关键点: https ://github.com/Bulat-Ziganshin/FARSH/blob/master/SMHasher/xxHashTest.cpp#L270

这允许使用像 imul 这样的指令,它可以从两个 32 位输入产生 64 位结果。 在 32 位模式下以及通过 AVX2 等矢量指令进行模拟基本上更友好。

@青色4973
有没有办法将所有状态保持在 32 位无符号整数 (uint32) 中? 我到处搜索,只有 128 位 x86 MurmurHash3 具有这种特性,对于非 32 位哈希来说非常罕见。 甚至我发现的一些“x86 优化”散列函数仍然使用 uint64 类型进行乘法步骤,遗憾的是,如果不制作 64 位乘法填充程序会降低性能,则无法在 JS 中原生完成。

@bryc zzHash(这是一种可能的 XXH128 实现的游乐场)也具有此功能。 虽然 Cyan(在您的引文中)谈到了另一种以 64 位块处理数据的哈希处理数据,因此使用了 64 位变量,但简化了一些操作以提高 x86/SSE2 效率。 在 64 位块中处理输入会使 64 位 CPU 的性能翻倍,因此您必须做出选择。

@Bulat-Ziganshin

是的,这看起来很有希望。 仔细观察,它似乎可以移植到原生 JS,因为它使用纯 32 位整数。 我试过了,但是我的 C++ 能力很弱,所以我什至无法在 VS 上编译 SMhasher。 但我喜欢组合/最终混合步骤中的想法/变化。

无论如何, :+1: for x86 XXH128 ;) 甚至可能是 x86 XXH64 几乎没有额外的工作。

另一种可能的解决方案:
保持XXH32XXH64 “核心”不变,
只需更改最终确定阶段,
这样XXH32就可以用来产生一个 64 位的散列,
XXH64可用于生成 128 位散列。

这将是相对微不足道的实施。

到目前为止,我没有这样做,因为有混淆的风险:
到目前为止,名称中的32既指操作类型,也指生成的哈希宽度。
这种变化将两个概念分开。
但是如何调用这些变体呢?
特别是,如何确保产生 64 位输出的XXH32变体不会与XXH64混淆?

这听起来像一个简单的问题,但它足以威慑。

尽管如此,我还是喜欢它,因为它使 XXH128 可用于稍后实现在 128 位单元上工作的哈希函数的可能性。

XXH32X2 ? 我知道有点拗口

是的,我现在确信混淆风险足够大,不值得。

所以暂时看不到奇怪的 xxh32->64 。

XXH128 将是一个产生 128 位输出的哈希,
不一定是在 128 位单元上工作的哈希。

如果您想避免混淆,您还可以选择另一种策略:

XXH96 基于 32 位“引擎”。
XXH128 基于 64 位“引擎”。

过去几天我用 SpookyHash (V2) 做了一些测试,128 位 Hash 版本比 xxHash64 (针对沙桥优化的 64 位编译)快一点,在 Sandy Bridge 2500K 上都接近 10GB/s。 测试了 4K、256K 缓冲区(256K 缓存和非缓存)。 同时 SpookyHash 有 64 位和 32 位版本的哈希结果。 对于较小的缓冲区,我会先进行测试,我看到从 1K 到 4K 的性能提高了 20% 以上。

所以对于那些需要在 64 位硬件上运行 128 位哈希的人来说,看看 SpookyHash 是值得的。 SpookyHash 由 Bob Jenkins 于 2012 年开发,他已于 1997 年在 Dr. Dobbs Journal 上写过关于 Hash 函数的文章。

#174 中的补丁目前正在审查中

此页面是否有帮助?
0 / 5 - 0 等级