在字符串上生成唯一的整数/长哈希键,以便更快地进行比较

Posted

技术标签:

【中文标题】在字符串上生成唯一的整数/长哈希键,以便更快地进行比较【英文标题】:unique integer/long hash key generation over strings for faster compairson 【发布时间】:2010-11-07 16:43:03 【问题描述】:

我很好奇其他人是如何解决这个问题的,以及天真的解决方案背后可能潜伏着什么问题:

我有一个处理股市数据的系统。有数以万计的符号,以及相关的价格/大小,以每毫秒几千的速度流入系统。

每个tick都需要进行的基本操作之一是字符串比较,以查看输入是否与我们感兴趣的符号匹配。在如此高的频率下,这些字符串比较的优化可以在性能上产生可衡量的差异整个系统。

我正在考虑生成符号字符串的散列,并将其与记录一起存储。对于后续的比较,系统应该使用这个哈希(作为一个int或long,比较应该是一个单一的操作,而不是遍历字符串的每个字符直到发现不匹配)。

让我们忽略生成散列本身的成本(实际上,这实际上可能令人望而却步)。我能看到的唯一问题是,对于大量唯一符号,哈希冲突(两个单独的符号生成相同的哈希)将是毁灭性的。是否有一种哈希算法可以保证匹配某些约束(例如字符数限制)的字符串是唯一的?

编辑:我将用 Java 编写这段代码。不确定 hashCode 的(碰撞)质量或计算速度。

【问题讨论】:

您是否考虑过使用以下一种或多种通用哈希函数:hashhttp://www.partow.net/programming/hashfunctions/index.html 想点击链接的朋友partow.net/programming/hashfunctions/index.html 【参考方案1】:

也许哈希函数在这里并不是最好的方法。如果您收到一个股票代码(而不是股票代码的散列),您将不得不在它每次通过时计算它的散列。如果它是一个没有冲突的散列算法,那么无论如何您都需要查看符号的每个字符。所以你不妨直接比较字符。

我建议为您感兴趣的所有代码构建一个 Trie 数据结构。(请参阅http://en.wikipedia.org/wiki/Trie)。遍历每个符号的树,如果您到达代码的末尾但没有找到匹配项,那么它就不是一个有趣的代码。

使用散列,无论如何您都必须在有趣代码的所有散列值集合中执行此遍历。

【讨论】:

关于首先计算哈希的成本的好点。尽管我决定在这个问题上忽略它,但这是一个真正的问题……但我可以通过运行测试来回答。我确实希望将每个传入的刻度存储到由符号键入的 Map 中(因此最新数据将覆盖旧数据)。在我的程序的其他地方,地图将用于在新的报价出现时经常查找。因为每次出价或报价出现时,都需要将其与最后的销售价格相结合以创建一个汇总的报价。这就是为什么预先计算哈希值可能是值得的。 与重新考虑哈希码解决方案的思路相同,另一种方法是每次有新符号进入时增加一个原子长并将其放入映射中。显然在增加计数器之前检查地图。现在我不知道它的 cpu 周期成本是多少,但至少我可以测试它。更简单的解决方案,让我不必担心哈希码冲突。无论哪种方式,这种优化都将隐藏在公共 API 之外【参考方案2】:

常见的加密散列函数(如 SHA-1)输出 20 个字节(160 位)。你的股票代码有多长?如果我们谈论的是ticker symbols,比如“WMT”(沃尔玛)、“KO”(可口可乐)等,那么它们似乎只有几个字节长——因此直接比较它们应该更快而不是处理 20 字节的哈希。你提到了哈希冲突——我不会担心它们,尤其是当输入远小于哈希输出时。

您可以根据编程语言和平台将字节转换为intlong,然后在一条 CPU 指令中比较这些“数字”。 (我不知道现代编译器是否可以通过调用memcmp 来同样快速地比较一堆字节?)

【讨论】:

第二。不确定它在 Java 中是否有意义,因为需要进行所有移位和或运算,但是您可以将大量信息打包成 64 位长,在现代硬件上,实际比较应该只需要一两个周期。不要忘记 Java 字符串是 Unicode,所以你可能想先去掉高位字节。【参考方案3】:

您应该考虑使用Perfect hash function,我认为它符合您的要求

【讨论】:

【参考方案4】:

如果您使用 String.intern() 或您自己的字符串池,则可以使用 == 而不是 .equals() - 我已经在类似的性能关键代码中完成了此操作,它产生了很大的不同。默认 String 已经有一个 hashCode() 可以相当有效地工作。

我刚刚意识到这不是 Java 问题,但同样适用。是的,散列然后使用身份检查可以节省时间。 java哈希算法使用:

s[0] * 31^(n-1) + s[1] * 31^(n-2) + ... + s[n-1]

【讨论】:

不是 Java 问题,但我的代码将使用 Java :) 我应该提到 Java,它确实包含一个 hashCode 函数。【参考方案5】:

如果您收到 4 个字母的股票代码,那么每个字母都应该可以表示为一个字节。将所有 4 个一起打包成一个 32 位的 int,瞧,你有你的“哈希”。您现在可以使用单个机器指令将其与参考进行比较。

如果您没有使用 Java,那就是。

我真的不建议将 Java 用于任何对速度要求很高的事情,尤其是不建议每毫秒进行数千次字符串比较。

编辑:如果您想使用 64 位代码,您可以在每个 long int 中打包最多 8 个字母,然后在 1 条指令中进行比较。

【讨论】:

+1。但我怀疑股票代码符号是否需要 64 位代码——每个字母可以用 5 位表示,这意味着 6 个字母可以轻松地放在 32 位单词中。像这样的打包速度很快——只是每个字符的减法和位移。【参考方案6】:

您可以通过将字符串视为 Base-27 数字来生成哈希(假设符号仅包含字母)。这将产生您正在寻找的独特性。例如:

(无字母) = 0, A = 1, B = 2, ... Z = 26

AA = (1 x 271) + (1 x 270) = 28

AAA = (1 x 272) + (1 x 271) + (1 x 270) = 757

BBB = (2 x 272) + (2 x 271) + (2 x 270) = 1514

GOOG = (7 x 273) + (15 x 272) + (15 x 271) + (7 x 270) = 149128

这在 32 位 int 中最多可以工作 6 个字符。

【讨论】:

【参考方案7】:

你想要的是一个具有良好辨别能力的快速哈希函数。 对于每个字符串,计算相关的散列函数并将其与字符串一起存储。 然后进行比较,代码: 如果(哈希(s1)==哈希(s2) && s1==s2) 然后 ... 除非哈希匹配,否则不会发生实际的字符串比较,这在实践中 只有当字符串匹配时。

有些人会告诉你实现一个完美的哈希。你只能做 当您要散列的字符串集具有有限大小时,通常 只有10-1000。对于任意大的字符串词汇表,您无法做到这一点。 既然你不能这样做,你实际上必须比较字符串来确定相等性。

加密哈希具有强大的识别能力,但并非经过设计 要快。什么通常非常快并且具有良好的辨别力 强大的是 CRC 函数,并且大多数语言都可以轻松找到库 快速计算这些(使用字节表查找技术)。 我们使用 CRC-32,它对此非常有效(基本上 2^32 中有 1 次机会发生哈希冲突,当字符串 不匹配)。你可以使用 CRC-64,但是额外的辨别能力 它提供的并不会真正添加任何真正的功能。

【讨论】:

【参考方案8】:

任何体面的哈希函数都能很好地处理冲突。基本上,如果哈希导致存在多个答案的命中,则该存储桶中有一个潜在解决方案的链接列表,并且必然会减慢找到正确答案(如果存在)的速度。

但不要编写自己的哈希函数,使用现有的。

哦,我认为应该只生成一次哈希。因为您有一个正在跟踪的事物的查找表,而哈希表只需要在您添加新的“有趣”事物进行扫描时才需要更改。

【讨论】:

【参考方案9】:

编辑:比我自己的更好的 cmets 被抛出(和更早),让我的充其量是多余的。

【讨论】:

【参考方案10】:

我赞同上述 Trie 结构的建议,作为这种情况下的最佳方法。计算上相当于一个完美的哈希,但在概念上更漂亮。这是假设您的符号在长度上是有界的。

【讨论】:

【参考方案11】:

FWIW,在我参与的最后一个大数据量项目中,我们发现使用一些经过大量调整的 C 代码过滤、聚合和预分类数据是关键。我们所有的提要都进入了这个预处理器,它负责简单的数据清理,然后将大量数据传递给我们基于 Java 的系统进行处理。基本上,预处理器会按照您的要求完成:识别感兴趣的记录,验证它们是否完整并删除重复和空文件。在高峰时段,预处理器可以消除我们每小时获得的 8M 左右记录中的 20%(可能不是我想象的从股票市场信息中获得的数量)。我们最初的 Java 版本很幸运地得到了一半(但它至少是“优雅的”!)

【讨论】:

【参考方案12】:

为了它的价值。我解决了特定于 CMS (NYSE) 和 CQS (NASDAQ) 符号系统的这个问题。符号根的长度最多为 6 个字符,并且是大写的。我的要求如下:

未知符号的数据会到达 在接收到数据后计算一个哈希值用于比较 计算一次值,将值存储在映射中以供将来比较 值比较将是相等的 值比较将针对一个范围。

例如,如果 GOOG 的数据到达,则需要对其进行处理并分发到符号范围 [F-HAA] 中的进程。 (F

unsigned long long SymbolToVal(std::string& str)

 size_t maxlen = 6; // Symbology constraint
 if (str.length() != maxlen) return 0;
 unsigned long long val;
 unsigned long long retval=0;
 int expon = maxlen*2; // ASCII val range (65-90)
 double factor = std::pow(10.0,expon);
 expon-=2;
 for (size_t i = 0; i < maxlen; i++)
 
    val = (unsigned long long)factor * str[i];
    retval += val;
    factor = (unsigned long long) std::pow(10.0,expon);
    expon-=2;
  
  return retval;
 

一种蛮力方法是计算所有可能的符号,对它们进行正确排序,并为它们分配一个整数,然后将它们存储在地图中。如果传入的数据仅包含整个域的一小部分(这是正常情况),则可能是矫枉过正。

【讨论】:

以上是关于在字符串上生成唯一的整数/长哈希键,以便更快地进行比较的主要内容,如果未能解决你的问题,请参考以下文章

如何将UUID设置成系统唯一标识

为银行交易生成唯一密钥

如何在 Mac 上生成 Facebook 发布密钥哈希?

使用多个 uint32_t 整数生成 uint64_t 哈希键

markdown [php:hash]在PHP上生成哈希。 #php #security

使用键值数据库作为具有持久索引的集合