计算平均指令时间_为什么向量化计算(vectorization)会这么快?
背景
在一次iOS程序的性能測試過程中,我們發現一個自己寫的argmax函數的耗時嚴重超出預期——這個預期是基于平常神經網絡中的argmax op的速度得到的直接感官體驗。不過這也不算意外,第一個版本我們只是用了for循環去實現argmax,在那時,我們已經有所預感這會是個性能瓶頸。最終,我們將for循環實現替換成了iOS庫中提供的vDSP_maxvi(value, 1, &maxValue, &maxIndex), (vDSP_Length)channel),性能得到了解放。
就像這樣,時不時的,在使用numpy庫或者各種Tensor張量庫進行計算的時候,我們都會感嘆這些庫計算的速度之快,以至于遠遠超越自己寫的for循環。然后,我們就會逐漸并且越來越多的聽說到一個詞——vectorization(向量化計算)——其帶來了巨大的計算性能。
什么是vectorization?
向量化計算(vectorization),也叫vectorized operation,也叫array programming,說的是一個事情:將多次for循環計算變成一次計算。
上圖中,左側為vectorization,右側為尋常的For loop計算。將多次for循環計算變成一次計算完全仰仗于CPU的SIMD指令集,SIMD指令可以在一條cpu指令上處理2、4、8或者更多份的數據。在Intel處理器上,這個稱之為SSE以及后來的AVX,在Arm處理上,這個稱之為NEON。
因此簡單來說,向量化計算就是將一個loop——處理一個array的時候每次處理1個數據共處理N次,轉化為vectorization——處理一個array的時候每次同時處理8個數據共處理N/8次。
vectorization如何讓速度更快?
我們以x86指令集為例,1997年,x86擴展出了MMX指令集,伴隨著80-bit的vector寄存器,首開向量化計算的先河。 之后,x86又擴展出了SSE指令集 (有好幾個版本, 從SSE1到SEE4.2),伴隨著128-bit寄存器。而在2011年,Intel發布了Sandy Bridge架構——擴展出了AVX指令集(256-bit寄存器)。在2016年,第一個帶有AVX-512寄存器的CPU發布了(512-bit寄存器,可以同時處理16個32-bit的float數)。SSE和AVX各有16個寄存器。SSE的16個寄存器為XMM0-XMM15,AVX的16個寄存器為YMM0-YMM15。XMM registers每個為128 bits,而YMM寄存器每個為256bit(AVX512為512bit)。
SSE有3個數據類型:__m128 , __m128d 和 __m128i,分別代表Float、double (d) 和integer (i)。AVX也有3個數據類型: __m256 , __m256d 和 __m256i,分別代表Float、double (d) 和 integer (i)。
Gemfield使用下面一小段C++程序來展示一下AVX帶來的計算速度:
#include編譯并運行:
#compile civilnet.cpp gemfield@ThinkPad-X1C:~$ g++ -march=skylake-avx512 civilnet.cpp -o civilnet#run civilnet gemfield@ThinkPad-X1C:~$ ./civilnet result: 2.68435e+08 5.36871e+08 5.36871e+08 1.07374e+09 1.07374e+09 2.14748e+09 2.14748e+09 2.14748e+09 elapsed time: 2.39723s resultV: 2.68435e+08 5.36871e+08 5.36871e+08 1.07374e+09 1.07374e+09 2.14748e+09 2.14748e+09 2.14748e+09 elapsed time: 0.325577sfor loop計算消耗了2.39723秒,而vectorization計算消耗了0.325577s,可以看到AVX的計算速度遠超for loop,因為AVX使用了下面這樣的并行方式:
除了vectorization,還有什么可以讓CPU計算速度更快?
如今的CPU并不是大多數程序員所想象的那個黑盒子——按照PC寄存器指向的地址load指令一條一條的執行,這樣的CPU在486之后就滅絕了。現代CPU(Intel Core2后,AMDBulldozer后)的管線寬度為4個uops,一個時鐘周期內最多可以執行4條指令(如果同時有loads、stores和single-uop的ALU指令)。因此,vectorization并不是CPU唯一一種并行計算的方式 。在指令與指令層面同樣有并行機制,可以讓一個單獨的CPU core在同一時間內執行多條CPU指令。當排隊中的多條CPU指令包含了loads、stores、ALU,多數現代的CPU可以在一個時鐘周期內同時執行4條指令。平均下來,CPU在每個時鐘周期內同時執行2條指令甚至更好——這仰仗于程序如何更好的優化。
接下來,應用層的程序員還會熟悉這一點:多線程——在多個處理器核上同時運行多個指令序列。比如,在gemfield的機器上,cpu型號為“Core(TM) i9-9820X CPU”,cpu核為10個,使用超線程技術將CPU核擴展為20個邏輯核/線程數:
gemfield@AI3:~$ cat /proc/cpuinfo | grep -i "processor" processor : 0 processor : 1 processor : 2 processor : 3 processor : 4 processor : 5 processor : 6 processor : 7 processor : 8 processor : 9 processor : 10 processor : 11 processor : 12 processor : 13 processor : 14 processor : 15 processor : 16 processor : 17 processor : 18 processor : 19gemfield@AI3:~$ cat /proc/cpuinfo | grep -i "processor" | wc -l 20在這臺機器上,我們可以同時運行20個線程(因為20個核是由HT擴展出來的,真正能同時運行的線程數量位于10個到20個之間)。只不過20個超線程對計算密集型的加速并非20倍(也即并非超線程數),而是10倍(也即cpu核數):
“由于超線程只是為每個核心提供兩組線程上下文單元,兩個線程其實是共享各種核內運算部件的。超線程的好處是線程之間往往沒有各種數據依賴關系,兩個線程的指令流可以盡量填充流水線并充分利用亂序多發射能力。互相掩蓋對方的各種延遲,提高每個核心的利用效率。這里的向量計算已經完整地利用了浮點乘加的吞吐能力,所以超線程并不帶來好處”,出自https://zhuanlan.zhihu.com/p/28226956。
因此,一個像Gemfield機器上這樣的強大CPU,它擁有20個邏輯核、10個CPU核,每個核的每個時鐘周期平均執行2個vector計算,每個vector計算可以同時操作8個float數。因此,至少在理論上,gemfield的機器可以在一個時鐘周期內執行10 * 2 * 8 = 160個操作(當前,不同的指令有不同的吞吐量)。
總結
因此,我們一共在3個層面上通過并行化來提高CPU的計算速度:
1,vectorization,也就是SIMD指令集;
2,cpu pipeline width及亂序執行;
3,多核處理器及多線程;
CPU通過上述不同層面的并行化來孜孜不倦的提高計算速度,而這種使用并行化來提高計算速度的理念,正是GPU與生俱來的天賦。
總結
以上是生活随笔為你收集整理的计算平均指令时间_为什么向量化计算(vectorization)会这么快?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: realme x是塑料后盖吗
- 下一篇: vue 前端显示图片加token_前端V