性能说明#

在大多数情况下,最小化内存使用是Vaex的首要任务,性能次之。这使得Vaex能够处理非常大的数据集,而不会自找麻烦。

然而,这有时会以性能为代价。

虚拟列#

当我们基于现有的数据框添加一个新列时,Vaex 将创建一个虚拟列,例如:

[18]:
import vaex
import numpy as np
x = np.arange(100_000_000, dtype='float64')
df = vaex.from_arrays(x=x)
df['y'] = (df['x'] + 1).log() - np.abs(df['x']**2 + 1).log()

在这个数据框中,x 使用内存,而 y 不使用内存,它将在需要时分块评估。为了展示性能影响,让我们对该列进行计算,以强制评估。

[21]:
%%time
df.x.mean()
CPU times: user 2.74 s, sys: 12.3 ms, total: 2.75 s
Wall time: 71.2 ms
[21]:
array(49999999.5)
[22]:
%%time
df.y.mean()
CPU times: user 3.88 s, sys: 635 ms, total: 4.52 s
Wall time: 304 ms
[22]:
array(-17.42068049)

由此可以看出,使用虚拟列进行类似的计算(平均值)可能会慢得多,这是我们为节省内存所付出的代价。

物化列#

我们可以要求Vaex具体化一个列,或者使用df.materialize具体化所有虚拟列

[23]:
df_mat = df.materialize()
[24]:
%%time
df_mat.x.mean()
CPU times: user 2.54 s, sys: 14 ms, total: 2.56 s
Wall time: 68.1 ms
[24]:
array(49999999.5)
[25]:
%%time
df_mat.y.mean()
CPU times: user 2.64 s, sys: 18.7 ms, total: 2.66 s
Wall time: 68.1 ms
[25]:
array(-17.42068049)

我们现在为两列获得了相同的性能

多工作线程后端的注意事项#

与Python中的Web框架常见的情况一样,我们使用多个工作进程,例如使用gunicorn。如果所有工作进程都实例化,将会浪费大量内存,针对这个问题有两种解决方案:

保存到磁盘#

将数据框以hdf5或arrow格式导出到磁盘作为预处理步骤,并让所有工作进程访问相同的文件。由于内存映射,每个工作进程将共享相同的内存。

例如。

df.export('materialized-data.hdf5', progress=True)

实现单一时间#

Gunicorn 有以下命令行标志:

--preload             Load application code before the worker processes are forked. [False]

这将让gunicorn首先运行你的应用(仅一次),允许你执行物化步骤。在你的脚本运行后,它将进行分叉,所有工作进程将共享相同的内存。

提示:#

一个好的想法可能是将两者混合使用,并使用 Vaex 的 df.fingerprint 方法将文件缓存到磁盘。

例如。

import vaex
import numpy as np
import os

x = np.arange(100_000_000, dtype='float64')
df = vaex.from_arrays(x=x)
df['y'] = (df['x'] + 1).log() - np.abs(df['x']**2 + 1).log()

filename = "vaex-cache-" + df.fingerprint() + ".hdf5"
if not os.path.exists(filename):
    df.export(filename, progress=True)
else:
    df = vaex.open(filename)

如果虚拟列发生变化,重新运行将创建一个新的缓存文件,而改回将使用之前生成的缓存文件。这在开发过程中特别有用。

在这种情况下,仍然重要的是让gunicorn首先运行一个单独的进程(使用--preload标志),以避免多个工作进程执行相同的工作。

[28]:

[ ]: