Logo
Section 1 / 5

简介

在本节课中,我们将学习如何利用 NumPy 最大化运行速度!

检查点

简介

在本节课中,我们将学习如何利用 NumPy 最大化运行速度!

本章讲解 NumPy 数组的基本结构,特别是内存布局、视图(view)、副本(copy)以及数据类型(dtype)。这些概念对于理解如何充分发挥 NumPy 的计算优势至关重要。

我们考虑一个简单的例子:想要清空一个数据类型为 np.float32 的数组中的所有值。如何编写代码才能最大化运行速度?以下写法虽然直观(至少对熟悉 NumPy 的用户而言),但关键在于找出执行最快的方案。

1import numpy as np 2Z = np.ones(4*1000000, np.float32) # 创建大小为 4*1000000 的全1数组 3print(Z) 4Z[...] = 0 # 清空数组,将所有元素设为0 5print(Z) 6print(Z.dtype) # 打印 Z 的数据类型

如果仔细观察数组的 dtype 和总大小,你会发现该数组可以转换(即“视图”)为许多其他“兼容”的数据类型。所谓兼容,是指 Z.size * Z.itemsize 能被新数据类型的 itemsize 整除。

1import numpy as np 2from tools import timeit # 从 tools.py(自定义模块)导入 timeit 3 4Z = np.ones(4*1000000, np.float32) # 创建大小为 4*1000000 的 np.float32 类型数组 5 6print("np.float16:") 7# 将数组视图为 np.float16 并清零所需时间 8timeit("Z.view(np.float16)[...] = 0", globals()) 9 10print("np.int16:") 11# 将数组视图为 np.int16 并清零所需时间 12timeit("Z.view(np.int16)[...] = 0", globals()) 13 14print("np.int32:") 15# 将数组视图为 np.int32 并清零所需时间 16timeit("Z.view(np.int32)[...] = 0", globals()) 17 18print("np.float32:") 19# 将数组视图为 np.float32 并清零所需时间 20timeit("Z.view(np.float32)[...] = 0", globals()) 21 22print("np.int64:") 23# 将数组视图为 np.int64 并清零所需时间 24timeit("Z.view(np.int64)[...] = 0", globals()) 25 26print("np.float64:") 27# 将数组视图为 np.float64 并清零所需时间 28timeit("Z.view(np.float64)[...] = 0", globals()) 29 30print("np.complex128:") 31# 将数组视图为 np.complex128 并清零所需时间 32timeit("Z.view(np.complex128)[...] = 0", globals()) 33 34print("np.int8:") 35# 将数组视图为 np.int8 并清零所需时间 36timeit("Z.view(np.int8)[...] = 0", globals()) 37 38print("np.float16:") 39# 将数组视图为 np.float16 并清零所需时间 40timeit("Z.view(np.float16)[...] = 0", globals()) 41 42print("np.int16:") 43# 将数组视图为 np.int16 并清零所需时间 44timeit("Z.view(np.int16)[...] = 0", globals()) 45 46print("np.int32:") 47# 将数组视图为 np.int32 并清零所需时间 48timeit("Z.view(np.int32)[...] = 0", globals()) 49 50print("np.float32:") 51# 将数组视图为 np.float32 并清零所需时间 52timeit("Z.view(np.float32)[...] = 0", globals()) 53 54print("np.int64:") 55# 将数组视图为 np.int64 并清零所需时间 56timeit("Z.view(np.int64)[...] = 0", globals()) 57 58print("np.float64:") 59# 将数组视图为 np.float64 并清零所需时间 60timeit("Z.view(np.float64)[...] = 0", globals()) 61 62print("np.complex128:") 63# 将数组视图为 np.complex128 并清零所需时间 64timeit("Z.view(np.complex128)[...] = 0", globals()) 65 66print("np.int8:") 67# 将数组视图为 np.int8 并清零所需时间 68timeit("Z.view(np.int8)[...] = 0", globals())

这里使用的 timeit 是一个自定义计时函数。有趣的是,最直接的清空方式并不是最快的。虽然每条指令总共执行了 100 个 CPU 周期,但其中有两条指令每次循环耗时更短。

通过将数组转换为更大的数据类型(如 np.float64),我们获得了 25% 的性能提升;而如果将数组视为字节数组(np.int8),则性能提升了 50%。这种加速的原因在于 NumPy 内部机制以及编译器优化。

这个简单示例体现了 NumPy 的设计哲学。接下来,我们将进入下一课,学习内存布局的相关知识。

Quiz

单选题

如何提高清空数组数据(将数组中的所有值设为0)的速度?

1Z = np.ones(4*1000000, np.float32)
1 / 1

简介

内存布局