性能说明#
在大多数情况下,最小化内存使用是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]:
[ ]: