SSE4.2 指令集内置函数来加速32位循环冗余校验(CRC-32)计算

1、_mm_crc32_u8 为什么比查表快?
_mm_crc32_u8 指令使用了 SIMD 技术(即单指令多数据流技术),可以同时处理多个字节的数据。这些字节被打包成 64 位整数,然后使用单条 _mm_crc32_u64 指令计算它们的 CRC-32 校验和。这种方式比查表法更快,因为它可以利用 CPU 的并行性,同时处理多个字节,从而减少了指令的数量。此外,由于使用了硬件实现的指令,因此 _mm_crc32_u8 指令的执行速度要比使用查表法的纯软件实现更快。 与之相比,查表法需要在每个字节上执行 8 次查表操作,每次操作需要从表中读取一个 32 位的值,然后进行异或和移位操作。这种方式需要更多的指令,会导致更多的数据访问和缓存操作,从而导致更慢的执行速度。 需要注意的是,使用 _mm_crc32_u8 指令计算 CRC-32 校验和需要满足一定的条件,即 CPU 必须支持 SSE4.2 指令集。如果 CPU 不支持 SSE4.2 指令集,那么使用查表法可能会更快。此外,如果数据的大小非常小,那么使用查表法可能会更快,因为使用 SIMD 指令的开销可能会超过它的优势。

2、代码中如何判断是否支持 SSE4.2指令集?
使用 __cpuid 指令来检查 CPU 是否支持 SSE4.2 指令集。该指令会返回 CPU 的一些信息,包括支持的指令集。具体来说,可以使用 __cpuid(1, eax, ebx, ecx, edx) 指令获取 CPU 的功能信息,并检查 ecx 寄存器是否包含 SSE4.2 的标志位。如果该标志位为 1,则表示 CPU 支持 SSE4.2 指令集。

#include <iostream>
#include <intrin.h>
bool has_sse42() {
  int cpu_info[4];
  __cpuid(cpu_info, 1);
  return (cpu_info[2] & (1 << 20)) != 0;
}
int main() {
  if (has_sse42()) {
    std::cout << "CPU supports SSE4.2" << std::endl;
  } else {
    std::cout << "CPU does not support SSE4.2" << std::endl;
  }
  return 0;
}

3、如何在cmakelists.txt中判断?
在 CMakeLists.txt 中,可以使用 CheckCXXSourceCompiles 命令来检查编译器是否支持 __cpuid 指令,进而检查 CPU 是否支持 SSE4.2 指令集。

cmake_minimum_required(VERSION 3.10)
project(MyProject)
include(CheckCXXSourceCompiles)
check_cxx_source_compiles("
#include <intrin.h>
bool has_sse42() {
  int cpu_info[4];
  __cpuid(cpu_info, 1);
  return (cpu_info[2] & (1 << 20)) != 0;
}
int main() {
  return has_sse42() ? 0 : 1;
}
" HAVE_SSE42)
if (HAVE_SSE42)
  message(STATUS "CPU supports SSE4.2")
else()
  message(STATUS "CPU does not support SSE4.2")
endif()

纯软件实现CRC32经常是借助于查表实现的(https://pycrc.org/一个可以生成CRC C语言计算代码的工具),当计算CRC32过于频繁时可通硬件指令优化以减少对CPU的占用。目前Intel支持的用于计算CRC的有CRC32和PCLMULQDQ两个指令。本文仅讨论使用CRC32指令的使用。CRC32指令计算的是iSCSI CRC,也就是生成多项式为0x11EDC6F41的32位CRC。

使用CRC32指令的方式有2种:一种是直接使用(内联)汇编代码;另一种是借助编译器intrinsics。本文介绍借助编译器intrinsics计算CRC32的过程。

1 使用CRC32指令之前必须检测处理器是否支持SSE4.2

可通过 (if CPUID.01H:ECX.SSE4_2[bit 20] = 1) 来判断。

1.1 使用汇编指令
int check_support_sse4_2() {
    int res=0;
    __asm__ __volatile__(
                        "movl $1,%%eax\n\t"
                        "cpuid\n\t"
                        "test $0x0100000,%%ecx\n\t"
                        "jz 1f\n\t"
                        "movl $1,%0\n\t"
                        "1:\n\t"
                        :"=m"(res)
                        :
                        :"eax","ebx","ecx","edx");
    return res;
}

1.2 利用gcc提供的cpuid.h

#include <cpuid.h>
#include <stdio.h>
 
void
main () {
  unsigned int eax, ebx, ecx, edx;
 
  __get_cpuid(1, &eax, &ebx, &ecx, &edx);
 
  if (ecx & bit_SSE4_2)
    printf ("SSE4.2 is supported\n");
 
  return;
}

2 使用compiler intrinsics(x86intrin.h) 计算CRC32c

unsigned int _mm_crc32_u8( unsigned int crc, unsigned char data )
unsigned int _mm_crc32_u16( unsigned int crc, unsigned short data )
unsigned int _mm_crc32_u32( unsigned int crc, unsigned int data )
unsinged __int64 _mm_crc32_u64( unsinged __int64 crc, unsigned __int64 data )

#ifdef __x86_64__
#define ALIGN_SIZE 8
#else
#define ALIGN_SIZE 4
#endif
#define ALIGN_MASK (ALIGN_SIZE - 1)
 
uint32_t extend(uint32_t init_crc, const char *data, size_t n) {
    uint32_t res = init_crc ^ 0xffffffff;
    size_t i;
#ifdef __x86_64__
    uint64_t *ptr_u64;
    uint64_t tmp;
#endif
    uint32_t *ptr_u32;
    uint16_t *ptr_u16;
    uint8_t *ptr_u8;
 
    // aligned to machine word's boundary
    for (i = 0; (i < n) && ((intptr_t)(data + i) & ALIGN_MASK); ++i) {
        res = _mm_crc32_u8(res, data[i]);
    }
 
#ifdef __x86_64__
    tmp = res;
    while (n - i >= sizeof(uint64_t)) {
       ptr_u64 = (uint64_t *)&data[i];
       tmp = _mm_crc32_u64(tmp, *ptr_u64);
       i += sizeof(uint64_t); 
    }
    res = (uint32_t)tmp;
#endif
    while (n - i >= sizeof(uint32_t)) {
       ptr_u32 = (uint32_t *)&data[i];
       res = _mm_crc32_u32(res, *ptr_u32);
       i += sizeof(uint32_t); 
    }
    while (n - i >= sizeof(uint16_t)) {
       ptr_u16 = (uint16_t *)&data[i];
       res = _mm_crc32_u16(res, *ptr_u16);
       i += sizeof(uint16_t); 
    }
    while (n - i >= sizeof(uint8_t)) {
       ptr_u8 = (uint8_t *)&data[i];
       res = _mm_crc32_u8(res, *ptr_u8);
       i += sizeof(uint8_t); 
    }
 
    return res ^ 0xffffffff;
}
static inline uint32_t crc32c(const char *data, size_t n) {
    return extend(0, data, n);
}

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -msse4.2")

3 再优化
    其实还可以使用CRC32指令并行计算CRC32,具体见引用[2]。

4 引用

[1] Intel® 64 and IA-32 Architectures Software Developer’s Manual

[2]Choosing a CRC polynomial and associated method for Fast CRC Computation on Intel® Processors

[3] simd,http://dirlt.com/simd.html

[4]Professional Assembly Language

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中提到了MMX/SSE指令集的由来。MMX指令集使用8个64位寄存器MM0~MM7,并借用8个80位寄存器ST。而SSE架构是由MMX指令集发展而来的。引用中提到,在TensorFlow运行时,出现了一条信息,表示工作站支持SSE4.1指令集,但在编译时并没有加入对该指令集的支持。这些指令集可以加速CPU计算。引用中给出了在完成后执行CPU版本编译的命令,其中包括了SSE-SSE4指令集。 综上所述,SSE-SSE4指令集是一组指令集,它们是从MMX指令集发展而来的,并用于加速CPU计算。在TensorFlow中,可以通过编译时加入对SSE-SSE4指令集的支持来提高计算速度。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [(6.6)--MMX及SSE指令集1](https://download.csdn.net/download/weixin_35792236/86309458)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [重新编译TensorFlow1.4源代码支持SSE-AVX-FMA指令集 (Python3.5版本)](https://blog.csdn.net/keith_bb/article/details/79139482)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值