# 性能测量和改进技术

## 目标

在图像处理中，由于我们每秒需要处理大量操作，因此我们的代码不仅要提供正确的解决方案，还要以最快的方式提供。所以，在本章中，我们将学习
* 衡量代码的性能
* 一些提高代码性能的技巧
* 我们将看到以下函数：[cv.getTickCount](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#gae73f58000611a1af25dd36d496bf4487),[cv.getTickFrequency](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#ga705441a9ef01f47acdc55d87fbe5090c)等。

除了 OpenCV 之外，Python 还提供了一个**时间**模块，有助于记录代码执行时间。另一个模块**配置文件**有助于获取有关代码的详细报告，例如代码中每个函数花费的时间，调用函数的次数等。但是，如果您使用的是 IPython，所有这些功能都集成在一个用户友好的方式中。我们将看到一些重要的内容，有关更多详细信息，请查看**其他资源**部分中的链接。

## 使用 OpenCV 测量性能

[cv.getTickCount](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#gae73f58000611a1af25dd36d496bf4487)函数返回参考事件（如机器开启时刻）到调用此函数的时钟周期数。因此，如果在函数执行之前和之后调用它，则会获得用于执行函数的时钟周期数。

[cv.getTickFrequency](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#ga705441a9ef01f47acdc55d87fbe5090c)函数返回时钟周期的频率，或每秒钟的时钟周期数。因此，要在几秒钟内找到执行时间，您可以执行以下操作

```python
e1 = cv.getTickCount()
#你的执行的代码
e2 = cv.getTickCount()
time = (e2 - e1)/cv.getTickFrequency()
```
我们将通过以下示例进行演示。下面的例子使用奇数大小从 5 到 49 的内核进行中值过滤。（不要担心结果会是什么样的，这不是我们的目标）：
```python
img1 = cv.imread('messi5.jpg')
e1 = cv.getTickCount()
for i in xrange(5,49,2):
    img1 = cv.medianBlur(img1,i)
e2 = cv.getTickCount()
t = (e2 - e1)/cv.getTickFrequency()
print( t )
# 得到的结果是 0.521107655 秒
```
> **注意**
你可以用时间模块做同样的事情。使用 time.time()函数来替代[cv.getTickCount](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#gae73f58000611a1af25dd36d496bf4487)，然后取两次的差异，比如start=time.time(), end = time.time(), print(end-start)。

## OpenCV 中的默认优化
许多 OpenCV 功能都使用 SSE2，AVX 等进行了优化。它还包含未经优化的代码。因此，如果我们的系统支持这些功能，我们应该利用它们（几乎所有现代处理器都支持它们）。优化功能在编译时是默认启用的，因此，OpenCV 在启用时运行优化代码，否则运行未优化代码。您可以使用[cv.useOptimized()](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#gafa6d5d04eff341825573ec6c0aa6519f)来检查它是否已启用/禁用，并使用[cv.setUseOptimized()](https://docs.opencv.org/4.0.0/db/de0/group__core__utils.html#ga3c8487ea4449e550bc39575ede094c7a)来启用/禁用它。让我们看一个简单的例子。
```python
# 检查是否使用了优化
In [5]: cv.useOptimized()
Out[5]: True
In [6]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
# 禁用优化
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop
```
请参阅，优化中值过滤比未优化版本快 2 倍。如果检查其来源，您可以看到中值过滤是 SIMD 优化的。因此，您可以使用它来在代码顶部启用优化（请记住它默认启用）。

## 测量 IPython 中的性能

有时您可能需要比较两个类似操作的性能。IPython 为您提供了一个神奇的命令时间来执行此操作。它运行代码几次以获得更准确的结果。注意，它们适用于测量单行代码。

例如，你知道以下哪个加法操作更好， x = 5; y = x**2, x = 5; y = x*x, x = np.uint8([5]); y = x*x 或者 y = np.square(x) ?我们可以使用IPython中的timeit魔术指令方便的进行测试。

```python
In [10]: x = 5
In [11]: %timeit y=x**2
10000000 loops, best of 3: 73 ns per loop
In [12]: %timeit y=x*x
10000000 loops, best of 3: 58.3 ns per loop
In [15]: z = np.uint8([5])
In [17]: %timeit y=z*z
1000000 loops, best of 3: 1.25 us per loop
In [19]: %timeit y=np.square(z)
1000000 loops, best of 3: 1.16 us per loop
```
你可以看到，x = 5; y = x * x 是最快的，与 Numpy 相比快了约 20 倍。如果您也考虑创建矩阵，它可能会快达 100 倍。很酷，对吗？（Numpy 开发者正在研究这个问题）

>**注意** Python 标量操作比 Numpy 标量操作更快。因此对于包含一个或两个元素的操作，Python 标量比 Numpy 数组更好。当阵列的大小稍大时，Numpy 会占据优势。

我们将再尝试一个例子。这次，我们将比较同一图像的[cv.countNonZero()](https://docs.opencv.org/4.0.0/d2/de8/group__core__array.html#gaa4b89393263bb4d604e0fe5986723914)和 np.count_nonzero()的性能。

```python
In [35]: %timeit z = cv.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop
In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop
```
看，OpenCV 功能比 Numpy 功能快近 25 倍。
>**注意** 通常，OpenCV 函数比 Numpy 函数更快。因此，对于相同的操作，OpenCV 功能是首选。但是，可能有例外，尤其是当 Numpy 使用视图而不是副本时。

## 更多 IPython 魔术命令
还有其他一些魔术命令可以测量性能，分析，线性分析，内存测量等。它们都有很好的文档记录。因此，此处仅提供这些文档的链接。建议有兴趣的读者试用。

## 性能优化技术

有几种技术和编码方法可以利用 Python 和 Numpy 的最大性能。这里仅注明相关的内容，并提供重要来源的链接。这里要注意的主要是，首先尝试以简单的方式实现算法。一旦它正常工作，对其进行分析，找到瓶颈并对其进行优化。
1. 尽量避免在 Python 中使用循环，尤其是双循环/三循环等。它们本质上很慢。
2. 将算法/代码矢量化到最大可能范围，因为 Numpy 和 OpenCV 针对向量运算进行了优化。
3. 利用缓存一致性。
4. 除非需要，否则永远不要复制数组。尝试使用视图。复制多维数组是一项耗费时间和空间都很多的操作。(译者注：译者认为这儿的意思是除非必要，否在尽量不要使用深拷贝)

即使在完成所有这些操作之后，如果您的代码仍然很慢，或者使用大型循环是不可避免的，请使用其他库（如 Cython）来加快速度。
## 其他资源
1. [Python 优化技术](http://wiki.python.org/moin/PythonSpeed/PerformanceTips)
2. Scipy 讲义-[高级 Numpy](http://scipy-lectures.github.io/advanced/advanced_numpy/index.html#advanced-numpy)
3. [Ipython 中的时序和分析](http://pynash.org/2013/03/06/timing-and-profiling/)

## 练习
