自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SIMD能力初體驗(yàn),你了解多少?

商務(wù)辦公
SIMD技術(shù)在大數(shù)據(jù)和機(jī)器學(xué)習(xí)領(lǐng)域有非常廣泛的應(yīng)用。Clickhouse為什么快,NumPy為什么快,背后都離不開SIMD技術(shù)的支持。那么SIMD到底是什么呢,我們來看看。

SIMD,Single Instruction Multiple Data,是一種在CPU指令級別支持的并行處理技術(shù)。大家最早聽說這個詞,應(yīng)該是在《計(jì)算機(jī)組成原理》的課上。

為了體現(xiàn)出區(qū)別,我們先看最簡單的模式:Single Instruction Single Data (SISD)。這種模式下,一個單核CPU接收并執(zhí)行一條指令。該指令只加載內(nèi)存單元里的一條數(shù)據(jù)到寄存器,然后進(jìn)行處理。

Single Instruction Single Data

SIMD模式下,CPU的寄存器通常比較大,比如128bit,目前最新已支持到512bit。如果我們使用512bit寄存器,那么一次性就可以加載8個int64數(shù)字,以并行度=8的速度進(jìn)行計(jì)算:

Single Instruction Multiple Data

當(dāng)然,還有兩個分類 MISD 和 MIMD,這里就不細(xì)說了。

Intel CPU對SIMD的支持

Intel CPU通過擴(kuò)充指令集提供了對SIMD的支持。按照出現(xiàn)順序,總共有三套:MMX、SSE 和 AVX:

我們可以通過Intel官方網(wǎng)站查詢自己的處理器是否支持(地址附在文章末尾)。下面以MacOS為例,簡單看一下。通過sysctl查看CPU型號:

sysctl -a | grep brand 
machdep.cpu.brand_string: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
machdep.cpu.brand: 0

下面是查詢結(jié)果,可見主流的SSE和AVX指令集都是支持的:

那么這些指令集怎么用呢?Intel官方提供了一套C語言庫,并且有詳細(xì)的函數(shù)文檔,名字為 "Intel? Intrinsics Guide"。

這些函數(shù)有明確的命名規(guī)范,由三段構(gòu)成,分別是:

  1. _mm<位數(shù)>_mm 128bit,_mm256 256bit,_mm512 512bit。
  2. _<運(yùn)算>_add 加, _sub 減,_mul 乘,_div 除,與或非同理。
  3. _<原始類型>_epi16 int16, _epi32 int32, _ps float32, _pd float64。

比如我想看下256bit下的加法,搜索 mm256_add 會返回一組函數(shù):

接下來我們用這些指令來看看下性能吧。

準(zhǔn)備工作

由于要做性能測試,編程語言是C/C++,所以選擇 google/benchmark 作為輔助。測試場景是兩個100w條數(shù)據(jù)的數(shù)組做加法,數(shù)組里的元素可以是int32、float32、int64等。后面我們采用float32進(jìn)行測試。

google/benchmark 跟著Github上"Installation" 部分走就好了,最后必須執(zhí)行安裝這一步:

sudo cmake --build "build" --config Release --target install

編寫代碼:

先寫一段比較正常的單測代碼,通過 #include <immintrin.h>可使用SIMD的能力。準(zhǔn)備工作包括:

  1. 初始化3個長度為100w的數(shù)組 a、b、c, _mm_malloc負(fù)責(zé)內(nèi)存分配。
  2. 對 a 和 b 進(jìn)行初始化。

計(jì)算邏輯是 c = a + b,跑多少輪次由 benchmark::State &state 來控制。代碼如下:

#include <immintrin.h>
#include <benchmark/benchmark.h>

constexpr int N = 1000000;

static void normal(benchmark::State &state)
{
    float *a = static_cast<float *>(_mm_malloc(sizeof(float) * N, 16));
    float *b = static_cast<float *>(_mm_malloc(sizeof(float) * N, 16));
    float *c = static_cast<float *>(_mm_malloc(sizeof(float) * N, 16));
    for (int i = 0; i < N; ++i)
    {
        a[i] = i;
        b[i] = 2 * i;
    }

    for (auto _ : state)
    {
        for (int i = 0; i < N; ++i)
        {
            c[i] = a[i] + b[i];
        }
    }

    _mm_free(a);
    _mm_free(b);
    _mm_free(c);
}

BENCHMARK(normal);

我們將文件命名為 benchmark_float32.cpp。編譯并執(zhí)行:

g++ -Wall -std=c++20 -msse4 -mavx512f -mavx512bw benchmark_float32.cpp -pthread -lbenchmark -o benchmark_float32

由于需要支持sse4 avx512,編譯時需要加上 -msse4 -maxv512f -mavx512bw。運(yùn)行 ./benchmark_float32 結(jié)果如下:

2023-06-17T18:30:04+08:00
Running ./benchmark_float32
Run on (8 X 2300 MHz CPU s)
CPU Caches:
  L1 Data 48 KiB
  L1 Instruction 32 KiB
  L2 Unified 512 KiB (x4)
  L3 Unified 8192 KiB
Load Average: 3.24, 3.72, 4.09
-----------------------------------------------------
Benchmark           Time             CPU   Iterations
-----------------------------------------------------
normal        1821404 ns      1812256 ns          386

到當(dāng)前為止,測試能夠跑起來了。我們再加一個 128bit 計(jì)算的支持。這需要3個函數(shù):

  1. _mm_load_ps 將4個打包的float32加載到一個__m128類型的變量里。
  2. _mm_add_ps 對2個 __m128類型的變量做加法。
  3. _mm_store_ps 將1個__m128類型的變量存到一個float32*指向的內(nèi)存里。

組裝起來就是:

for (int i = 0; i < N; i += 4)
{
    __m128 v1 = _mm_load_ps(a + i);
    __m128 v2 = _mm_load_ps(b + i);
    __m128 v3 = _mm_add_ps(v1, v2);
    _mm_store_ps(c + i, v3);
}

由于一個 __m128類型的變量可以容納4個float32,所以 i 每次加4。

同樣的方法,我們可以把 __m256 和 __m512 都納入測試,測試結(jié)果如下:

可以發(fā)現(xiàn),這些擴(kuò)容指令集的執(zhí)行性能還是不錯的,不過由于load和store需要額外的時間,并沒有倍數(shù)的提升。

同樣的方式,我們拿 int32 和 int64 進(jìn)行測試,測試結(jié)果如下:

我們看 Iterations 這個指標(biāo),這個指標(biāo)越大,說明運(yùn)行速度越快。我們將不同類型匯總成表格:

Iterations

float32

int32

int64

float32-O3

int64-O3

normal

388

383

361

1541

516

sse128

573

709

345

1441

2716

avx256

943

1176

550

1435

2959

avx512

1791

1872

759

6611

2601

可以發(fā)現(xiàn),float32 和 int32 的迭代輪次逐個增加,而int64 在sse128和normal模式下基本持平,avx256和avx512呈現(xiàn)增加趨勢??赡茉蚴窃趕se128模式下,計(jì)算速度理論上增加100%,load和save的時間抵消了計(jì)算速度的收益。

另外,在編譯時開啟 -O3 最高級別的編譯優(yōu)化之后, int64 在 SIMD下提速非常明顯,float32只有在 avx512模式下有明顯提速。

除了簡單的加減乘除,SSE 和 AVX 能夠支持很多形式的計(jì)算,包括但不限于:位運(yùn)算、類型轉(zhuǎn)換、比較、加密算法、數(shù)學(xué)函數(shù)、統(tǒng)計(jì)函數(shù)、隨機(jī)數(shù)、字符串等,完整列表可參考 "Intel? Intrinsics Guide"。由于只是學(xué)習(xí)目的,并沒有對所有這些函數(shù)進(jìn)行測試。

問了下ChatGPT “SIMD的劣勢”,給出的回復(fù)如下:

  • 需要特定的硬件支持:SIMD指令是由CPU硬件支持的,需要特定的硬件支持才能使用。如果CPU不支持SIMD指令集合,就無法使用SIMD指令。
  • 編碼復(fù)雜度高:使用SIMD指令需要對指令集有深入的了解和熟練運(yùn)用,編碼過程相對比較復(fù)雜。
  • 數(shù)據(jù)結(jié)構(gòu)限制:SIMD指令適用于處理順序存儲的數(shù)據(jù),并且需要按照一定的方式將數(shù)據(jù)打包。因此,在某些情況下,需要進(jìn)行數(shù)據(jù)結(jié)構(gòu)的轉(zhuǎn)換才能使用SIMD指令。
  • 無法處理分支:SIMD指令是在數(shù)據(jù)流上進(jìn)行操作,不能直接處理分支,需要將分支轉(zhuǎn)換為數(shù)據(jù)流并按照特定的方式處理分支。
  • 容易出現(xiàn)精度誤差:使用SIMD指令進(jìn)行高精度計(jì)算時,可能會出現(xiàn)精度誤差。這是由于浮點(diǎn)數(shù)精度有限,并且處理過程中可能會將數(shù)據(jù)轉(zhuǎn)換為其他精度的數(shù)據(jù)類型而導(dǎo)致的。

單純從應(yīng)用上來看,Clickhouse啟發(fā)了一眾大數(shù)據(jù)處理框架對SIMD能力的引入、NumPy庫的引入,還有多媒體領(lǐng)域的廣泛使用,我們有理由相信SIMD帶來的性能收益。

Clickhouse具體做了哪些優(yōu)化,對SIMD的能力的應(yīng)用有多充分,后面了解完再補(bǔ)充。

查詢CPU信息:
https://ark.intel.com/content/www/us/en/ark.html。

責(zé)任編輯:姜華 來源: 今日頭條
相關(guān)推薦

2009-08-01 09:06:35

UbuntuOneLinux開源操作系統(tǒng)

2009-03-09 15:12:39

XenServer安裝

2012-12-27 10:58:24

KVMKVM概念

2023-10-25 08:17:06

Lite模式代理類

2023-10-29 08:35:47

AndroidAOP編程

2021-06-06 18:22:04

PprofGopher邏輯

2020-03-25 08:47:22

智能邊緣邊緣計(jì)算網(wǎng)絡(luò)

2010-11-22 10:31:17

Sencha touc

2011-05-30 15:12:10

App Invento 初體驗(yàn)

2023-08-17 10:12:04

前端整潔架構(gòu)

2015-11-09 10:44:37

DevOpsIT運(yùn)維

2020-12-10 09:00:00

開發(fā).NET工具

2021-12-09 07:47:58

Flink 提交模式

2023-12-24 12:56:36

協(xié)程

2022-06-07 07:37:40

線程進(jìn)程開發(fā)

2019-08-07 17:18:18

云計(jì)算云原生函數(shù)

2023-09-07 10:26:50

接口測試自動化測試

2025-01-16 10:41:40

2011-08-23 11:03:35

ATM

2022-02-08 12:06:12

云計(jì)算
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號