【问题标题】:Performance of Numpy Array vs Python List over 1D matrix(Vector)一维矩阵(向量)上的 Numpy 数组与 Python 列表的性能
【发布时间】:2021-02-22 10:25:19
【问题描述】:

我想计算 pi,它总结了特定类中的几个标签/GaussianMixture 模型中的标签总数。

tr_y 是一个熊猫数据框

index labels
0 6
1 5
2 6
3 5
4 6

1000 行 × 1 列。


然后我尝试比较两种方法:

  • 第一个是使用列表:
%%timeit
y_list = tr_y.values.flatten().tolist()
>>> 12.3 µs ± 193 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
sum([1 if y == 5 else 0 for y in y_list]) / len(y_list)
54.9 µs ± 1.21 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
  • 第二种方法是使用numpy数组:
%%timeit
arr = tr_y.to_numpy()
>>> 4.55 µs ± 92.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
sum([1 for i in arr if i == 5 ])/arr.__len__()
>>> 883 µs ± 48 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

如果使用 tolist() 将 NumPy 转换为列表,则更新的方法比之前的两种方法快得多。

arr = tr_y.to_numpy().tolist()
%%timeit
sum([1 for i in arr if i == 5 ])/arr.__len__()
>>> 43.1 µs ± 410 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

source最后一种方法


所以使用列表比使用 NumPy 数组更快。

我已经搜索过了,我发现 NumPy 必须使用 python 类型(例如,在这种情况下为 NumPy.float64 或 NumPy.int64)包装返回的对象,如果您逐项迭代,这需要时间-项目1。迭代时进一步证明了这一点——我们看到我们在迭代数组时在 2 个单独的 ID 之间交替。这意味着python的内存分配器和垃圾收集器超时工作以创建新对象然后释放它们。

列表没有这种内存分配器/垃圾收集器开销。列表中的对象已经作为 python 对象存在(并且它们在迭代后仍然存在),因此它们在列表的迭代中都没有任何作用。

我的搜索得出的结论是,如果我们需要处理多维矩阵或进行一些向量化,我们必须更快地使用 NumPy 数组并占用更少的内存。这是真的吗?

我要计算的另一件事是 Numpy 数组和列表的内存消耗。尽管如此,我发现sys.sizeOf 并不可靠,它给了我们指针数组和容器头的大小,这是越来越多的考虑因素。有没有可靠的方法来计算内存消耗?

另一项调查是,当我将 NumPy 数组转换为列表时,我将其转换为行矩阵,该行矩阵会立即上传到 L1 缓存中,而不是 col 向量,这会在 L1 缓存中产生很多错误。 source

那么如果我们使用 Fortran 顺序的向量呢?

【问题讨论】:

  • 像这样使用 NumPy 就像你走到商店时用手把车拖在身后 - 你实际上并没有使用你可以使用的工具的力量。
  • @user2357112supportsMonica,你能解释一下这是怎么回事吗?
  • 您在 Python 级别进行迭代,而不是在 NumPy 代码中直接迭代机器整数。
  • 我想如果你为你的 1000 个元素的数组做%timeit np.count_nonzero(arr==5)/arr.size,你会有一个大发现。

标签: python arrays numpy


【解决方案1】:

TL;DR

像这样使用np.sum()

np.sum(tr_y.labels.to_numpy()==5)/len(tr_y)

测试

现在让我们做一些实验。我们将使用以下设置:

import pandas as pd
import numpy as np
tr_y = pd.DataFrame({'labels': [6, 5, 6, 5, 6]*200000})

我们使用更大的数据集来查看这些方法是否能很好地适应更大的输入。在这里,我们将有一个包含 1,000,000 行的数据集。我们将尝试几种不同的方法,看看它们的效果如何。

表现最差的是:

sum(tr_y.labels.to_numpy()==5)/len(tr_y) 

1.91 s ± 42.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

下一个选项平均快 14 倍:

y_list = tr_y.to_numpy().tolist()
sum([1 if y == 5 else 0 for y in y_list]) / len(y_list)

132 ms ± 2.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

之后我们得到了 1.6 倍的增长:

sum(tr_y.labels==5)/len(tr_y)

79.3 ms ± 796 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

但是,这些方法都没有使用 numpy 进行优化。他们使用 numpy 数组,但被 python 的sum() 困住了。如果我们使用优化的NumPy 版本,我们会得到:

np.sum(tr_y.labels.to_numpy()==5)/len(tr_y)

1.36 ms ± 6.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

此操作平均比我们之前的最佳操作快 58 倍。这更像是我们承诺的NumPy 的力量。通过使用 np.sum() 而不是 python 的标准 sum(),我们能够以大约 1,400 倍的速度执行相同的操作(1.9 s vs 1.4 ms)

结语

由于 Pandas 系列是基于 NumPy 数组构建的,因此以下代码的性能与我们的最佳设置非常相似。

np.sum(tr_y.labels==5)/len(tr_y)

1.83 ms ± 39.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

除非优化您的代码是必不可少的,否则我个人会选择此选项,因为它最清晰易读而不会损失太多性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-02
    • 2019-07-30
    • 2014-08-25
    • 2017-04-15
    • 1970-01-01
    相关资源
    最近更新 更多