TLDR;的python中提供的各种压缩算法 gzip,bz2,lzma,等,具有最佳的减压性能?

完整讨论:

蟒3具有用于压缩/解压缩的数据的各种模块,包括gzip,bz2和lzma。gzip并bz2可以设置不同的压缩级别。

如果我的目标是平衡文件大小(/压缩比)和解压缩速度(与压缩速度无关),哪个将是最佳选择?解压缩速度比文件大小更重要,但是由于有问题的未压缩文件每个约为600-800MB(32位RGB .png图像文件),我有十几个,因此我确实希望进行一些压缩。

我的用例是,我要从磁盘加载一堆图像,对其进行一些处理(作为numpy数组),然后在程序中使用经过处理的数组数据。

映像永远不会改变,我只需要在每次运行程序时加载它们。

处理所需的时间与加载时间相同(几秒钟),因此我试图通过保存处理后的数据(使用pickle)来节省一些加载时间,而不是每次都加载未经处理的原始图像。最初的测试很有希望-加载原始/未压缩的腌制数据花了不到一秒钟,而加载和处理原始图像只花了3到4秒钟-但如上所述,文件大小约为600-800MB,而原始png图像只有5MB左右。因此,我希望可以通过以压缩格式存储选择的数据来在加载时间和文件大小之间取得平衡。

更新:这种情况实际上比我上面表示的要复杂。我的应用程序使用PySide2,因此我可以访问这些Qt库。

如果我读取图像并使用pillow(PIL.Image)转换为numpy数组,则实际上无需进行任何处理,但是将图像读取到数组中的总时间约为4秒。

如果取而代之的是我QImage用来读取图像,则必须对结果进行一些处理,以使其可用于程序的其余部分,这是由于QImage数据加载方式的字节顺序所致-基本上,我必须交换位顺序并然后旋转每个“像素”,以使alpha通道(显然是QImage添加的)排在最后而不是排在第一位。这整个过程只需约3.8秒,所以稍微比只使用PIL更快。

如果我保存numpy未压缩的数组,则可以在.8秒内将它们重新加载,这是迄今为止最快的,但是文件很大。

┌────────────┬────────────────────────┬───────────────┬─────────────┐
│ Python Ver │ Library/Method │ Read/unpack + │ Compression │
│ │ │ Decompress (s)│ Ratio │
├────────────┼────────────────────────┼───────────────┼─────────────┤
│ 3.7.2 │ pillow (PIL.Image) │ 4.0 │ ~0.006 │
│ 3.7.2 │ Qt (QImage) │ 3.8 │ ~0.006 │
│ 3.7.2 │ numpy (uncompressed) │ 0.8 │ 1.0 │
│ 3.7.2 │ gzip (compresslevel=9) │ ? │ ? │
│ 3.7.2 │ gzip (compresslevel=?) │ ? │ ? │
│ 3.7.2 │ bz2 (compresslevel=9) │ ? │ ? │
│ 3.7.2 │ bz2 (compresslevel=?) │ ? │ ? │
│ 3.7.2 │ lzma │ ? │ ? │
├────────────┼────────────────────────┼───────────────┼─────────────┤
│ 3.7.3 │ ? │ ? │ ? │
├────────────┼────────────────────────┼───────────────┼─────────────┤
│ 3.8beta1 │ ? │ ? │ ? │
├────────────┼────────────────────────┼───────────────┼─────────────┤
│ 3.8.0final │ ? │ ? │ ? │
├────────────┼────────────────────────┼───────────────┼─────────────┤
│ 3.5.7 │ ? │ ? │ ? │
├────────────┼────────────────────────┼───────────────┼─────────────┤
│ 3.6.10 │ ? │ ? │ ? │
└────────────┴────────────────────────┴───────────────┴─────────────┘

png / PIL情况的代码(加载到numpy数组中):

fromPILimportImageimporttimeimportnumpy
start=time.time()FILE='/path/to/file/AlaskaCoast.png'Image.MAX_IMAGE_PIXELS=Noneimg=Image.open(FILE)arr=numpy.array(img)print("Loaded in",time.time()-start)

在使用Python 3.7.2的计算机上,此负载大约需要4.2 s。

或者,我可以加载通过选择上面创建的数组而生成的未压缩的pickle文件。

未压缩的泡菜装载工况的代码:

importpickleimporttime
start=time.time()withopen('/tmp/test_file.pickle','rb')aspicklefile:arr=pickle.load(picklefile)print("Loaded in",time.time()-start)

从此未压缩的泡菜文件加载到我的机器上需要约0.8s。

解决方案

您可以使用Python-blosc

它非常快,对于小型阵列(<2GB)也非常易于使用。对于像您的示例这样的易于压缩的数据,通常可以更快地压缩数据以进行IO操作。(SATA-SSD:大约500 MB / s,PCIe-SSD:最高3500MB / s)在解压缩步骤中,阵列分配是最昂贵的部分。如果图像的形状相似,则可以避免重复分配内存。

对于以下示例,假定使用连续数组。

importbloscimportpickledefcompress(arr,Path):#c = blosc.compress_ptr(arr.__array_interface__['data'][0], arr.size, arr.dtype.itemsize, clevel=3,cname='lz4',shuffle=blosc.SHUFFLE)c=blosc.compress_ptr(arr.__array_interface__['data'][0],arr.size,arr.dtype.itemsize,clevel=3,cname='zstd',shuffle=blosc.SHUFFLE)f=open(Path,"wb")pickle.dump((arr.shape,arr.dtype),f)f.write(c)f.close()returnc,arr.shape,arr.dtypedefdecompress(Path):f=open(Path,"rb")shape,dtype=pickle.load(f)c=f.read()#array allocation takes most of the timearr=np.empty(shape,dtype)blosc.decompress_ptr(c,arr.__array_interface__['data'][0])returnarr#Pass a preallocated array if you have many similar imagesdefdecompress_pre(Path,arr):f=open(Path,"rb")shape,dtype=pickle.load(f)c=f.read()#array allocation takes most of the timeblosc.decompress_ptr(c,arr.__array_interface__['data'][0])returnarr

基准测试

#blosc.SHUFFLE, cname='zstd' -> 4728KB,%timeit compress(arr,"Test.dat")1.03s±12.5ms per loop(mean±std.dev.of7runs,1loop each)#611 MB/s%timeit decompress("Test.dat")146ms±481µs per loop(mean±std.dev.of7runs,10loops each)#4310 MB/s%timeit decompress_pre("Test.dat",arr)50.9ms±438µs per loop(mean±std.dev.of7runs,10loops each)#12362 MB/s#blosc.SHUFFLE, cname='lz4' -> 9118KB,%timeit compress(arr,"Test.dat")32.1ms±437µs per loop(mean±std.dev.of7runs,10loops each)#19602 MB/s%timeit decompress("Test.dat")146ms±332µs per loop(mean±std.dev.of7runs,10loops each)#4310 MB/s%timeit decompress_pre("Test.dat",arr)53.6ms±82.9µs per loop(mean±std.dev.of7runs,10loops each)#11740 MB/s

时机