【HPC】Intel SIMD技术——如何用code检查你的CPU支持哪些指令集?

本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


博客内容主要围绕:
       5G协议讲解
       算力网络讲解(云计算,边缘计算,端计算)
       高级C语言讲解
       Rust语言讲解

       去年公司启动了6G项目的预研,我们实验室研究的主题是目前6G中比较火的技——通感一体。我们的原型机可以通过无线电(CSI)来检测人们的呼吸频率,运动轨迹等
       因为整体的研究还处于起步阶段,算法设计复杂度也比较大,例如,我们需要在20ms内完成 218400B 的计算,计算中涉及多次FFT、SG滤波等算法。
       开始,我们没利用SIMD加速,会出现实时性差(我们需要实时显示呼吸频率的变化),还会出现数据在网卡中丢失的问题。之后利用AVX512指令集进行加速,速度提升了200%。

在这里插入图片描述

用code检查你的CPU支持哪些指令集

       并不是每个Intel 的 CPU 都支持所有的 SIMD 指令集。例如,至强10代就不支持AVX512。我之前是在至强11代上开发的程序,使用AVX512没有问题,后来在实验室的机器上程序就无法运行,原因就是实验室的机器是至强10代,不支持AVX512指令。后来不得不使用AVX指令重新优化code。

       那么问题来了,如何通过code实现程序启动动态选择使用哪个指令集?如果感兴趣接着往下看👇。

内联汇编看不懂的,点这里🤘

CPUID指令简介

       根据输入到EAX (在某些情况下,也包括ECX) 中的值,返回处理器信息和支持的特性信息。对于SIMD指令集的检测,我们需要将 0x01输入到EAX中,支持的feature信息会输出到ECX和EDX中,如下图
在这里插入图片描述
ECX中的返回值含义:
在这里插入图片描述
EDX中的返回值含义:
在这里插入图片描述
要想使用CPUID首先我们需要检查处理器是否支持CPUID 指令。EFLAGS寄存器中的ID标志(第21位)表示对CPUID指令的支持,见下图
在这里插入图片描述
EFLAGS寄存器中的ID标志(第21位)表示对CPUID指令的支持。如果软件可以设置并清除这个标志,则执行该程序的处理器支持CPUID指令。如果在不支持CPUID指令的处理器上执行,会产生一个无效的opcode异常(#UD)。


检查处理器是否支持CPUID指令

操作流程如下:

  1. 读取RFLAGS;
  2. 将读取到的值的ID flag值为1,写回RFLAGS。再次读取RFLAGS,判断ID flag是否位1,如果为1,继续步骤3。否则不支持CPUID指令;
  3. 将读取到的值的ID flag值为0,写回RFLAGS。再次读取RFLAGS,判断ID flag是否位0,如果为0,支持CPUID指令。否则不支持CPUID指令。

代码实现:

uint8_tsupports_cpuid()
{
	uint64_trc=0;

	__asm__volatile("pushfq\n\t"
					"popq %%rax\n\t"
					"and $0x00200000,%%eax\n\t"
					"cmp $0x00200000,%%eax\n\t"
					"je NOT_SUPPORT%=\n\t"
					"or $0x00200000,%%eax\n\t"
					"pushq %%rax\n\t"
					"popfq\n\t"
					"pushfq\n\t"
					"popq %%rax\n\t"
					"and $0x00200000,%%eax\n\t"
					"cmp $0x00200000,%%eax\n\t"
					"jne NOT_SUPPORT%=\n\t"
					"and $0xffdfffff,%%eax\n\t"
					"pushq%%rax\n\t"
					"popfq\n\t"
					"pushfq\n\t"
					"popq %%rax\n\t"
					"and $0x00200000,%%eax\n\t"
					"cmp $0x00200000,%%eax\n\t"
					"je NOT_SUPPORT%=\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT%=:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:NOP"
					:"=rm"(rc)
					:
					:"cc");

	return(uint8_t)rc;
}

检查是否支持XMM技术

操作流程:

  1. 写1到eax中;
  2. 执行cpuid指令;
  3. 检查edx中的bit 23是否为1,如果是1表示支持MMX。否则不支持。

代码如下:

uint8_tsupports_mmx()
{
	uint64_trc=0;

	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"test $0x00800000,%%edx\n\t"
					"je NOT_SUPPORT_MMX\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT_MMX:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持SSE

操作流程:

  1. 写1到eax中;
  2. 执行cpuid指令;
  3. 检查edx中的bit 25是否为1,如果是1表示支持SSE。否则不支持。

代码如下:

uint8_tsupports_sse()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"test $0x02000000,%%edx\n\t"
					"je NOT_SUPPORT_SSE\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT_SSE:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持SSE2

操作流程:

  1. 写1到eax中;
  2. 执行cpuid指令;
  3. 检查edx中的bit 26是否为1,如果是1表示支持SSE2。否则不支持。

代码如下:

uint8_tsupports_sse2()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"test $0x04000000,%%edx\n\t"
					"je NOT_SUPPORT_SSE2\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT_SSE2:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持SSE3

操作流程:

  1. 写1到eax中;
  2. 执行cpuid指令;
  3. 检查ecx中的bit 0是否为1,如果是1表示支持SSE3。否则不支持。

代码如下:

uint8_tsupports_sse3()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"test $0x00000001,%%ecx\n\t"
					"je NOT_SUPPORT_SSE3\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT_SSE3:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持SSE4.1

操作流程:

  1. 写1到eax中;
  2. 执行cpuid指令;
  3. 检查ecx中的bit 19是否为1,如果是1表示支持SSE4.1。否则不支持。

代码如下:

uint8_tsupports_sse4_1()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"test $0x00080000,%%ecx\n\t"
					"je NOT_SUPPORT_SSE4_1\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT_SSE4_1:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持SSE4.2

操作流程:

  1. 写1到eax中;
  2. 执行cpuid指令;
  3. 检查ecx中的bit 20是否为1,如果是1表示支持SSE4.2。否则不支持。

代码如下:

uint8_tsupports_sse4_2()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"test $0x00100000,%%ecx\n\t"
					"je NOT_SUPPORT_SSE4_2\n\t"
					"mov $1,%0\n\t"
					"jmp CSRS%=\n\t"
					"NOT_SUPPORT_SSE4_2:\n\t"
					"mov $0,%0\n\t"
					"CSRS%=:"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

首先介绍 XCR0(EXTENDED CONTROL REGISTERS)寄存器

如果 CPUID.01H:ECX.XSAVE[bit 26] 为1,处理器支持一个或多个扩展控制寄存器(XCRs)。
目前,唯一定义的此类寄存器是XCR0。这个寄存器中的每一位都是一个标志位,表示操作系统是否为某个处理器状态组件提供上下文管理功能,例如x87 FPU状态、SSE状态、AVX状态。操作系统通过设置 XCR0指明它为那个组件提供了上下文管理的功能。
在这里插入图片描述
如果CPUID.01H:ECX.OSXSAVE[bit 27] == 1,那么软件可以访问 XCR0。XCR0中的每个位(63位除外)对应一个处理器状态组件,XCR0支持多达63组处理器状态组件。XCR0的第63位预留给未来扩展。

检查是否支持AVX

操作流程:

  1. 检查CPUID是否支持;
  2. 执行CPUID;
  3. 检查ebx中的bit 28为1(表示支持AVX);
  4. ecx中的bit 27是否为1(表示 XGETBV 指令可以使用);
  5. 执行XGETBV,读取XCR0;
  6. 检查返回的结果中bit1和bit2是否为1(表示操作系统支持XMM和YMM的状态管理)。
    在这里插入图片描述
    代码如下:
uint8_tsupports_avx()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"and $0x18000000,%%ecx\n\t"
					"cmp $0x18000000,%%ecx\n\t"
					"jne NOT_SUPPORT_AVX\n\t"
					"mov $0,%%ecx\n\t"
					"xgetbv\n\t"
					"and $0x6,%%eax\n\t"
					"cmp $0x6,%%eax\n\t"
					"jne NOT_SUPPORT_AVX\n\t"
					"mov $1,%0\n\t"
					"jmp RSCS_AVX\n\t"
					"NOT_SUPPORT_AVX:\n\t"
					"mov $0,%0\n\t"
					"RSCS_AVX:NOP"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持AVX2

操作流程:

  1. 检查CPUID是否支持;
  2. 执行CPUID;
  3. 检查ebx中的bit 5是否为1(表示支持AVX2);
  4. ecx中的bit 27是否为1(表示 XGETBV 指令可以使用);
  5. 执行XGETBV,读取XCR0;
  6. 检查返回的结果中bit1和bit2是否为1(表示操作系统支持XMM和YMM的状态管理)。

代码如下:

uint8_tsupports_avx2()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"and $0x08000000,%%ecx\n\t"
					"cmp $0x08000000,%%ecx\n\t"
					"jne NOT_SUPPORT_AVX2\n\t"
					"mov $7,%%eax\n\t"
					"mov $0,%%ecx\n\t"
					"cpuid\n\t"
					"and $0x20,%%ebx\n\t"
					"cmp $0x20,%%ebx\n\t"
					"jne NOT_SUPPORT_AVX2\n\t"
					"mov $0,%%ecx\n\t"
					"xgetbv\n\t"
					"and $0x6,%%eax\n\t"
					"cmp $0x6,%%eax\n\t"
					"jne NOT_SUPPORT_AVX2\n\t"
					"mov $1,%0\n\t"
					"jmp RSCS_AVX2\n\t"
					"NOT_SUPPORT_AVX2:\n\t"
					"mov $0,%0\n\t"
					"RSCS_AVX2:NOP"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

检查是否支持AVX512

操作流程:

  1. 检查CPUID是否支持;
  2. 执行CPUID;
  3. 检查ebx中的bit 16是否为1(表示支持AVX512);
  4. ecx中的bit 27是否为1(表示 XGETBV 指令可以使用);
  5. 执行XGETBV,读取XCR0;
  6. 检查 XCR0[7:5] = ‘111b’ (表示操作系统开启了 OPMASK状态、ZMM0-ZMM15的高256-bit 和 ZMM16-ZMM31状态管理功能);
  7. 检查 XCR0[2:1] = ‘11b’ (表示操作系统支持XMM、YMM和ZMM的状态管理)。
    在这里插入图片描述

代码如下:

uint8_tsupports_avx512()
{
	uint64_trc=0;
	
	__asm__volatile("mov $1,%%eax\n\t"
					"cpuid\n\t"
					"and $0x08000000,%%ecx\n\t"
					"cmp $0x08000000,%%ecx\n\t"
					"jne NOT_SUPPORT_AVX512\n\t"
					"mov $7,%%eax\n\t"
					"mov $0,%%ecx\n\t"
					"cpuid\n\t"
					"and $0x10000,%%ebx\n\t"
					"cmp $0x10000,%%ebx\n\t"
					"jne NOT_SUPPORT_AVX512\n\t"
					"mov $0,%%ecx\n\t"
					"xgetbv\n\t"
					"and $0xE6,%%eax\n\t"
					"cmp $0xE6,%%eax\n\t"
					"jne NOT_SUPPORT_AVX512\n\t"
					"mov $1,%0\n\t"
					"jmp RSCS_AVX512\n\t"
					"NOT_SUPPORT_AVX512:\n\t"
					"mov $0,%0\n\t"
					"RSCS_AVX512:NOP"
					:"=rm"(rc)
					:
					:"cc"
					);
	
	return(uint8_t)rc;
}

下面是在我的至强10代上运行的结果

int
main(int argc, char *argv[])
{
    printf("support cpuid %d\n",supports_cpuid());
    printf("support mmx %d\n",supports_mmx());
    printf("support sse %d\n",supports_sse());
    printf("support sse2 %d\n",supports_sse2());
    printf("support sse3 %d\n",supports_sse3());
    printf("support sse4.1 %d\n",supports_sse4_1());
    printf("support sse4.2 %d\n",supports_sse4_2());
    printf("support avx %d\n",supports_avx());
    printf("support avx2 %d\n",supports_avx2());
    printf("support avx512 %d\n",supports_avx512());

    return 0;
}

在这里插入图片描述



这里是从善若水的博客,感谢您的阅读📕📕📕


在这里插入图片描述


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从善若水

原创不易,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值