mirror of
https://github.com/Light-City/CPlusPlusThings.git
synced 2026-10-28 19:04:45 +08:00
- const README: rewrite the space-saving claim per issue #5 follow-ups (constant-expression const vs #define immediates; which const qualify as constant expressions) - const README: fix the class-section diagnosis per issue #338 (overload resolution picks the const overload; the restriction only surfaces once that overload is removed) - sizeof README: virtual-inheritance result is compiler-dependent (GCC merges vbptr+vfptr -> 16, MSVC keeps them separate -> 32) #246 - static README: new section on internal linkage / access restriction with anonymous-namespace comparison #142 - 10-day practice: full problem list table + fixed day links #136 - new design_pattern/singleton/singleton_default.hpp: reusable thread-safe CRTP singleton template, compile+concurrency tested #303
174 lines
5.5 KiB
Markdown
174 lines
5.5 KiB
Markdown
> 来自 [C++那些事网站](https://light-city.github.io/stories_things/cpp-concurrency/cpp-simd/) 的文章,由站点同步脚本导出。
|
||
|
||
# C++ SIMD 向量化
|
||
|
||
最近在看相关向量化的内容,看起来有点头大,借此机会,学习一下高性能SIMD编程。
|
||
|
||
SIMD全称single-instruction multiple-data,单指令多数据。
|
||
|
||
在传统的计算机架构中,CPU一次只能处理一个数据元素。但是,许多任务涉及对大量数据执行相同的操作,例如对数组中的所有元素进行加法、乘法或逻辑操作等。SIMD编程通过向CPU提供专门的指令集,使得CPU能够同时对多个数据元素执行相同的操作。
|
||
|
||
这种处理方式特别适合涉及向量、矩阵、图像、音频和视频等数据的计算。
|
||
|
||
目前比较常用的有SSE、SSE2、AVX128、AVX256、AVX512。
|
||
|
||
本节,将简单学习一下AVX512的一些操作,操作比较多,这里只是引入一些。
|
||
|
||
## 1.术语
|
||
|
||
首先第一个问题便是,simd编程的代码跟平时写的代码长相不大一样,各种下划线以及命名,完全看不懂,如何理解呢?
|
||
|
||
诸如:
|
||
|
||
- _mm512_set1_ps
|
||
|
||
Broadcast single-precision (32-bit) floating-point value a to all elements of dst.
|
||
|
||
- _mm512_set1_epi32
|
||
|
||
Broadcast 32-bit integer a to all elements of dst.
|
||
|
||
于是,找到了下面这个表格:
|
||
|
||
| Abbreviation | Full Name | C/C++ Equivalent |
|
||
| ------------ | ------------------------------ | ---------------- |
|
||
| ps | packed single-precision | float |
|
||
| ph | packed half-precision | None* |
|
||
| pd | packed double-precision | double |
|
||
| pch | packed half-precision complex | None* |
|
||
| pi8 | packed 8-bit integer | int8_t |
|
||
| pi16 | packed 16-bit integer | int16_t |
|
||
| pi32 | packed 32-bit integer | int32_t |
|
||
| epi8 | extended packed 8-bit integer | int8_t |
|
||
| epi16 | extended packed 16-bit integer | int16_t |
|
||
| epi32 | extended packed 32-bit integer | int32_t |
|
||
| epi64 | extended packed 64-bit integer | int64_t |
|
||
| epi64x | extended packed 64-bit integer | int64_t |
|
||
|
||
> https://stackoverflow.com/questions/70911872/what-are-the-names-and-meanings-of-the-intrinsic-vector-element-types-like-epi6
|
||
|
||
再比如:
|
||
|
||
_mm512_mask_load_ps
|
||
|
||
_mm512_mask_loadu_ps
|
||
|
||
u表示unordered,表示加载无序,当使用 `_mm512_mask_loadu_ps` 函数加载内存中的数据时,不会执行对内存地址的任何对齐要求。而_mm512_mask_load_ps要求满足 64 字节对齐要求。
|
||
|
||
这样对照着学习,非常快的便可以知道每个接口的含义了。
|
||
|
||
相关API可以看看Interl Intrinsics Guide。
|
||
|
||
> https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html
|
||
|
||
## 2.实际例子
|
||
|
||
### 2.1 求最小值
|
||
|
||
对于512位,我们可以存储16个32位float。
|
||
|
||
```cpp
|
||
static inline rf_512 load(float* ptr) { return _mm512_loadu_ps(ptr); }
|
||
float a[width] = {1.0, 2.0, 3.0, 22.0, 5.0, 17.0, 9.0, 8.0,
|
||
9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
|
||
float b[width] = {3.3, 6.2, 5.3, 4.4, 5.5, 6.6, 7.7, 8.8,
|
||
9.9, 10.10, 21.11, 12.12, 13.13, 14.14, 15.0, 16.16};
|
||
rf_512 data = minimum(load(a), load(b));
|
||
```
|
||
|
||
于是我们可以快速得到:
|
||
|
||
```cpp
|
||
1 2 3 4.4 5 6.6 7.7 8 9 10 11 12 13 14 15 16
|
||
```
|
||
|
||
## 2.2 快速打乱数据顺序
|
||
|
||
对于输入数据是
|
||
|
||
```
|
||
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||
```
|
||
|
||
我们可以快速得到:
|
||
|
||
```cpp
|
||
16.16 2 14.14 13.13 5 6 7 8 9 10 11 12 13 14 15 16
|
||
```
|
||
|
||
对应实现:
|
||
|
||
```cpp
|
||
static inline rf_512 permutexvar(ri_512 idx, rf_512 src) {
|
||
return _mm512_permutexvar_ps(idx, src);
|
||
}
|
||
/*
|
||
raw data: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||
shuffle data: 16.16 2 14.14 13.13 5 6 7 8 9 10 11 12 13 14 15 16
|
||
*/
|
||
void print_permutexvar_mask() {
|
||
float a[width] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
|
||
9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
|
||
float b[width] = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8,
|
||
9.9, 10.10, 11.11, 12.12, 13.13, 14.14, 15.0, 16.16};
|
||
mask_type mask = make_bit_mask<1, 0, 1, 1, 0>();
|
||
int idx_array[width] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
|
||
ri_512 idx = _mm512_loadu_si512((__m512i*)idx_array);
|
||
rf_512 result = permutexvar_mask(load(a), mask, idx, load(b));
|
||
float result_arr[width];
|
||
store(result, result_arr);
|
||
std::cout << "raw data: ";
|
||
for (int i = 0; i < width; i++) {
|
||
std::cout << a[i] << " ";
|
||
}
|
||
std::cout << std::endl;
|
||
|
||
std::cout << "shuffle data: ";
|
||
for (int i = 0; i < width; i++) {
|
||
std::cout << result_arr[i] << " ";
|
||
}
|
||
std::cout << std::endl;
|
||
}
|
||
```
|
||
|
||
### 2.3 旋转
|
||
|
||
对于一个数组:
|
||
|
||
```
|
||
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
||
```
|
||
|
||
我们可以旋转得到:
|
||
|
||
```
|
||
4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3
|
||
```
|
||
|
||
or
|
||
|
||
```
|
||
14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13
|
||
```
|
||
|
||
也就是rotate down or rotate up。
|
||
|
||
在这里,我们可以这样实现:
|
||
|
||
```cpp
|
||
rf_512 rotateGeneral(float* arr, int s) {
|
||
int idx_array[width];
|
||
for (int i = 0; i < width; i++) {
|
||
idx_array[i] = (i + s) % width;
|
||
}
|
||
ri_512 idx = _mm512_loadu_si512((__m512i*)idx_array);
|
||
rf_512 result = permutexvar(idx, load(arr));
|
||
return result;
|
||
}
|
||
```
|
||
|
||
等等,还有其他的例子,可以发现通过使用simd,我们可以实现一些非常有趣的算法,加速对数组,批量数据的处理。
|
||
|
||
后面会继续学习simd,一起加油吧~
|
||
|
||
注:本节的完整示例已在星球更新,谢谢~ |