我试图了解通过使用numba算法的各种实现所看到的性能差异。特别是,我希望func1d从下面开始是最快的实现,因为它是唯一不复制数据的算法,但是从我的时间来看func1b似乎是最快的。
import numpy\nimport numba\n\n\ndef func1a(data, a, b, c):\n # pure numpy\n return a * (1 + numpy.tanh((data / b) - c))\n\n\n@numba.njit(fastmath=True)\ndef func1b(data, a, b, c):\n new_data = a * (1 + numpy.tanh((data / b) - c))\n return new_data\n\n\n@numba.njit(fastmath=True)\ndef func1c(data, a, b, c):\n new_data = numpy.empty(data.shape)\n for i in range(new_data.shape[0]):\n for j in range(new_data.shape[1]):\n new_data[i, j] = a * (1 + numpy.tanh((data[i, j] / b) - c)) \n return new_data\n\n\n@numba.njit(fastmath=True)\ndef func1d(data, a, b, c):\n for i in range(data.shape[0]):\n for j in range(data.shape[1]):\n data[i, j] = a * (1 + numpy.tanh((data[i, j] / b) - c)) \n return data\nRun Code Online (Sandbox Code Playgroud)\n用于测试内存复制的辅助函数
\ndef get_data_base(arr):\n """For a given NumPy array, find the base array\n that owns the actual data.\n \n https://ipython-books.github.io/45-understanding-the-internals-of-numpy-to-avoid-unnecessary-array-copying/\n """\n base = arr\n while isinstance(base.base, numpy.ndarray):\n base = base.base\n return base\n\n\ndef arrays_share_data(x, y):\n return get_data_base(x) is get_data_base(y)\n\n\ndef test_share(func):\n data = data = numpy.random.randn(100, 3)\n print(arrays_share_data(data, func(data, 0.5, 2.5, 2.5)))\nRun Code Online (Sandbox Code Playgroud)\n时间安排
\n# force compiling\ndata = numpy.random.randn(10_000, 300)\n_ = func1a(data, 0.5, 2.5, 2.5)\n_ = func1b(data, 0.5, 2.5, 2.5)\n_ = func1c(data, 0.5, 2.5, 2.5)\n_ = func1d(data, 0.5, 2.5, 2.5)\n\ndata = numpy.random.randn(10_000, 300)\n%timeit func1a(data, 0.5, 2.5, 2.5)\n%timeit func1b(data, 0.5, 2.5, 2.5)\n%timeit func1c(data, 0.5, 2.5, 2.5)\n%timeit func1d(data, 0.5, 2.5, 2.5)\nRun Code Online (Sandbox Code Playgroud)\n67.2 ms \xc2\xb1 230 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n13 ms \xc2\xb1 10.9 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n69.8 ms \xc2\xb1 60.4 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n69.8 ms \xc2\xb1 105 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\nRun Code Online (Sandbox Code Playgroud)\n测试哪些实现复制内存
\n# force compiling\ndata = numpy.random.randn(10_000, 300)\n_ = func1a(data, 0.5, 2.5, 2.5)\n_ = func1b(data, 0.5, 2.5, 2.5)\n_ = func1c(data, 0.5, 2.5, 2.5)\n_ = func1d(data, 0.5, 2.5, 2.5)\n\ndata = numpy.random.randn(10_000, 300)\n%timeit func1a(data, 0.5, 2.5, 2.5)\n%timeit func1b(data, 0.5, 2.5, 2.5)\n%timeit func1c(data, 0.5, 2.5, 2.5)\n%timeit func1d(data, 0.5, 2.5, 2.5)\nRun Code Online (Sandbox Code Playgroud)\nFalse\nFalse\nFalse\nTrue\nRun Code Online (Sandbox Code Playgroud)\n
在这里,数据复制并没有发挥很大的作用:瓶颈在于函数的tanh计算速度。算法有很多种:有些更快,有些更慢,有些更精确,有些不太精确。
不同的 numpy 发行版使用不同的tanh-function 实现,例如,它可以是 mkl/vml 中的一种,也可以是 gnu-math-library 中的一种。
根据 numba 版本,还使用 mkl/svml 实现或 gnu-math-library。
\n查看内部的最简单方法是使用探查器,例如perf。
对于我机器上的 numpy 版本,我得到:
\n>>> perf record python run.py\n>>> perf report\nOverhead Command Shared Object Symbol \n 46,73% python libm-2.23.so [.] __expm1\n 24,24% python libm-2.23.so [.] __tanh\n 4,89% python _multiarray_umath.cpython-37m-x86_64-linux-gnu.so [.] sse2_binary_scalar2_divide_DOUBLE\n 3,59% python [unknown] [k] 0xffffffff8140290c\nRun Code Online (Sandbox Code Playgroud)\n正如我们所看到的,numpy 使用慢速的 gnu-math-library ( libm) 功能。
对于 numba 函数我得到:
\n 53,98% python libsvml.so [.] __svml_tanh4_e9\n 3,60% python [unknown] [k] 0xffffffff81831c57\n 2,79% python python3.7 [.] _PyEval_EvalFrameDefault\nRun Code Online (Sandbox Code Playgroud)\n这意味着使用快速 mkl/svml 功能。
\n这就是(几乎)全部内容。
\n正如 @user2640045 正确指出的那样,由于创建临时数组而导致额外的缓存未命中,numpy 性能将受到损害。
\n然而,缓存未命中并没有像 的计算那样发挥如此大的作用tanh:
%timeit func1a(data, 0.5, 2.5, 2.5) # 91.5 ms \xc2\xb1 2.88 ms per loop \n%timeit numpy.tanh(data) # 76.1 ms \xc2\xb1 539 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\nRun Code Online (Sandbox Code Playgroud)\n即临时对象的创建占据了大约 20% 的运行时间。
\nFWIW,对于带有手写循环的版本,我的 numba 版本(0.50.1)能够矢量化并调用 mkl/svml 功能。如果对于其他版本,这种情况没有发生 - numba 将回退到 gnu-math-library 功能,这似乎是在您的计算机上发生的情况。
\n清单run.py:
>>> perf record python run.py\n>>> perf report\nOverhead Command Shared Object Symbol \n 46,73% python libm-2.23.so [.] __expm1\n 24,24% python libm-2.23.so [.] __tanh\n 4,89% python _multiarray_umath.cpython-37m-x86_64-linux-gnu.so [.] sse2_binary_scalar2_divide_DOUBLE\n 3,59% python [unknown] [k] 0xffffffff8140290c\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
812 次 |
| 最近记录: |