【问题标题】:Why does pickle take so much longer than np.save?为什么泡菜比 np.save 花费这么多时间?
【发布时间】:2018-08-14 09:03:27
【问题描述】:

我想保存 dict 或数组。

我同时尝试了np.savepickle,发现前者总是花费更少的时间。

我的实际数据要大得多,但我只是在这里展示一小块用于演示目的:

import numpy as np
#import numpy.array as array
import time
import pickle

b = {0: [np.array([0, 0, 0, 0])], 1: [np.array([1, 0, 0, 0]), np.array([0, 1, 0, 0]), np.array([0, 0, 1, 0]), np.array([0, 0, 0, 1]), np.array([-1,  0,  0,  0]), np.array([ 0, -1,  0,  0]), np.array([ 0,  0, -1,  0]), np.array([ 0,  0,  0, -1])], 2: [np.array([2, 0, 0, 0]), np.array([1, 1, 0, 0]), np.array([1, 0, 1, 0]), np.array([1, 0, 0, 1]), np.array([ 1, -1,  0,  0]), np.array([ 1,  0, -1,  0]), np.array([ 1,  0,  0, -1])], 3: [np.array([1, 0, 0, 0]), np.array([0, 1, 0, 0]), np.array([0, 0, 1, 0]), np.array([0, 0, 0, 1]), np.array([-1,  0,  0,  0]), np.array([ 0, -1,  0,  0]), np.array([ 0,  0, -1,  0]), np.array([ 0,  0,  0, -1])], 4: [np.array([2, 0, 0, 0]), np.array([1, 1, 0, 0]), np.array([1, 0, 1, 0]), np.array([1, 0, 0, 1]), np.array([ 1, -1,  0,  0]), np.array([ 1,  0, -1,  0]), np.array([ 1,  0,  0, -1])], 5: [np.array([0, 0, 0, 0])], 6: [np.array([1, 0, 0, 0]), np.array([0, 1, 0, 0]), np.array([0, 0, 1, 0]), np.array([0, 0, 0, 1]), np.array([-1,  0,  0,  0]), np.array([ 0, -1,  0,  0]), np.array([ 0,  0, -1,  0]), np.array([ 0,  0,  0, -1])], 2: [np.array([2, 0, 0, 0]), np.array([1, 1, 0, 0]), np.array([1, 0, 1, 0]), np.array([1, 0, 0, 1]), np.array([ 1, -1,  0,  0]), np.array([ 1,  0, -1,  0]), np.array([ 1,  0,  0, -1])], 7: [np.array([1, 0, 0, 0]), np.array([0, 1, 0, 0]), np.array([0, 0, 1, 0]), np.array([0, 0, 0, 1]), np.array([-1,  0,  0,  0]), np.array([ 0, -1,  0,  0]), np.array([ 0,  0, -1,  0]), np.array([ 0,  0,  0, -1])], 8: [np.array([2, 0, 0, 0]), np.array([1, 1, 0, 0]), np.array([1, 0, 1, 0]), np.array([1, 0, 0, 1]), np.array([ 1, -1,  0,  0]), np.array([ 1,  0, -1,  0]), np.array([ 1,  0,  0, -1])]}


start_time = time.time()
with open('testpickle', 'wb') as myfile:
    pickle.dump(b, myfile)
print("--- Time to save with pickle: %s milliseconds ---" % (1000*time.time() - 1000*start_time))

start_time = time.time()
np.save('numpy', b)
print("--- Time to save with numpy: %s milliseconds ---" % (1000*time.time() - 1000*start_time))

start_time = time.time()
with open('testpickle', 'rb') as myfile:
    g1 = pickle.load(myfile)
print("--- Time to load with pickle: %s milliseconds ---" % (1000*time.time() - 1000*start_time))

start_time = time.time()
g2 = np.load('numpy.npy')
print("--- Time to load with numpy: %s milliseconds ---" % (1000*time.time() - 1000*start_time))

给出输出:

--- Time to save with pickle: 4.0 milliseconds ---
--- Time to save with numpy: 1.0 milliseconds ---
--- Time to load with pickle: 2.0 milliseconds ---
--- Time to load with numpy: 1.0 milliseconds ---

根据我的实际大小(字典中约 100,000 个键),时差更加明显。

为什么 pickle 的保存和加载时间都比 np.save 长?

什么时候应该使用pickle

【问题讨论】:

    标签: python numpy save pickle


    【解决方案1】:

    因为只要写入的对象不包含 Python 数据,

    • numpy 对象在内存中的表示方式比 Python 对象简单得多
    • numpy.save 是用 C 编写的
    • numpy.save 以超级简单的格式写入,需要最少的处理

    同时

    • Python 对象有很多开销
    • pickle 是用 Python 编写的
    • pickle 将数据从内存中的底层表示转换为写入磁盘上的字节

    请注意,如果一个 numpy 数组确实包含 Python 对象,那么 numpy 只会腌制该数组,所有的胜利都会消失。

    【讨论】:

    • “对象”是指方法、函数等?
    • 我的意思是任何不是 numpy 值的值。触发.dtype.hasobject 变为真的值。例如,np.array([1, "foo"]) 可以,np.array([lambda x: x + 1])np.array([{}]) 不行。
    • 抱歉我的无知,但最后两个例子“不好”到底是什么意思?你说的“python 对象有很多开销”是什么意思?
    • 我的意思是np.array([{}]).dtype.hasobjectTrue,因此np.save 将使用pickle 来表示它而不是它自己的表示,这反过来意味着它实际上稍微比泡菜。
    • 关于开销,Python 是一种动态语言,Python 对象需要有很多 C 没有的额外信息(因为编译器会处理知道所有内容的位置和内容的代码)。例如。对于a = list(range(100)),比较sys.getsizeof(a) + sum(sys.getsizeof(x) for x in a)(内存中Python 列表的大小)与np.array(a).nbytes(相同numpy 数组的大小)。如果您使用np.array(a, dtype=np.uint8).nbytes,则差异会更大。但是将其更改为非 numpy 类型(例如 dicts),numpy 不再能够就地存储它们,并存储指针:例如对于a = [{i: i} for i in range(100)]
    【解决方案2】:

    我认为你需要更好的时机。我也不同意接受的答案。

    b 是一个有 9 个键的字典;这些值是数组列表。这意味着 pickle.dumpnp.save 将相互使用 - pickle 使用 save 腌制数组,save 使用 pickle 保存字典和列表。

    save 写入数组。这意味着它必须将您的字典包装在一个对象 dtype 数组中才能保存它。

    In [6]: np.save('test1',b)
    In [7]: d=np.load('test1.npy')
    In [8]: d
    Out[8]: 
    array({0: [array([0, 0, 0, 0])], 1: [array([1, 0, 0, 0]), array([0, 1, 0, 0]), .... array([ 1, -1,  0,  0]), array([ 1,  0, -1,  0]), array([ 1,  0,  0, -1])]},
          dtype=object)
    In [9]: d.shape
    Out[9]: ()
    In [11]: list(d[()].keys())
    Out[11]: [0, 1, 2, 3, 4, 5, 6, 7, 8]
    

    一些时间安排:

    In [12]: timeit np.save('test1',b)
    850 µs ± 36.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    In [13]: timeit d=np.load('test1.npy')
    566 µs ± 6.44 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    In [20]: %%timeit 
        ...: with open('testpickle', 'wb') as myfile:
        ...:     pickle.dump(b, myfile)
        ...:     
    505 µs ± 9.24 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    In [21]: %%timeit 
        ...: with open('testpickle', 'rb') as myfile:
        ...:     g1 = pickle.load(myfile)
        ...:     
    152 µs ± 4.83 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
    

    在我的时间里,pickle 更快。

    pickle 文件略小:

    In [23]: ll test1.npy testpickle
    -rw-rw-r-- 1 paul 5740 Aug 14 08:40 test1.npy
    -rw-rw-r-- 1 paul 4204 Aug 14 08:43 testpickle
    

    【讨论】:

      【解决方案3】:

      这是因为 pickle 适用于各种 Python 对象,并且是用纯 Python 编写的,而 np.save 是为数组设计的,并以高效的格式保存它们。

      来自numpy.save documentation,它实际上可以在幕后使用pickle。这可能会限制 Python 版本之间的可移植性,并存在执行任意代码的风险(这是解封未知对象时的一般风险)。

      有用的参考:This answer

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-24
        • 2015-09-13
        • 2017-11-15
        • 1970-01-01
        • 2018-06-28
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多