1. 簡介
本教程將介紹多種優化應用,以支持其在英特爾® 至強融核™ 處理器上運行。 本教程中的優化流程分為三個部分:
- 第一部分介紹用於對代碼進行矢量化(數據並行化)處理的通用優化技巧。
- 第二部分介紹如何添加線程層並行化,以充分利用處理器中的所有可用內核。
- 第三部分將通過在英特爾至強融核處理器上啟用內存優化,以優化代碼。
最后的結論部分將通過圖表方式展示各優化步驟所實現的性能提升。
優化過程如下:以串行、性能有待提升的示例代碼為基礎。 然后采用優化技巧處理該代碼,獲得矢量化版本的代碼,並對該矢量化代碼進行進一步的線程並行化處理,以使其成為並行版代碼。 最后使用英特爾® VTune™ Amplifier 分析該並行代碼的內存帶寬,以使用高帶寬內存進一步優化性能。 本教程以附件形式提供這三個版本的代碼(mySerialApp.c、myVectorizedApp.c 和 myParallelApp.c)。
示例代碼為一個流處理應用,帶有兩個包含輸入和輸出的緩沖區。 第一個輸入數據集包含二次方程系數。 第二個輸出數據集用於保留每個二次方程式的根。 為簡單起見,選擇的系數可確保二次方程式始終求得兩個實根。
考慮二次方程式:

兩個根為已知公式的解:

求得兩個實根的條件是
和 
2. 硬件和軟件
該程序在預生產英特爾® 至強融核™ 處理器(型號 7250,68 個內核,時鍾速度為 1.4 GHz,96 GB DDR4 RAM、16 GB 多通道動態隨機存取存儲器 (MCDRAM))上運行。 每內核 4 個硬件線程,因此該系統運行時共有 272 個硬件線程。 我們在該系統中安裝了 Red Hat Enterprise Linux* 7.2、英特爾® 至強融核™ 處理器軟件版 1.3.1 和英特爾® Parallel Studio XE 2016 更新版 3。
如果需要查看系統的處理器類型和數量,可以使用 /proc/cpuinfo 顯示輸出。 例如:

測試系統的完整輸出顯示了 272 個 CPU 或硬件線程。 請注意,標記字段顯示了指令擴展 avx512f、avx512pf、avx512er、avx512cd;它們均為英特爾至強融核處理器支持的指令擴展。
還可以運行lscpu,顯示有關 CPU 的信息:

以上命令顯示系統包含 1 個插座、68 個內核和 272 個 CPU。 它還顯示該系統有 2 個 NUMA 節點,272 個 CPU 全部屬於 NUMA 節點 0。 更多有關 NUMA 的信息敬請參閱 Knights Landing 上的 MCDRAM(高帶寬內存)簡介。
分析和優化示例程序之前,請編譯該程序並運行二進制代碼以獲取基准性能。
3. 評測基准代碼
隨附的程序 mySerialApp.c 中展示了簡單的解決方案實施。 系數 a、b 和 c 分成結構 Coefficients 組,根 x1和 x2 分成結構 Roots 組。 系數和根為單精度浮點數。 每個系數元組分別對應一個根元組。 程序將分配N 個系數元組和 N 個根元組。 N 的數值很大(N = 512M 元素,准確來說為 512*1024*1024 = 536,870,912 個元素)。 系數結構和根結構如下所示:

簡單程序根據上述公式計算實根 x1 和 x2。 我們還使用標准系統計時器測量計算時間。 不測量緩沖區分配時間和初始化時間。 簡單程序將計算流程重復 10 次。
開始時,通過使用英特爾® C++ 編譯器編譯基准代碼,以評測應用的基准性能:
$ icc mySerialApp.c
默認情況下,編譯器借助開關 -O2(面向最大速度優化)進行編譯。 然后運行該應用:
$ ./a.out
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
SERIAL
Elapsed time in msec: 461,222 (after 10 iterations)
輸出顯示,系統耗費了 461,222 毫秒對數量龐大的條目(N = 512M 元素)進行了 10 次迭代,以流處理數據、計算根並保存結果。 該程序為每個系數元組計算根元組。 注意,該基准代碼無法充分利用系統中的大量可用內核或 SIMD 指令,因為它以串行和標量模式運行(每次僅一個線程處理一個元組元素)。 因此,僅一個硬件線程 (CPU) 處於運行狀態,其他 CPU 均處於閑置狀態。 你可以使用編譯器選項 -qopt-report=5 -qopt-report-phase:vec 生成矢量化報告 (*.optrpt) 來驗證這點。
$ icc mySerialApp.c -qopt-report=5 -qopt-report-phase:vec
測量基准代碼性能后,我們開始對代碼進行矢量化處理。
4. 代碼矢量化
4.1. 將結構陣列改為陣列結構。 不要使用緩沖區分配中的多個層級。
提升代碼性能的第一個方法是將結構陣列 (AoS) 改為陣列結構 (SoA)。 (SoA) 可增加單位步長訪問的數據量。 我們不定義大量系數元組(a、b、c)和根元組的(x1、x2),而是重新安排數據結構,以便將其分配至 5 個大型陣列:a、b、c、x1 和 x2(參考程序 myVectorizedApp.c)。 另外,我們不使用 malloc 分配內存,而使用 _mm_malloc 使數據對齊 64 位邊界(見下一章節)。

4.2. 其他性能提升方法:消除類型轉換、數據對齊
下一步是消除不必要的類型轉換。 例如,函數 sqrt() 將雙精度視作輸入。 但由於我們將單精度作為本程序的輸入,因此編譯器需要將單精度轉化為雙精度。 為了消除不必要的數據類型轉換,我們可以使用 sqrtf(),而非 sqrt()。 同樣,我們不使用整數,而使用單精度。 例如,我們不使用 4,而使用 4.0f。 注意,4.0(沒有后綴 f)為雙精度浮點數,而 4.0f 為單精度浮點數。
數據對齊有助於數據高效地在內存之間移動。 對英特爾至強融核處理器而言,當數據起始地址位於 64 字節邊界時,內存數據移動可達到最佳狀態,就像英特爾® 至強融核™ 協處理器一樣。 為幫助編譯器進行矢量化,需要借助 64 位對齊進行內存分配,並使用編譯指示/指令,其中數據可用於告知編譯器內存訪問已對齊。 最好借助適當對齊的數據進行矢量化。 本文中所述的矢量化指能夠用單指令處理多個數據 (SIMD)。
在上述示例中,為對齊堆分配的數據,我們使用 _mm_malloc() 和 _mm_free() 來分配陣列。 注意,_mm_malloc() 相當於 malloc(),但它將對齊參數(以位為單位)用作第二個參數,即面向英特爾至強融核處理器的 64 位。 我們需要在數據前面插入一個子句,告知編譯器使用的 assume_aligned(a, 64)表示陣列 a 已對齊。 為告知編譯器特定循環中訪問的所有陣列均已對齊,可在循環前面添加子句 #pragma vector aligned。
4.3. 使用自動矢量化、運行編譯器報告,並通過編譯器開關禁用矢量化
矢量化指使用矢量處理單元 (VPU) 同時運算多個值的編程技巧。 自動矢量化指編譯器能夠識別循環中的機會並執行相應的矢量化。 你可以充分利用英特爾編譯器的自動矢量化功能,因為自動矢量化默認支持優化層 –O2 或更高層級。
例如,使用英特爾編譯器 icc 編譯 mySerialApp.c 示例代碼時,編譯器默認查找循環中的矢量化機會。 但編譯器需要遵循一定的規則(必須知曉循環運行、單進單出、直線式代碼、嵌套的最內層循環等等),以對這些循環進行矢量化處理。 你可以提供更多信息,幫助編譯器對循環進行矢量化處理。
為確定代碼是否已經實現矢量化,可以通過指定選項 -qopt-report=5 -qopt-report-phase:vec,生成矢量化報告。 之后編譯器會生成一份矢量化報告 (*.optrpt)。 該報告會告訴你各循環是否已完成矢量化,並簡要介紹循環矢量化。 注意,矢量化報告選項為 –qopt-report=<n>,其中的 n 用於規定細節等級。
4.4. 借助優化層 –O3 進行編譯
現在我們可借助優化層 –O3 進行編譯。 該優化層可實現最高速度,其優化程度遠高於默認優化層 –O2 。
借助自動矢量化,編譯器將 16 個單精度浮點數打包在矢量寄存器中並對該矢量執行運算,而不是在每個迭代循環中一次處理一個元素。
$ icc myVectorizedApp.c –O3 -qopt-report -qopt-report-phase:vec -o myVectorizedApp
編譯器生成以下輸出文件:二進制文件 myVectorizedApp 和矢量化報告 myVectorizedApp.optrpt。 如要運行二進制文件:
$ ./myVectorizedApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
Elapsed time in msec: 30496 (after 10 iterations)
該二進制文件運行時僅使用一個線程,但使用了矢量化。 myVectorizedApp.optrpt 報告應確認是否所有內層循環都已完成矢量化。
進行對比時,還需使用 -no-vec 選項編譯該程序:
$ icc myVectorizedApp.c –O3 -qopt-report -qopt-report-phase:vec -o myVectorizedApp-noVEC -no-vec
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
現在運行 myVectorizedApp–noVEC 二進制文件:
$ ./myVectorizedApp-noVEC
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
Elapsed time in msec: 180375 (after 10 iterations)
這次 myVectorizedApp.optrpt 報告顯示由於禁用了自動矢量化功能,因此循環沒有完成矢量化,與預期一樣。
現在我們可以觀察到兩次性能提升。 從原始版本(461,222 毫秒)到 no-vec 版本(180,375 版本)的性能提升主要得益於采用通用優化技巧。 從未矢量化版本(180,375 毫秒)到矢量化版本(30,496 毫秒)的性能提升主要得益於自動矢量化。
即使實現了性能提升,仍然只有一個線程執行運算。 支持多個線程並行運行,可進一步增強代碼,從而充分利用多核架構。
5. 啟用多線程化
5.1. 線程層並行化: OpenMP*
為充分利用英特爾至強融核處理器中數量龐大的內核(本系統中 68 個內核),可以通過並行運行 OpenMP 線程來擴展應用。 OpenMP 是面向共享內存的標准 API 和編程模型。
使用 OpenMP 線程時,需要包含頭文件"omp.h"並連接代碼和標記 –qopenmp。 在 myParallelApp.c 程序中,可在 for-loop 前添加以下指令:
#pragma omp parallel for simd
添加在 for-loop 前的編譯指示可告知編譯器生成一組線程並將 for-loop 中的工作分成多個數據塊。 每個線程按照 OpenMP 運行時調度執行幾個數據塊。 SIMD 結構僅顯示使用 SIMD 指令可同時執行的多次循環迭代。 它會告知編譯器忽略循環中的假定矢量依賴性,因此需謹慎使用。
在本程序中,線程並行化和矢量化在同一個循環中進行。 每個線程從循環的下限開始。 為確保 OpenMP(靜態調度)取得良好的對齊效果,我們可以限制並行循環的數量,並以串行方式處理其他循環。

另外,計算根的函數將變成

現在你可以編譯該程序,並將其連接至 –qopenmp:
$ icc myParallelApp.c –O3 -qopt-report=5 -qopt-report-phase:vec,openmp -o myParallelAppl -qopenmp
查看 myParallelApp.optrpt 報告,確認循環是否已借助 OpenMP 完成了矢量化和並行化。
5.2. 使用環境變量設置線程數量和相似性
OpenMP 實施可同時啟動幾個線程。 默認情況下,線程數量設為系統中的最多硬件線程。 本案例中將默認運行 272 個 OpenMP 線程。 不過我們還可以使用 OMP_NUM_THREADS 環境變量設定 OpenMP 線程的數量。 例如,以下命令可啟動 68 個 OpenMP 線程:
$ export OMP_NUM_THREADS=68
使用 KMP_AFFINITY 環境變量可設置線程相似性(能夠將 OpenMP 線程綁定至 CPU)。 為使線程均勻分布於系統,可將變量設置為分散 (scatter):
$ export KMP_AFFINITY=scatter
現在可使用系統中的全部內核來運行程序,並改變每內核運行的線程數。 以下是測試輸出,測試對每內核分別運行 1、2、3、4 個線程時的性能進行了對比。
測試系統中每內核運行 1 個線程:
$ export KMP_AFFINITY=scatter
$ export OMP_NUM_THREADS=68
$ ./myParallelApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 68, N = 536870912, N1 = 536870336, num-iters in remainder serial loop = 576, parallel-pct = 99.999893
Starting Compute on 68 threads
Elapsed time in msec: 1722 (after 10 iterations)
每內核運行 2 個線程:
$ export OMP_NUM_THREADS=136
$ ./myParallelApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 136, N = 536870912, N1 = 536869248, num-iters in remainder serial loop = 1664, parallel-pct = 99.999690
Starting Compute on 136 threads
Elapsed time in msec: 1781 (after 10 iterations)
每內核運行 3 個線程:
$ export OMP_NUM_THREADS=204
$ ./myParallelApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 204, N = 536870912, N1 = 536869248, num-iters in remainder serial loop = 1664, parallel-pct = 99.999690
Starting Compute on 204 threads
Elapsed time in msec: 1878 (after 10 iterations)
每內核運行 4 個線程:
$ export OMP_NUM_THREADS=272
$ ./myParallelApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 272, N = 536870912, N1 = 536867072, num-iters in remainder serial loop = 3840, parallel-pct = 99.999285
Starting Compute on 272 threads
Elapsed time in msec: 1940 (after 10 iterations)
從上述結果可看出,每內核運行 1 個線程並使用全部 68 個內核時性能達到最佳。
6. 面向英特爾至強融核處理器優化內核
6.1. 內存帶寬優化
系統中有兩種內存:16 GB 封裝內存 MCDRAM 和 96 GB 傳統平台 6 通道 DDR4 RAM(借助選項最高可擴展至 384 GB)。 MCDRAM 帶寬約為 500 GB/秒,而 DDR4 峰值性能帶寬約為 90 GB/秒。
有三種適用於 MCDRAM 的配置模式:扁平模式、緩存模式或混合模式。 如果 MCDRAM 配置為可尋址內存(扁平模式),用戶可明確地分配 MCDRAM 中的內存。 如果 MCDRAM 配置成緩存模式,整個 MCDRAM 都可用作二級緩存和 DDR4 內存之間的末級緩存。 如果 MCDRAM 配置成混合模式,部分 MCDRAM 可用作高速緩存,其余部分可用作可尋址內存。 下表列出了這些配置模式的優點和缺點:
| 內存模式 | 優點 | 缺點 |
| 扁平模式 |
|
|
| 緩存模式 |
|
|
| 混合模式 |
|
|
關於非一致性內存訪問 (NUMA) 架構,根據 MCDRAM 配置方式的不同,英特爾至強融核處理器以一個或兩個節點的形式出現。 如果 MCDRAM 配置為緩存模式,英特爾至強融核處理器將以 1 個 NUMA 節點的形式出現。 如果 MCDRAM 配置為扁平或混合模式,英特爾至強融核處理器將以 2 個 NUMA 節點的形式出現。 注意,集群模式可將英特爾至強融核處理器進一步划分成 8 個 NUMA 節點;不過本教程暫不涉及集群模式。
使用 numactl 實用程序可顯示系統中的 NUMA 節點。 例如,在本系統執行“numactl –H” — 其中 MCDRAM 配置為扁平模式,將顯示 2 個 NUMA 節點。 節點 0 包含 272 個 CPU 和 96 GB DDR4 內存,節點 1 包含 16 GB MCDRAM。
$ numactl -H
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
node 0 size: 98200 MB
node 0 free: 92888 MB
node 1 cpus:
node 1 size: 16384 MB
node 1 free: 15926 MB
node distances:
node 0 1
0: 10 31
1: 31 10
在部分 NUMA 模式下,可使用"numactl" 工具分配內存。 本示例中,節點 0 包含所有 CPU 和平台內存 DDR4,節點 1 包含封裝內存 MCDRAM。 可使用開關 –m 或 –-membind 迫使程序將內存分配至某個 NUMA 節點。
如果迫使應用分配 DDR 內存(節點 0),可運行以下命令:$ numactl -m 0 ./myParallelApp
這相當於:$ ./myParallelApp
現在使用 68 個線程來運行應用:
$ export KMP_AFFINITY=scatter
$ export OMP_NUM_THREADS=68
$ numactl -m 0 ./myParallelApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 68, N = 536870912, N1 = 536870336, num-iters in remainder serial loop = 576, parallel-pct = 99.999893
Starting Compute on 68 threads
Elapsed time in msec: 1730 (after 10 iterations)
如顯示 NUMA 節點的其他視圖,可運行命令“lstopo”。 該命令不僅顯示 NUMA 節點,還顯示與這些節點相關的一級高速緩存和二級高速緩存。
6.2. 分析內存使用率
應用是否受帶寬限制? 使用英特爾 VTune Amplifier 分析內存訪問。 DDR4 DRAM 峰值性能帶寬約為 90 GB/秒,MCDRAM 內存峰值性能約為 500 GB/秒。
在系統上安裝英特爾 VTune Amplifier,然后運行以下英特爾 VTune Amplifier 命令,以收集應用分配 DDR 內存時的內存訪問信息:
$ export KMP_AFFINITY=scatter; export OMP_NUM_THREADS=68; amplxe-cl -collect memory-access -- numactl -m 0 ./myParallelApp
通過查看“帶寬利用率直方圖”字段,可以了解應用的帶寬利用率。 該直方圖顯示 DDR 帶寬利用率較高。

通過查看內存訪問分析我們發現,DDR4 最高帶寬為 96 GB/秒,這幾乎是 DDR4 的峰值性能帶寬。 該結果表明應用受帶寬限制。

通過查看應用的內存分配,我們發現分配了包含 512 M 元素(即 512 * 1024 * 1024 個元素)的 5 個大型陣列。 每個元素都是單精度浮點(4 字節)數;因此每個陣列的大小約為 4*512 M 或 2 GB。 總內存分配為 2 GB * 5 = 10 GB。 這種內存大小非常適合 MCDRAM(16 GB 容量),因此分配 MCDRAM 中的內存(扁平模式)將有利於該應用。
分配 MCDRAM 中的內存(節點 1)時,需將參數–m 1 傳遞至命令 numactl,如下所示:
$ numactl -m 1 ./myParallelApp
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 68, N = 536870912, N1 = 536870336, num-iters in remainder serial loop = 576, parallel-pct = 99.999893
Starting Compute on 68 threads
Elapsed time in msec: 498 (after 10 iterations)
顯然,應用分配 MCDRAM 中的內存時,性能得到了明顯提升。
為進行對比,我們運行英特爾 VTune Amplifier 命令,收集應用分配 MCDRAM 內存時的內存訪問信息:
$ export KMP_AFFINITY=scatter; export OMP_NUM_THREADS=68; amplxe-cl -collect memory-access -- numactl -m 1 ./myParallelApp
該直方圖顯示 DDR 帶寬利用率較低,而 MCDRAM 的利用率較高:


通過查看內存訪問分析我們發現,DDR4 峰值帶寬為 2.3 GB/秒,而 MCDRAM 峰值帶寬達到了 437 GB/秒。

6.3. 使用編譯器手柄 –xMIC-AVX512 進行編譯
英特爾至強融核處理器支持 x87、英特爾® SIMD 流指令擴展(英特爾® SSE)、英特爾® SSE2、英特爾® SSE3、SIMD 流指令擴展 3 補充版、英特爾® SSE4.1、英特爾® SSE4.2、英特爾® 高級矢量擴展指令集(英特爾® AVX)、英特爾® 高級矢量擴展指令集 2(英特爾® AVX2)和英特爾® 高級矢量擴展指令集 512 (英特爾® AVX-512)指令集架構 (ISA), 但不支持英特爾® 交易同步擴展。
英特爾 AVX-512 在英特爾至強融核處理器中實施。 英特爾至強融核處理器支持以下組: 英特爾 AVX-512F、英特爾 AVX-512CD、英特爾 AVX-512ER 和英特爾 AVX-FP。 英特爾 AVX-512F(英特爾 AVX-512 基礎指令)包括適用於 512 位適量寄存器的英特爾 AVX 和英特爾 AVX2 SIMD 流指令;英特爾 AVX-512CD(英特爾 AVX-512 沖突檢測)有助於高效檢測沖突,以支持更多循環完成矢量化;英特爾 AVX-512ER(英特爾 AVX-512 指數和倒數指令)為以 2 為底的指數函數、倒數和平方根倒數提供指令。 英特爾 AVX-512PF(英特爾 AVX-512 預取指令)有助於降低內存操作延遲。
為充分利用英特爾 AVX-512,可借助編譯手柄 –xMIC-AVX512 編譯程序
$ icc myParallelApp.c -o myParallelApp-AVX512 -qopenmp -O3 -xMIC-AVX512
$ export KMP_AFFINITY=scatter
$ export OMP_NUM_THREADS=68
$ numactl -m 1 ./myParallelApp-AVX512
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 68, N = 536870912, N1 = 536870336, num-iters in remainder serial loop = 576, parallel-pct = 99.999893
Starting Compute on 68 threads
Elapsed time in msec: 316 (after 10 iterations)
注意,現在可以運行以下命令,生成名為 myParallelApp.s 的匯編文件:
$ icc -O3 myParallelApp.c -qopenmp -xMIC-AVX512 -S -fsource-asm
通過檢查匯編文件,可以確認是否生成了英特爾 AVX512 ISA。
6.4. 使用 –no-prec-div -fp-model fast=2 優化標記。
如果不要求高精度,我們可以使用 -fp-model fast=2進行編譯,大膽使用浮點模型,從而進一步優化浮點數(但不太安全)。 編譯器實施更快速、精度較低的平方根和除法運算。 例如:
$ icc myParallelApp.c -o myParallelApp-AVX512-FAST -qopenmp -O3 -xMIC-AVX512 -no-prec-div -no-prec-sqrt -fp-model fast=2
$ export OMP_NUM_THREADS=68
$ numactl -m 1 ./myParallelApp-AVX512-FAST
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 68, N = 536870912, N1 = 536870336, num-iters in remainder serial loop = 576, parallel-pct = 99.999893
Starting Compute on 68 threads
Elapsed time in msec: 310 (after 10 iterations)
6.5. 將 MCDRAM 配置成緩存
在 BIOS 設置中,將 MCDRAM 配置成緩存並重啟系統。 numactl 實用程序可確認是否只有一個 NUMA 節點,因為 MCDRAM 配置成緩存后,對該實用程序來說是透明的:
$ numactl -H
available: 1 nodes (0)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
node 0 size: 98200 MB
node 0 free: 94409 MB
node distances:
node 0
0: 10
重新編譯該程序:
$ icc myParalledApp.c -o myParalledApp -qopenmp -O3 -xMIC-AVX512 -no-prec-div -no-prec-sqrt -fp-model fast=2
並運行該程序:
$ export OMP_NUM_THREADS=68
$ ./myParalledApp-AVX512-FAST
No. of Elements : 512M
Repetitions = 10
Start allocating buffers and initializing ....
thread num=0
Initializing
numthreads = 68, N = 536870912, N1 = 536870336, num-iters in remainder serial loop = 576, parallel-pct = 99.999893
Starting Compute on 68 threads
Elapsed time in msec: 325 (after 10 iterations)
觀察該應用中將 MCDRAM 用作高速緩存是否沒有其他優勢。
7. 總結和結論
本教程介紹了以下主題:
- 內存對齊
- 矢量化
- 生成編譯器報告以協助代碼分析
- 使用命令行實用程序
cpuinfo、lscpu、numactl、lstopo - 使用 OpenMP 添加線程層並行化
- 設置環境變量
- 使用英特爾 VTune Amplifier 分析帶寬利用率
- 使用
numactl分配 MCDRAM 內存 - 使用英特爾 AVX512 標記進行編譯,以提升性能
下表顯示了從基准代碼完成每一個優化步驟后所實現的性能提升:基於數據對齊的通用優化、矢量化、添加線程層並行化、以扁平模式分配 MCDRAM 內存、借助英特爾 AVX512 進行編譯、借助無精度標記進行編譯,以及將 MCDRAM 用作高速緩存。

通過使用全部可用內核、英特爾 AVX-512 矢量化和 MCDRAM 帶寬,我們能夠大幅縮短執行時間。
參考資料:
- 面向英特爾® 至強融核™ 處理器以及英特爾® AVX-512 ISA 的編譯
- Knights Landing 上的 MCDRAM(高帶寬內存)簡介
- 英特爾® C++ 編譯器矢量化指南
- 在 Stream Triad 上優化 Knights Landing 的內存帶寬
- 英特爾® 至強融核™ 協處理器上的流處理
- 數據對齊有助於實現矢量化
- 為在英特爾® 至強™ 處理器上實現最佳性能的內存管理: 對齊和預取
- 英特爾® MIC 架構高級優化
- 使用 OpenMP 4.0 在程序中啟用 SIMD
- 英特爾® 至強™ 處理器 — 內存模式和集群模式: 配置和用例
關於作者
Loc Q Nguyen 擁有達拉斯大學 MBA 學位、麥吉爾大學電子工程專業碩士學位以及蒙特利爾理工學院電子工程專業學士學位。 目前在英特爾公司軟件及服務事業部擔任軟件工程師。 研究領域包括計算機網絡、並行計算和計算機圖形。
性能測試中使用的軟件和工作負載可能僅在英特爾® 微處理器上進行了性能優化。 諸如SYSmark和MobileMark等測試均系基於特定計算機系統、硬件、軟件、操作系統及功能, 上述任何要素的變動都有可能導致測試結果的變化。 請參考其他信息及性能測試(包括結合其他產品使用時的運行性能)以對目標產品進行全面評估。 如欲了解更多信息,請訪問http://www.intel.com/performance.
