快速方法在numpy数组中对每个元素进行md5加密

5

我正在使用Python 2.7中的NumPy 1D数组,其中包含数千个uint64数字。有什么最快的方法可以单独计算每个数字的md5值?

在调用md5函数之前,每个数字都必须转换为字符串。我在许多地方读到过,对NumPy数组进行迭代并在纯Python中执行操作非常缓慢。有没有办法绕过这个问题?


这种转换有什么意义?MD5字符串有哪些用途是原始float64无法实现的呢? - lenik
我只想尽可能快地将uint64转换为字符串,然后获取它们的MD5。稍后会使用这些MD5字符串。 - Frederico Schardong
我非常确定@lenik是对的,你不需要进行这种转换。在应用MD5之前进行转换似乎是试图优化尚未运行正常的代码。你可以尝试应用lenik的建议吗? - Tim
3个回答

11
你可以为OpenSSL的MD5()函数编写一个接受NumPy数组的包装器。我们的基准将是一个纯Python实现。
创建一个构建器。
# build.py
import cffi

ffi = cffi.FFI()

header = r"""
void md5_array(uint64_t* buffer, int len, unsigned char* out);
"""

source = r"""
#include <stdint.h>
#include <openssl/md5.h>

void md5_array(uint64_t * buffer, int len, unsigned char * out) {
    int i = 0;
    for(i=0; i<len; i++) {
        MD5((const unsigned char *) &buffer[i], 8, out + i*16);
    }
}
"""

ffi.set_source("_md5", source, libraries=['ssl'])
ffi.cdef(header)

if __name__ == "__main__":
    ffi.compile()

和一个包装器
# md5.py
import numpy as np
import _md5

def md5_array(data):
    out = np.zeros(data.shape, dtype='|S16')

    _md5.lib.md5_array(
        _md5.ffi.from_buffer(data),
        data.size,
        _md5.ffi.cast("unsigned char *", _md5.ffi.from_buffer(out))
    )
    return out

并且有一个脚本来比较这两个。
# run.py
import numpy as np
import hashlib
import md5

data = np.arange(16, dtype=np.uint64)
out = [hashlib.md5(i).digest() for i in data]
out2 = md5.md5_array(data)

print(data)
# [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15]
print(out)
# [b'}\xea6+?\xac\x8e\x00\x95jIR\xa3\xd4\xf4t', ... , b'w)\r\xf2^\x84\x11w\xbb\xa1\x94\xc1\x8c8XS']
print(out2)
# [b'}\xea6+?\xac\x8e\x00\x95jIR\xa3\xd4\xf4t', ... , b'w)\r\xf2^\x84\x11w\xbb\xa1\x94\xc1\x8c8XS']

print(all(out == out2))
# True

编译绑定并运行脚本,请运行以下命令:
python build.py
python run.py

对于大型数组来说,速度快了大约15倍(老实说,我有点失望...)
data = np.arange(100000, dtype=np.uint64)

%timeit [hashlib.md5(i).digest() for i in data]
169 ms ± 3.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit md5.md5_array(data)
12.1 ms ± 144 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

如果你想将绑定放在一个库中,并在安装时编译它们,请将以下内容放入你的setup.py文件中:
setup(
    ...,
    setup_requires=["cffi>=1.0.0"],
    cffi_modules=["package/build.py:ffi"],
    install_requires=["cffi>=1.0.0"],
)

AttributeError: 模块“_md5”没有属性“lib”,为什么? - Mowshon
我也遇到了AttributeError: module '_md5' has no attribute 'lib'的问题,而且我正在运行cffi版本0.14.5(如果我没记错的话是在此发布之后),所以我认为这不是一个旧版本的问题...有人成功解决了“没有lib”问题吗? 干杯 :) - Luca Clissa
1
@LucaClissa 你可能忘记构建绑定了。我已经调整了答案,使其更加明确。 - Nils Werner
提前为(可能)愚蠢的回答道歉:我在同一个交互式会话中运行build.py和run.py代码,并且我可以在文件夹中看到三个与*_md5*相关的文件(_md5.c_md5.o_md5.cpython-39-x86_64-linux-gnu.so)。这就是您所说的构建绑定吗? - Luca Clissa
是的。这些文件是在运行 build.py 后由 CFF 输出的。 - Nils Werner
显示剩余3条评论

2

我强烈建议避免将uint64转换为字符串。您可以使用struct获取二进制数据,然后将其馈送到hashlib.md5()中:

>>> import struct, hashlib
>>> a = struct.pack( '<Q', 0x423423423423 )
>>> a
'#4B#4B\x00\x00'
>>> hashlib.md5( a ).hexdigest()
'de0fc624a1b287881eee581ed83500d1'
>>> 

这肯定会加速处理过程,因为没有转换,只是简单的字节复制。

此外,可以使用digest()替换hexdigest(),它返回二进制数据,比将其转换为十六进制字符串更快。根据您之后计划如何使用这些数据,这可能是一个好方法。


这是整个数组的MD5,对吧?OP想要单独计算每个元素的MD5。 - Nils Werner
啊,我明白了。 - Nils Werner

1

注意!抱歉,我错过了问题。下面的代码计算整个数组的MD5,没有任何转换。这放在了错误的位置。

>>> import hashlib
>>> import numpy as np
>>> arr = np.array([1, 2, 3, 4, 5], dtype="uint64")
>>> m = hashlib.md5(arr.astype("uint8"))
>>> m.hexdigest()
'7cfdd07889b3295d6a550914ab35e068'

看起来这个获取整个数组的md5,而不是每个元素的md5。 - thomaskeefe
问题的标题有误导性,我刚刚更新了它以反映真正的问题。然而,这个回答正是我一直在寻找的:对一个numpy数组进行md5加密。 - Adrian W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接