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

Posted 从善若水

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HPCIntel 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;



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



以上是关于HPCIntel SIMD技术——如何用code检查你的CPU支持哪些指令集?的主要内容,如果未能解决你的问题,请参考以下文章

如何用命令行让vscode打开文件

如何用visual studio code调试javascript

如何用分类帐签署比特币 psbt?

如何用Code Blocks创建C++控制台项目

如何用vs code调试运行c语言程序

你如何用 jQuery 检测你何时接近屏幕底部? [复制]