声明:任何情况下,都不要在正式比赛中使用指令集

\(n\) 方过百万,暴力碾标算。

简介

简单来说,指令集就是 CPU 可以直接接收并执行而不需要像其他代码一样需要经过繁杂处理的一类命令。现在的 Intel 和 AMD 的 CPU 一般都支持 SSE(128 位)、AVX(256 位)等指令集。以下以 AVX 指令集为例介绍一般使用方法。若想用别的指令集可以尝试把下文的 256 改为 128 等,如 __m256i 改为 __m128i

功能

指令集通过对存储和运算向量化来优化效率。通俗的一种理解是分块/状压,通过一种 64/128/256 位的容器存储变量。以 int 为例,一个 256 位的容器就可以存储 8 个 int,可以得到近似 \(\dfrac{1}{8}\) 的常数优化。

环境

1
#include<immintrin.h>

头文件是帮你封装好的,让你不用在代码中内联汇编。

变量类型

__m256float__m256ilong long__m256ddouble。 额,int 呢?其实可以用 __m256i 存,方式类似那个一个 long long 拆成两个 int 用的奇技淫巧。

基础指令

声明

直接像其他变量一样就行

1
2
3
__m256i a;
__m256 b;
__m256d c;

赋值

对于 __m256i,用 _mm256_set_epi32(int e7,int e6,int e5,int e4,int e3,int e2,int e1,int e0)_mm256_set_epi64x(long long e3,long long e2,long long e1,long long e0),这两条都指令返回一个 __m256i

1
2
3
__m256i a;
a=_mm256_set_epi32(1,1,4,5,1,4,1,9);
a=_mm256_set_epi64x(1,1,4,5);
注意,以上指令的下标是逆序的。
如果想整体赋值,可以用 _mm256_set1_epi32(int a)_mm256_set1_epi64x(long long a)

运算

_mm256_add_epi32(__m256i a,__m256i b)ab 内的值按 32 位整型大小相加并返回结果。同理,add 可换为 sub(减法)、mullo(乘法)、abs(绝对值)还有位运算等等。
64 位同理,参考上文格式,将 epi32 换为 epi64x 即可。下文亦不再过多赘述。

访问

这里是一个难点,认真看:
可以直接下标访问

1
2
__m256i a=_mm256_set_epi32(1,2,3,4,5,6,7,8);
printf("%d",a[2]);
结果为 \(3\times 2^{32}+4\)(事实上不会显示这个数,因为是以 int 形式输出的)。原因如下:
__m256i 存储是逆序的,所以下标要倒着看。而 __m256i 存的又是 long long,所以寻址到元素 \(3\)\(4\) 所在的那 64 位。 那我咋访问 int 类型?
int 指针强行指……
1
2
3
__m256i a=_mm256_set_epi32(1,2,3,4,5,6,7,8);
int *b=(int*)&a;
printf("%d",b[2]);
这样就输出 \(6\)

试试看:

参考资料

https://ouuan.github.io/post/n%E6%96%B9%E8%BF%87%E7%99%BE%E4%B8%87-%E6%9A%B4%E5%8A%9B%E7%A2%BE%E6%A0%87%E7%AE%97%E6%8C%87%E4%BB%A4%E9%9B%86%E4%BC%98%E5%8C%96%E7%9A%84%E5%9F%BA%E7%A1%80%E4%BD%BF%E7%94%A8/