【问题标题】:Most memory efficient way to store 8M+ sha256 hashes存储 8M+ sha256 哈希的最高效内存方式
【发布时间】:2014-01-29 22:00:41
【问题描述】:

我一直在使用 dict 来存储键值对,其中键和值都是 sha256 哈希摘要。我需要能够找出列表中是否存在键,并且还能够检索该字典的值。

目前,根据我的一些测试,我估计我需要大约 10Gb 的内存来存储 8,000,000 个散列,而实际存储的数据只有大约 512MB(每个散列 32 个字节,因此每条记录 64 个字节)

有人有什么建议吗?

更新,基于我认为应该更新的一些 cmets。我将哈希存储为字节,而不是十六进制字符串。我正在使用 sqlite 数据库来永久存储数据,但是在大约 1,000,000 条记录之后插入带有索引的许多记录变得太慢,并且如果没有索引检查键的存在也会成倍地变慢。这就是为什么我想使用内存结构来进行查找。

更新 2

这行得通吗? atbr hashtable

我的解决方案:(我应该把这个作为答案吗?) 我最终做的是从@abarnert 那里得到很多建议,创建了一个新的类,它实现了 1024 个 [count, bytearray(8000 * 32), bytearray(8000 *32)] 列表

我使用哈希的前 10 位作为我应该存储哈希的列表的索引。然后我只需将键附加到下一个 32 字节插槽,并将值附加到另一个字节数组中的相同插槽。

我可以生成 16,000,000 个散列(一个用于键,一个用于值)并在大约 30 秒内将 8,000,000 个键值对插入到结构中。

搜索正好相反,我使用前 10 位来查找列表,然后我只是对哈希进行线性搜索,直到找到为止。

搜索从 8,000,000 中随机挑选的 200,000 个哈希需要 30 秒,因此比写入慢 40 倍,但它应该足够快以满足我的需要。

这一切的好处是,它现在只消耗 519MB 的 RAM 来处理所有 8,000,000 个哈希。

感谢大家的帮助。

【问题讨论】:

  • 减少内存消耗的一种直接方法是用 C 编写哈希表。您仍然可以在 Python 中使用它。
  • 使用数据库。 sqlite 库随 Python 一起提供。
  • 恐怕我没有那么聪明,这就是我用python编写代码的原因,我要花很长时间才能学会如何做到这一点。但如果我真的绝望了,我可能不得不学习。
  • 我使用的是 sqlite 数据库,但由于我需要不断插入值,我无法拥有索引,因此插入记录的时间越长,其中的值越多。如果没有索引,检查和项目是否存在需要太长时间。
  • 您是将哈希摘要存储为字节还是十六进制字符串?如果是后者……那是你要改变的第一件事。

标签: python dictionary hash


【解决方案1】:

首先,让我们看看为什么它这么大。

每个有 32 个字节。这意味着以二进制形式存储大约需要 32 个字节,例如,bytesbytearray 对象的存储。到目前为止,一切顺利。

但所有 Python 对象都有标头,通常为 24-64 字节。从快速检查来看,至少在我检查的两个 CPython 版本上,bytes 对象在 32 位(可能加上对齐填充)上占用了额外的 36 个字节,在 64 位上占用了 48 个字节。

那么,如何摆脱 150% 的额外存储空间?将字节打包成一个巨大的数组,例如bytesbytearray。然后你有 48 个字节 total 加上每个哈希 32,而不是每个哈希 48+32。当你需要访问一个哈希时,如果你有索引,它只是切片[index*32:(index+1)*32]

另外,根据您创建bytes 的方式,可能会有一些溢出溢出。您可以检查一下——如果是sys.getsizeof(s) - sys.getsizeof(b'') > len(s),您需要对所有对象进行切片以创建没有额外填充的新副本。

无论如何,现在您有 8M 额外的索引。如果这些是暂时的,那很好,但是如果您将 它们 作为 ints 存储在 dict 值槽中,那么它们中的每一个 都有一个标题。通过快速测试,在实际存储的 4 个字节之上(对于 1

您可以使用某种形式的打包存储,例如array 模块。数组类型I 每个整数只使用 4 个字节。但是,您需要对数组进行索引,这……与您刚刚解决的问题相同。

但是你真的甚至不需要索引——如果你将键本身存储在一个数组中,那么任何键的索引已经是字节字符串中哈希的索引(除以 32),对吧?

仅当您可以将键存储在某种紧凑数组中时,这才有效。如果它们的大小都差不多,您可以再次使用相同的“giantbytestring”技巧来做到这一点。在您的情况下,它们是 - 键是 也是 32 字节哈希。因此,您只需将两个巨型字节字符串按键值排序(请参阅bisect 模块,这样您就不必自己编写该代码)。

当然,使用二进制搜索算法而不是散列意味着您正在进行查找和插入对数而不是常数。而且,虽然 log(8M) 仅为 16 左右,比 8M 好很多,但它仍然是 1 的 16 倍。但这实际上是您从经过理想调整的关系数据库中得到的,除了您不需要进行任何调整,它都在内存中,并且没有额外的开销,所以它必须比你迄今为止尝试的有所改进。

您当然可以在 Python 中构建自定义哈希表,使用两个巨型字节数组作为存储,两个 array('I') 作为索引。但这还有很多工作要做,所以我先尝试一下简单的方法。

【讨论】:

  • @ababert 感谢详细的回复,我会研究一下 bisect 看看它是什么样子的。
  • @ababert 从内存方面来看,字节数组将是完美的,但搜索它们非常困难,我查看了 bisect 模块,但无法弄清楚如何将它与字节数组一起使用。使用普通列表似乎很简单,但是使用字节数组,您实际上拥有一个单字节列表。你怎么能在 32 块中搜索?也可以将字节块插入字节数组吗?还是只是单个字节?谢谢
  • @Deano123: 好的,首先你可以通过使用bytearray 来实现ArrayOfByteArray32s 类进行存储,并通过乘以索引或切片来实现__len__/__getitem__/__setitem__ 32 位成员,然后委托给底层bytearray。那么它看起来就像一个bytearrays的序列,所以它可以和bisect一起使用。如果这没有意义,我可以拼凑一个实现。
  • @Deano123:但是……您的使用配置文件是什么样的?如果您执行与查找一样多的插入,则此实现将没有足够的帮助(它使搜索成为对数,但插入是线性的),因此只有在每次插入进行大量查找时才适用。
  • @abarbert 最坏的情况是我会做与查找一样多的插入,我取一个哈希,看看它是否已经存在,如果没有,则将其添加到列表中。首先分配整个数组会有所帮助吗?然后用数据填充它?我想我理解__len____getitem____setitem__,但如果没有帮助就不想这样做。我正在查看 memcache,但真的不想运行额外的服务器以及我的应用程序,宁愿在“内部”完成所有操作。如果在 sqlite 中插入索引表不是那么慢,那将是完美的。还有其他想法吗?感谢您的帮助顺便说一句
【解决方案2】:

如果您不想或不能使用外部数据库,您可以创建一个内存数据库,该数据库更接近内存使用的信息理论最小值,同时速度极快。不过,您需要使用比 Python 对象更低级别的工具。

您可以使用 array.arraybytearray 来存储键和值而无需任何开销,这意味着 8M 条目适合 488 MiB。然后你可以在上面写一个哈希表。不过这很不方便,因此您可能希望使用像 cffi 这样的外部库来处理紧凑的 C 结构和数组。

一个简单的带有线性探测的开放寻址哈希表可以很好地处理您的数据(将密钥的最低 N 位作为哈希)并且实现起来并不难,如果您不需要删除则更容易。只需保持合理的负载系数,在二分之一到三分之二之间。如果你想节省空间(每个空条目浪费半千字节),请将键值对紧紧地打包到数组中,并且只在哈希表中存储一个指针/索引。

【讨论】:

  • 确实需要动态插入。在 Martijn 的回答中查看他的 cmets。
  • @abarnert 注意并调整答案。
  • 只是为了好玩,here 是一个示例实现。它可能有一些错误,在我的测试中它比 dict 慢 3 到 278 倍,但应该很容易理解。
【解决方案3】:

我单独写了一个更复杂的答案,因为我不确定这是否适合你,但是:

dbm 是一个键值对数据库,其工作方式几乎与dict 完全相同(其键和值是bytes 字符串),只是它被备份到磁盘并根据需要将值分页进出。

它比 SQL 数据库简单得多,具有三大优势。

  • 您无需将代码从hash = hashes[thingy]更改为hash = db.execute('SELECT * FROM Hashes WHERE Thingy = ?', (thingy,)).fetchone()[0],您只需继续使用hashes[thingy]即可。

  • 没有要正确的索引 - 键哈希是数据库中一个表的唯一索引 - 没有其他优化可做。

  • 大量的数据库将被缓存在内存中,使其速度更快。


尽管被称为“Unix 数据库”,但每个平台都存在至少一个不同的dbm 系列模块。详情请见dbm: not just for Unix

【讨论】:

  • 谢谢你的建议,我去看看有没有帮助
【解决方案4】:

使用sqlite3 library 将您的哈希值存储在数据库中。 sqlite 嵌入式数据库将使用内存缓冲和磁盘存储为您处理内存管理,尽可能满足您的查询。

一个非常简单的表格就足够了:

import sqlite3

connection = sqlite3.connect('/tmp/hashes.db')
connection.execute('CREATE TABLE hashes (key UNIQUE, value)')

然后使用:

with connection:
    cursor = connection.cursor()
    sql = 'INSERT INTO hashes VALUES (?, ?)'
    cursor.executemany(sql, ((key1, hash1), (key2, hash2), ....))

您可以通过以下方式查询数据库:

with connection:
    cursor = connection.cursor()
    sql = 'SELECT hash FROM hashes WHERE key=?'
    cursor.execute(sql, (key,))
    hash = cursor.fetchone()
    if hash is not None:
        hash = hash[0]

【讨论】:

  • 对不起,我已经更新了问题,我已经在我的应用程序中使用 sqlite 进行永久存储。
  • @Deano123:您是否考虑过暂时禁用索引,然后在插入 之后重新创建它?
  • 是的,我不能这样做,因为我必须不断插入新值,我无法批量添加它们。一旦我的初始过程完成并且所有值都加载到数据库中,为了再次取回数据,我实际上创建了一个索引,以便我可以更快地读回它们。
  • 那么,您插入哈希的方式效率低下;有没有办法为分组插入收集成批的散列?或者您是否需要在生成更多哈希时访问已经生成的哈希?
【解决方案5】:

因为它是一个散列,你可以使用seek()/tell()在一个文件中实现一个字典......你需要预先创建给定大小的文件(10GB或其他)......

那么它就像任何其他哈希表一样。你必须自己处理碰撞,显然它会更慢,因为它在一个文件中......

offset = dict_hash(in_key)
dict_file.seek(offset)
hashval = dict_file.read(hash_len)

类似的东西(以前在 C 中做这样的事情,在 python 中没有做过,但底层文件支持是一样的......)

【讨论】:

  • 当 stdlib 中有模块做同样的事情时,为什么要自己构建它,具有更好的包装器,经过数十年的测试和优化?
  • 好点 - 我看过 dbm 一次或两次,但被文档页面的 unix 方向吓到了。这是一个更好的主意。
【解决方案6】:

您实际上根本不需要存储哈希数据。您需要做的就是正确索引哈希。

这是我的哈希索引函数

首先我们有这个函数来快速索引哈希。

ZequebaHashB[bvc_, yvc_, avc_] :=
 {Drop[Flatten[Reap[
  Module[{a34 = bvc, a35 = yvc, rt2 = avc, z75, zler},
    z75 = 1;
    zler = Total[BCC[Re[Floor[Px[a34, a35^2]]], a35^3]];
    Label[Start5629];
    Sow[Denominator[zler/FiveItt[a35, z75]], yvc];
    z75 = z75 + 1;
    If[z75 >= rt2 + 1, Goto[end5629]];
    Goto[Start5629];
    Label[end5629];
    ];]], 1], Total[BCC[Floor[Re[Px[bvc, yvc^2]]], yvc^3]], bvc};

第二个我们有这个函数来获取哈希的彩虹索引

RainbowHashXG[zf_, xz_, zd_, fd_] := 
Column[{Table[
 Flatten[Drop[ZequebaHashB[zf + xf, xz, zd], -2]], {xf, 0, 
  fd - 1}], zf}];

现在当你尝试这些功能时

Table[ZequebaHashB[Hash[xu, "SHA512"], 2, 5], {xu, 1, 10}]

{{{1, 2, 3, 4, 5}, 427, 

12579926171497332473039920596952835386489858401292624452730263741969\ 1347390182282976402981790496477460666208142347425205936701161323553455\ 43156774710409041}, {{1, 1, 1, 1, 5}, 396, 378544712​​15291391986149267401049113295567628473597440675968265868739\ 3920246834469920751231286910611366704757913119360843344094113813460828\ 6029275267369625}, {{1, 1, 1, 2, 5}, 378, 71668700870008575285238318023246235316098096074289026150051114683524\ 8893999285271969471146596174190457020264703584540790263678736452792747\ 5984118971455163}, {{1, 2, 3, 4, 5}, 377, 33095966240281217830184164668404219514626500609945265788213543056523\ 6612792119604718913684957565086394439681603253709963629672412822522528\ 4694992131191098}, {{1, 2, 1, 4, 5}, 363, 86087420302049294430262146818103852368792727362988712093781053088200\ 5531339261473092981846995901587757487311471069416835834626804973821926\ 684090578667825}, {{1, 1, 3, 2, 5}, 374, 18586086601485268646467765285794047467027639305129763019055665664163\ 2819380637531124748570695025942793945139516664108034654512831533948189\ 743738184270224}, {{1, 1, 3, 1, 1}, 380, 72109882448403363840259529414390721196358024901859951350044294221621\ 3409708767088486766304397692430037767785681544787701437132358156239382\ 5256452011168475}, {{1, 2, 3, 4, 5}, 397, 22760214977694020069971224118591466739483553732805530503408373418535\ 1711847169063849360187954434350675389187296376543635586233555068331343\ 3001046271103001}, {{1, 2, 1, 4, 5}, 369, 11906459655144790308170064541982556680120578173098014909650827827844\ 2313493552143468785692756291539132782149145837942478466345517803751070\ 21641806135272354}, {{1, 1, 3, 2, 5}, 382, 88155955858214177781767282869972903505820511583564376117417944351446\ 8458315518532665921338085983977628624644833036888032312932654944528755\

  1. 5410805140620789}}

    Table[RainbowHashXG[Hash[xu, "SHA512"], 2, 5, 5], {xu, 1, 10}]
    

    {{{{1, 1, 1, 1, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 2, 5}, {1, 2, 1, 4 , 1}, {1, 1, 3, 1, 5}},
    12579926171497332473039920596952835386489858401292624452730263741969\ 1347390182282976402981790496477460666208142347425205936701161323553455\ 43156774710409041}, {{{1, 2, 1, 4, 5}, {1, 1, 3, 2, 5}, {1, 2, 3, 4, 1}, {1, 1, 1, 1, 5}, {1, 2, 3, 4, 5}},
    378544712​​15291391986149267401049113295567628473597440675968265868739\ 3920246834469920751231286910611366704757913119360843344094113813460828\ 6029275267369625}, {{{1, 2, 3, 4, 5}, {1, 1, 1, 1, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 2, 5}, {1, 2, 1, 4, 1}},
    71668700870008575285238318023246235316098096074289026150051114683524\ 8893999285271969471146596174190457020264703584540790263678736452792747\ 5984118971455163}, {{{1, 2, 3, 4, 5}, {1, 1, 1, 1, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 2, 1}, {1, 2, 1, 4, 5}},
    33095966240281217830184164668404219514626500609945265788213543056523\ 6612792119604718913684957565086394439681603253709963629672412822522528\ 4694992131191098}, {{{1, 2, 3, 4, 1}, {1, 1, 3, 1, 5}, {1, 2, 1, 4, 5}, {1, 1, 3, 2, 5}, {1, 1, 3, 1, 5}},
    86087420302049294430262146818103852368792727362988712093781053088200\ 5531339261473092981846995901587757487311471069416835834626804973821926\ 684090578667825}, {{{1, 1, 1, 1, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 2, 5}, {1, 2, 1, 4, 1}, {1, 1, 3, 1, 5}},
    18586086601485268646467765285794047467027639305129763019055665664163\ 2819380637531124748570695025942793945139516664108034654512831533948189\ 743738184270224}, {{{1, 2, 3, 4, 1}, {1, 1, 1, 2, 5}, {1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 2, 5}},
    72109882448403363840259529414390721196358024901859951350044294221621\ 3409708767088486766304397692430037767785681544787701437132358156239382\ 5256452011168475}, {{{1, 1, 3, 1, 5}, {1, 2, 3, 4, 1}, {1, 1, 1, 2, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 1, 5}},
    22760214977694020069971224118591466739483553732805530503408373418535\ 1711847169063849360187954434350675389187296376543635586233555068331343\ 3001046271103001}, {{{1, 1, 1, 2, 5}, {1, 2, 3, 4, 5}, {1, 1, 3, 1, 1}, {1, 2, 1, 4, 5}, {1, 2, 1, 4, 1}},
    11906459655144790308170064541982556680120578173098014909650827827844\ 2313493552143468785692756291539132782149145837942478466345517803751070\ 21641806135272354}, {{{1, 2, 1, 4, 5}, {1, 1, 3, 1, 1}, {1, 2, 3, 4, 5}, {1, 1, 1, 2, 5}, {1, 2, 3, 4, 5}},
    88155955858214177781767282869972903505820511583564376117417944351446\ 8458315518532665921338085983977628624644833036888032312932654944528755\ 5410805140620789}}

    FiveItt[x98_, cc5_] :=
    DifferenceRoot[
    Function[{\[FormalY], \[FormalN]}, {-cc5 -
    cc5 \[FormalY][\[FormalN]] + \[FormalY][1 + \[FormalN]] == 0, \[FormalY][1] == 1, \[FormalY][2] == cc5}]][x98];
    

    BCC[x5​​5_, g77_] := Drop[Flatten[Reap[ Module[ {x45 = x55, z7 = 0, z8 = 0, z9, g7 = g77, 钟},

    z7 = If[x45/FiveItt[Length[IntegerDigits[x45, g7]], g7]

    标签[SPo]; z8 = If[IntegerQ[x45/g7] && x45 > g7, Quotient[x45 - bell - (1/(2*g7)), z9], If[x45

    如果[z7

    标签[EndD];

    ] ]], 1];

    Px = 编译[ {{x1d, _Complex}, {si1d, _Real}} , 模块[{x1c = x1d, si1c = si1d} , x1c + 1/2 (Floor[ Re[(-4 + si1c + Sqrt[(-4 + si1c)^2

    • 8 (-2 + si1c) (-1 + x1d)])/( 2 (-2 + si1c))]] + Floor[Im[(-4 + si1c + Sqrt[(-4 + si1c)^ 2 + 8 (-2 + si1c) (-1 + x1d)])/( 2 (-2 + si1c))]] I) (-4 + si1c - (-2 + si1c) (楼层[ Re[(-4 + si1c + Sqrt[(-4 + si1c)^2 + 8 (-2 + si1c) (-1 + x1c)])/( 2 (-2 + si1c))]] + 楼层[Im[(-4 + si1c + Sqrt[(-4 + si1c)^2 + 8 (-2 + si1c) (-1 + x1c)])/( 2 (-2 + si1c))]] I))] , CompilationTarget -> "C", "RuntimeOptions" -> "Speed"];

【讨论】:

  • 我认为你错过了一些格式......你的答案的结尾根本没有意义。
猜你喜欢
  • 2011-10-14
  • 1970-01-01
  • 1970-01-01
  • 2016-07-26
  • 1970-01-01
  • 2013-09-11
  • 1970-01-01
  • 1970-01-01
  • 2013-06-16
相关资源
最近更新 更多