【问题标题】:Most efficient way to store list of integers存储整数列表的最有效方法
【发布时间】:2017-01-28 22:12:44
【问题描述】:

我最近一直在做一个项目,其中一个目标是使用 Python 3 使用尽可能少的内存来存储一系列文件。除了一个列表之外,几乎所有文件都占用很少的空间整数大约为 333,000 整数长,并且整数大小最大为 8000

我目前正在使用pickle 来存储列表,它占用了大约7mb,但我觉得必须有一种内存效率更高的方法来做到这一点。

我已尝试将其存储为文本文件和csv,但两者都使用了超过10mb 的空间。

【问题讨论】:

  • 您可能想了解 Pandas 和 HDF5 格式(+ blosc 压缩)
  • 你的最大整数需要多少字节?
  • integers up to about 8000 in size 什么意思?
  • 你的意思是max(of_all_integers) <= 8000
  • pickle/text/csv 文件压缩后有多大(zip、gz、lzma 或 bzip2,Python 标准库都支持)?

标签: python list python-3.x memory integer


【解决方案1】:

您可以使用的一个stdlib 解决方案是来自array 的数组,来自文档:

该模块定义了一个对象类型,它可以紧凑地表示一组基本值:字符、整数、浮点数。数组是序列类型,其行为与列表非常相似,只是其中存储的对象类型受到限制。

这通常会减少大列表的一些内存,例如,一个列表有 1000 万个元素,数组会修剪11mb

import pickle    
from array import array

l = [i for i in range(10000000)]
a = array('i', l)

# tofile can also be used.
with open('arrfile', 'wb') as f:  
    pickle.dump(a, f)

with open('lstfile', 'wb') as f:
    pickle.dump(l, f)

尺寸:

!du -sh ./*
39M     arrfile
48M     lstfile

【讨论】:

  • 这对于 OP 的问题可能是一个不错的解决方案。然而,重要的是要知道,该数组将使用平台的本机 C-int 类型存储值,而不是 Python 的任意精度整数。
  • array('i', [10**7999]) 崩溃。
  • 当然可以@StefanPochmann,array('i', [2**31-1]) 上的任何内容都不允许使用'i' :-)。如果 OP 明确指出 size8000 实际上意味着 8000 数字(我对此表示严重怀疑),我将确保注意有多大的 int 受到底层 C 类型的限制。
  • 就像我在别处说的:将超过 10mb 的文本文件除以 333000。每个数字超过 30 个字节。在文本文件中每个四位数字占用超过 30 个字节,他们会多么愚蠢?看起来很确定它们意味着最多 8000 位数字(或者可能是位)。
【解决方案2】:

这是一个使用 Pandas 模块的小演示:

import numpy as np
import pandas as pd
import feather

# let's generate an array of 1M int64 elements...
df = pd.DataFrame({'num_col':np.random.randint(0, 10**9, 10**6)}, dtype=np.int64)
df.info()

%timeit -n 1 -r 1 df.to_pickle('d:/temp/a.pickle')

%timeit -n 1 -r 1 df.to_hdf('d:/temp/a.h5', 'df_key', complib='blosc', complevel=5)
%timeit -n 1 -r 1 df.to_hdf('d:/temp/a_blosc.h5', 'df_key', complib='blosc', complevel=5)
%timeit -n 1 -r 1 df.to_hdf('d:/temp/a_zlib.h5', 'df_key', complib='zlib', complevel=5)
%timeit -n 1 -r 1 df.to_hdf('d:/temp/a_bzip2.h5', 'df_key', complib='bzip2', complevel=5)
%timeit -n 1 -r 1 df.to_hdf('d:/temp/a_lzo.h5', 'df_key', complib='lzo', complevel=5)

%timeit -n 1 -r 1 feather.write_dataframe(df, 'd:/temp/a.feather')

数据帧信息:

In [56]: df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 1 columns):
num_col    1000000 non-null int64
dtypes: int64(1)
memory usage: 7.6 MB

结果(速度):

In [49]: %timeit -n 1 -r 1 df.to_pickle('d:/temp/a.pickle')
1 loop, best of 1: 16.2 ms per loop

In [50]: %timeit -n 1 -r 1 df.to_hdf('d:/temp/a.h5', 'df_key', complib='blosc', complevel=5)
1 loop, best of 1: 39.7 ms per loop

In [51]: %timeit -n 1 -r 1 df.to_hdf('d:/temp/a_blosc.h5', 'df_key', complib='blosc', complevel=5)
1 loop, best of 1: 40.6 ms per loop

In [52]: %timeit -n 1 -r 1 df.to_hdf('d:/temp/a_zlib.h5', 'df_key', complib='zlib', complevel=5)
1 loop, best of 1: 213 ms per loop

In [53]: %timeit -n 1 -r 1 df.to_hdf('d:/temp/a_bzip2.h5', 'df_key', complib='bzip2', complevel=5)
1 loop, best of 1: 1.09 s per loop

In [54]: %timeit -n 1 -r 1 df.to_hdf('d:/temp/a_lzo.h5', 'df_key', complib='lzo', complevel=5)
1 loop, best of 1: 32.1 ms per loop

In [55]: %timeit -n 1 -r 1 feather.write_dataframe(df, 'd:/temp/a.feather')
1 loop, best of 1: 3.49 ms per loop

结果(大小):

{ temp }  » ls -lh a*                                                                                         /d/temp
-rw-r--r-- 1 Max None 7.7M Sep 20 23:15 a.feather
-rw-r--r-- 1 Max None 4.1M Sep 20 23:15 a.h5
-rw-r--r-- 1 Max None 7.7M Sep 20 23:15 a.pickle
-rw-r--r-- 1 Max None 4.1M Sep 20 23:15 a_blosc.h5
-rw-r--r-- 1 Max None 4.0M Sep 20 23:15 a_bzip2.h5
-rw-r--r-- 1 Max None 4.1M Sep 20 23:15 a_lzo.h5
-rw-r--r-- 1 Max None 3.9M Sep 20 23:15 a_zlib.h5

结论:如果您需要速度和合理的大小,请注意 HDF5(+ blosclzo 压缩),如果您只关心速度,请注意 Feather-format - 它是 4比 Pickle 快几倍!

【讨论】:

    【解决方案3】:

    我喜欢Jim's suggestion 使用array 模块。如果您的数值足够小以适合机器的本机 int 类型,那么这是一个很好的解决方案。 (不过,我更喜欢使用 array.tofile 方法序列化数组,而不是使用 pickle。)如果 int 是 32 位,那么每个数字使用 4 个字节。

    不过,我想问一下您是如何制作文本文件的。如果我创建一个文件,其中包含 [0, 8000] 范围内的 333000 个整数,每行一个数字,

    import random
    
    with open('numbers.txt', 'w') as ostr:
        for i in range(333000):
            r = random.randint(0, 8000)
            print(r, file=ostr)
    

    它的大小只有 1.6MiB,与二进制表示将使用的 1.3MiB 相比,这还不算太糟糕。而且如果有一天你碰巧有一个超出原生int类型范围的值,文本文件会愉快地处理它而不会溢出。

    此外,如果我使用 gzip 压缩文件,文件大小会缩小到 686KiB。这比压缩二进制数据要好!使用 bzip2 时,文件大小仅为 562KiB。 Python 的标准库同时支持gzipbz2,因此您可能想再试一次纯文本格式和压缩。

    【讨论】:

    • 您的号码最多只能有 4 位,而不是最多 8000。
    • @StefanPochmann 这就是我对问题的解释; 8000 也有 4 位数字。
    • 来吧。将超过 10mb 的文本文件除以 333000。每个数字超过 30 个字节。在文本文件中每个四位数字占用超过 30 个字节,他们会多么愚蠢?
    • 你说 “这比压缩二进制数据好” 但我在任何地方都看不到它的大小。这是什么?
    • 嗯,奇怪,我一直得到 708-709 KiB。当我使用 array('h', ...) 时,顺便说一句 589-590 KiB。
    猜你喜欢
    • 2014-01-29
    • 1970-01-01
    • 1970-01-01
    • 2012-12-22
    • 2017-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-06
    相关资源
    最近更新 更多