• Python 存储大量 NumPy Array 等数据的方案:HDF5

    对于序列化保存各种 array / data frame 等类型的数据,一直以来有各种各样的办法。例如我用过的,对于简单的一个 array,NumPy 有提供读写的方法;pandas 也有对应的 data frame 读写;而字符串/字典,可以变成 json 保存等。

    但是,如果数量多了,例如有 100 个 array,上面的方法就不太方便了。我比较懒,会把这些 array 放到一个 dict 里面,然后用 pickle 把这个 dict pickle下来——保存和读取都非常方便,而且兼容所有数据类型。

    后来,数据量多了之后,就发现 pickle 的方案也是有缺点的,就是性能不好(文末有初步的性能对比)。所以调研了一下后,选择了 HDF5。以前只是听过,没有用过,现在用了感觉不错,在下面稍微总结一下。

    目标用户

    无论是科学研究,还是各行各业,都有 HDF5 的身影。高效、跨平台、无上限,尤其适合数据量大的情景。见官网的 Who Uses HDF?

    安装

    HDF5 支持各种语言,Python 对应的库是 h5py。

    $ pip install h5py
     or
    $ conda install h5py  # Anaconda

    核心概念

    HDF5 里只有 2 种类型:datasetgroup
    – dataset 就像数组,类似 Python 的 list (一维或多维),或 NumPy 的 ndarray。dataset 的语法和 ndarray 类似。
    – group 就像 Python 的 dict,在我看来,它更像是带路径的文件夹。group 的语法和 dict 类似。

    就像是在一层一层的文件夹中,存放着不同的 dataset。记住以上两点,就🆗

    阅读更多…
  • Python 多进程共享内存、NumPy 数组 | Sharing NumPy Array When Using Python Multiprocessing

    背景

    当前的项目需要对大型 numpy 数组进行各种运算(不是深度学习的那种运算),实践发现只开一个 python 进程时,只能使用一个 CPU 核心。所以考虑使用 multiprocessing 模块进行多进程运算。

    但是,问题也很明显:用的是 multiprocessing.pool,如果我的 pool 的 size 是 4,一个 GB 级的 ndarray 传给 pool,会复制 4 份到每一个子进程。这首先会在传输时花时间做相应的 pickle 和 unpickle 操作;更重要的是,这坨数据会在内存里复制 4 份——这直接导致能处理的最大数据大小缩小了四分之三。

    本文使用的 Python 版本为 3.6 / 3.7,Windows 系统。
    在 3.8 版本中,新加入了 multiprocessing.shared_memory 模块,应该能简化这个问题。但是目前为止,项目使用的部分包还不支持 3.8,所以仍需要在旧版本中解决这个问题。

    Value 与 Array

    在 multiprocessing 包中,提供了一些可共享的对象:Value、Array、RawValue 与 RawArray。基本上,前者没有 Raw 的,可以加锁以进行进程间同步,后面 Raw 的没有锁。项目中用到的 numpy 数组都是只读的,子进程只需要读不需要写,所以选择使用 RawArray。

    阅读更多…
  • 用 Numba 加速你的 Python 代码,性能轻松大提升

    Numba 简介

    Numba 是 Python 的一个 JIT (just-in-time) 编译器,最适用于 NumPy 数组、函数,以及 Python 循环。基本上,用法就是给原来的 Python 函数加一个修饰器,当运行到经 Numba 修饰的函数时,它会被编译为机器码,之后再调用时,就能以机器码的速度来执行了。

    按我上手使用的经验来看,Numba 对原代码的改动不是太大,对能加速的部分,加速效果明显;对不支持的加速的 Python 语句/第三方库,可以选择不使用 numba 来规避。这是我选择 Numba 的原因。

    首先:应该编译(优化)什么?

    由于 Numba 本身的限制(稍后介绍),不能做到对整个程序完全的优化。实际上,也没必要这样做——只需要优化真正耗时间的部分即可。

    怎么找到真正耗时间的部分?除了靠直觉,还可以借用工具来分析,例如 Python 自带的 cProfile,还有 line_profiler 等,这里不再细讲。

    安装

    可以通过 conda 或 pip,一个命令安装:
    conda / pip install numba

    什么样的代码能加速?

    按照官方文档的示例代码,如果代码中含有很多数学运算、使用 NumPy,或者有大量 Python 的 for 循环(这可是 Python 性能大忌),那么 Numba 就能给你很好的效果。尤其是多重 for 循环,可以获得极大的加速

    大家都知道,给一个 np.ndarray 加 1 是很快的(向量化、广播),但是如果 for 遍历这个 array 的元素再每个加 1就会很慢(新手容易犯的小错误);但是这都没关系,有了 Numba 再 for 遍历元素加 1,和直接用 ndarray 加 1 的耗时是差不多的!

    再举个例子,下面这段代码,就能享受到 JIT:

    from numba import jit
    import numpy as np
    
    x = np.arange(100).reshape(10, 10)
    
    @jit(nopython=True)  # 设置为"nopython"模式 有更好的性能
    def go_fast(a):  # 第一次调用时会编译
        trace = 0
        for i in range(a.shape[0]):   # Numba likes loops
            trace += np.tanh(a[i, i]) # Numba likes NumPy functions
        return a + trace              # Numba likes NumPy broadcasting
    
    print(go_fast(x))

    但是,类似下面的代码,Numba 就没什么效果:

    from numba import jit
    import pandas as pd
    
    x = {'a': [1, 2, 3], 'b': [20, 30, 40]}
    
    @jit
    def use_pandas(a):  # 这个函数就加速不了
        df = pd.DataFrame.from_dict(a) # Numba 不支持 pd.DataFrame
        df += 1                        # Numba 也不支持这个
        return df.cov()                # 和这个
    
    print(use_pandas(x))

    总之,Numba 应付不了 pandas。以我的经验,需要先把 DataFrame 转成 np.ndarray,再输入给 Numba。

    要强制用 nopython 模式

    刚才有效果的代码中,@jit(nopython=True) 这里传入了 nopython 这个参数,而没什么效果的代码中,就没有这个参数。为什么呢?

    这是因为,@jit 实际上有两种模式,分为别 nopython 和 object 模式。只有 nopython 模式,才是能真正大幅加速的模式。而 nopython 模式只支持部分的 Python 和 NumPy 函数,如果运行时用到了不支持的函数/方法,程序就会崩掉 (例如刚才不能加速的例子如果加上 nopython 就会崩) 。如果不强制设定 nopython 模式,编译函数失败时,会回退到 object 模式,程序虽然不会崩,但却偏离了我们给它加速的本意。

    我既然用了 Numba,我就希望它能真正地发挥作用。所以选择强制开启 nopython ,如果不能加速,不如让它直接崩溃,我们再作对应修改。

    阅读更多…