高性能string库-stringzilla

这段时间在优化服务耗时问题,其中perf打出来的热点显示,有一部分热点集中在string find. 由于之前看到sonic-cpp在使用simd加速string的一些操作,所以当时我想使用AVX2实现一版strstr来加速这个过程。但是在实现过程中,碰到一些问题,例如: 需要对输入进行对齐,类似是32、64字节,而输入字符串长度通常是不满足的,这样的话我们针对不同长度的字符串使用不同的指令,做起来还是有些繁琐的。幸运的是,我们找到了一个string库,做了相同的事情,而且支持多个指令集。通过引入该库,明显降低了在string操作上的热点,提升了响应速度。

1. stringzilla简介

stringzilla使用了SIMD做字符串常规操作的加速,支持多个指令集和多种语言。作者称It is up to 10x faster than the default and even other SIMD-accelerated string libraries in C, C++, Python, and other languages。以C/C++为例,支持find、rfind、hash、sort、copy、move等操作。header only,使用起来也很方便。

2. 常用操作

我们以C库为例,了解下其支持哪些常用操作。api集中在stringzilla.h文件头,只需要包含该文件即可。
首先定义了一个类似C++中string_view的sz_string_view_t,该类型是很多接口的入参类型。start指向的是数据首地址,length是string长度。

typedef struct sz_string_view_t {sz_cptr_t start;sz_size_t length;
} sz_string_view_t;

2.1 find

同多数接口一样,支持对称的操作,例如 rfind

  std::string input = "abcdef";std::string target = "cde";sz_string_view_t src = {input.c_str(), input.size()};sz_string_view_t tar = {target.c_str(), target.size()};auto pos = sz_find(src.start, src.length, tar.start, tar.length);if (pos == SZ_NULL) {std::cout << "not found" << std::endl;return 0;}std::cout << pos << std::endl;

2.2 equal

等价于memcmp
equal

2.3 hash

std::string input = "abcdef";
auto res = sz_hash(input.c_str(), input.size());

有了hash、equal之后,我们可以自定义hashmap. 例如下面这种unordered_set可以将sz_string_view_t作为其成员,避免了字符串拷贝。
hash

2.4 其他

  • copy
  • move
  • fill
  • 随机数

3. 源码分析

以下源码以avx2指令集为例。

3.1 预备知识

3.1.1 查看机器支持的CPU指令集

cat /proc/cpuinfo flags可以看到机器支持的指令集。
支持的指令集

3.1.2 avx2 的几个指令解释
  1. _mm256_set1_epi8, 使用8位的有符号整型填充256位
  char src = 'a';__m256i ret = _mm256_set1_epi8(src);char * p = (char *)&(ret);for(int idx = 0; idx < 32; ++idx){std::cout << p[idx] << std::endl;}
  1. _mm256_lddqu_si256, 从256位整数内存地址中加载256位
  char src = 'a';__m256i ret = _mm256_set1_epi8(src);__m256i tmp = _mm256_lddqu_si256(&ret);
  1. _mm256_cmpeq_epi8, 比较两个256位的整型向量中每8位是否相等。相同的位置结果设1,否则设0,所以返回值是无符号整型。
  __m256i src1 = _mm256_set_epi32('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h');__m256i src2 = _mm256_set_epi32('a', 'b', 'c', 'd', 'e', 'f', 'g', '+');__m256i result = _mm256_cmpeq_epi8(src1, src2);for (int idx = 0; idx < 32; ++idx) {printf("%u,", ((unsigned char *)&result)[idx]);}// 'h'=0x00000068  '+'=0x0000002b// result: 0,0xFF,... 只有最后8位不等,同时考虑到入寄存器的顺序。所以输出结果只有第一个8位为0,其他位均为1
  1. _mm256_movemask_epi8, 对256位整型向量,按照每8位提取一个最高有效位,返回一个32位int数。
  int mask = _mm256_movemask_epi8(result);printf("\n%x", mask);// 0xfffffffe, 对 3) 中result执行提取最该有效位。
  1. __builtin_ctz, 计算一个无符号整型从最低位开始第一个非0位的位置
  int m = __builtin_ctz(mask);printf("%u\n", m);// 0Xffff fffe, 输出的是1

3.2 sz_find_byte_avx2

在源串中查找某个字符出现的位置。
find_byte
首先是填充目标字符为32字节,256位向量。
然后,如果源串的长度大于32字节,则每次比较256位,通过_mm256_cmpeq_spi8逐字节进行比较,然后_mm256_movemask_epi8来提取最高位,也即是每一字节的匹配结果,然后求掩码的非0位即获得与目标字符匹配的位置。
其次,如果源串不足32位,则sz_find_byte_serial来实现查找。
在这里插入图片描述
该函数同样是先尽可能同时判断8字节,不足8字节的部分逐个比较。
(sz_u64_t)n[0] * 0x0101010101010101ull, 这一步是将单个字符扩充为8字节,64位。_sz_u64_each_byte_equal 是对64位实现按字节比较,返回对应掩码。
在这里插入图片描述
首先使用异或和取补操作,比较两个64位数,则相同部分是1,不同部分为0。接着按字节比较是否相等。vec.u64 & 0x7F7F7F7F7F7F7F7Full 先去除每个字节的最高位,vec.u64 & 0x8080808080808080ull提取每个字节的最高位。((vec.u64 & 0x7F7F7F7F7F7F7F7Full) + 0x0101010101010101ull), 这里是比较了每个字节的低7位是否相等,如果相等,加操作之后高位进1,变成0x80。同时如果高位为1,则单字节的结果为 0x80,表示单字节相等。
举个例子:

  left.u64 = 0x1111111111111111;right.u64 = 0x1122222222222222;sz_u64_vec_t result = _sz_u64_each_byte_equal(left, right);

result结果为: 0x00 00 00 00, 00 00 00 80
sz_u64_ctz(match_vec.u64) / 8 为 63/8=7;如果输入字符为0x11,则说明在源串的第7字节(最高字节)找到了该字符。

3.3 sz_find

搞清楚了sz_find_byte_avx2的实现方式,我们再来看看sz_find是怎么实现的。
find
首先是根据宏定义选择合适的指令集

SZ_USE_X86_AVX512: avx512指令集
SZ_USE_X86_AVX2: avx2指令集
SZ_USE_ARM_NEON: arm处理器的NEON指令集

以avx指令集为例。find
使用的是avx2 256位的寄存器,所以要求长度是大于32字节。首先是取目标字符串的首尾和中间三个位置的字符填充为256位,然后一次加载源字符串首尾和中间位置的32字节内容,接着分别进行比较,并获取掩码结果。掩码为1的部分表示源串与目标串首尾、中间字符相同的部分(这其实不太好理解,可以认为是在找源串中的一个位置,该位置很大可能是在结果集中的),得到这个位置后比较一次源串和目标串是否相等,相同直接返回,否则获取下一个掩码为1的位置。当目标串与源串长度差不够32位时,要使用其他的比较策略sz_find_serial。

在这里插入图片描述
sz_find_serial更为复杂,根据目标串长度,使用不同的查找函数。sz_find_byte_serial前面已经分析过了,我们来看一下_sz_find_2byte_serial。
在这里插入图片描述
双字节首先进行扩展(n_vec.u64 *= 0x0001000100010001ull), h_odd_vec,h_even_vec 分别存的是奇数起始、偶数起始向量。然后再64位上执行双字节比较(_sz_u64_each_2byte_equal, 与_sz_u64_each_byte_equal 相似的思路)。

总结

SIMD技术被应用于提高数据处理速度,但实现细节多、指令集不易理解,往往是服务优化的后续选择。而stringzilla 封装了多个指令集,提升了字符串处理速度,能够以较小改造成本提升服务表现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1541288.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【速成Redis】03 Redis 五大高级数据结构介绍及其常用命令 | 消息队列、地理空间、HyperLogLog、BitMap、BitField

前言&#xff1a; 上篇博客我们讲到redis五大基本数据类型&#xff08;也是就下图的第一列&#xff09;。 【速成Redis】02 Redis 五大基本数据类型常用命令-CSDN博客文章浏览阅读1k次&#xff0c;点赞24次&#xff0c;收藏10次。该篇适用于速成redis。本篇我们将讲解&#…

Innodb存储架构

Innodb整体存储架构 Innodb是一款兼顾性能及可靠性的存储引擎&#xff0c;主要分为内存存储结构和磁盘存储结构&#xff0c;二者分别扮演着提高性能和数据持久化的工作 内存结构中定义了缓冲池、变更缓冲区、日志缓冲区、自适应哈希四个缓冲区&#xff0c;它们均是为提升查询…

linux网络-----传输层

前言 一.传输层&#xff1a; 数据要交接应用层先通过传输层&#xff08;给哪个程序发数据&#xff09; 传输层作用&#xff1a;负责数据能够从发送端传输接收端。对于应用层来说有许多服务&#xff0c;传输层怎么知道把数据发给那个应用服务&#xff1f; 这时就有了端口号&am…

kubernetes中的认证授权

目录 一、kubernetes API访问控制 1、UserAccount与ServiceAccount &#xff08;1&#xff09;ServiceAccount &#xff08;2&#xff09;示例 二、认证&#xff08;在k8s中建立认证用户&#xff09; 1、创建UserAccount 2、RBAC&#xff08;Role Based Access Control&…

Redis——redispluspls库——通用命令以及String类型相关接口使用

文章目录 通用命令get&#xff0c;setkeys插入迭代器 expire和ttltype string 类型接口set和getset NX和XXmset 和 mgetgetrange 和 setrangeincr 和 decr 通用命令 get&#xff0c;set void get_set_test(sw::redis::Redis& redis){//bool set(const sw::redis::StringV…

Iterative Regularized Policy Optimization with Imperfect Demonstrations

ICML 2024 paper code Intro 利用基于次优专家数据的专家策略&#xff0c;通过policy constraint的形式引导智能体的在线优化&#xff0c;同时通过利用在线高质量数据扩展专家数据&#xff0c;并有监督得对专家策略进行矫正。二者交替优化实现目标策略的迭代更新 Method 上述…

51单片机-红外遥控器(NEC标准)-实验(红外遥控及调速电机)

作者&#xff1a;Whappy 时间&#xff1a;2024.9.20 总结一下&#xff01;基础实验到这儿里就圆满结束&#xff0c;历经25天&#xff0c;将51单片机学完并亲自手敲代码近5000行&#xff0c;在手敲代码过程中&#xff0c;明显感觉的看和敲&#xff0c;明显就是不同的感觉&…

STM32 通过 SPI 驱动 W25Q128

目录 一、STM32 SPI 框图1、通讯引脚2、时钟控制3、数据控制逻辑4、整体控制逻辑5、主模式收发流程及事件说明如下&#xff1a; 二、程序编写1、SPI 初始化2、W25Q128 驱动代码2.1 读写厂商 ID 和设备 ID2.2 读数据2.3 写使能/写禁止2.4 读/写状态寄存器2.5 擦除扇区2.6 擦除整…

基于SpringBoot的在线点餐系统【附源码】

​基于SpringBoot的高校社团管理系统&#xff08;源码L文说明文档&#xff09; 4 系统设计 4.1 系统概述 网上点餐系统的结构图4-1所示&#xff1a; 图4-1 系统结构 模块包括主界面&#xff0c;首页、个人中心、用户管理、美食店管理、美食分类管理、美食…

前端开发者必学:mo.js动画库

前端开发者必学&#xff1a;mo.js动画库 前言 在当今的网页设计中&#xff0c;动态效果和交互性是提升用户体验的关键因素。 mo.js&#xff0c;一个轻量级的 JavaScript 动画库&#xff0c;为前端开发者提供了一种简单而强大的方法来创建引人注目的动画效果。 本文将向您介…

Nature|PathChat:病理学多模态生成性AI助手的创新与应用|顶刊精析·24-09-21

小罗碎碎念 今日顶刊&#xff1a;Nature 这篇文章今年6月就发表了&#xff0c;当时我分析的时候&#xff0c;还是预印本&#xff0c;没有排版。今天第一篇推文介绍的是Faisal Mahmood &#xff0c;所以又把这篇文章拉出来详细分析一下。 作者角色作者姓名单位名称单位英文名称第…

目标拟合椭圆

对于目标区域&#xff0c;the ellipse fit is computing by matching second-order moments.

【C/C++】速通涉及string类的经典编程题

【C/C】速通涉及string类的经典编程题 一.字符串最后一个单词的长度代码实现&#xff1a;&#xff08;含注释&#xff09; 二.验证回文串解法一&#xff1a;代码实现&#xff1a;&#xff08;含注释&#xff09; 解法二&#xff1a;&#xff08;推荐&#xff09;1. 函数isalnum…

Linux文件IO(六)-多次打开同一个文件

大家看到这个小节标题可能会有疑问&#xff0c;同一个文件还能被多次打开&#xff1f;事实确实如此&#xff0c;同一个文件可以被多次打开&#xff0c;譬如在一个进程中多次打开同一个文件、在多个不同的进程中打开同一个文件&#xff0c;那么这些操作都是被允许的。本小节就来…

Linux软件包管理器、Linux开发工具、vim的配置等的介绍

文章目录 前言一、Linux软件包管理器yum二、Linux开发工具1. 命令模式2. 插入模式3. 底行模式4. 三种模式的切换5. 命令模式下的快捷键 三、vim的配置总结 前言 Linux软件包管理器、Linux开发工具、vim的配置等的介绍 一、Linux软件包管理器yum 关于rzsz 这个工具用于 window…

动手学深度学习(李沐)PyTorch 第 2 章 预备知识

2.1 数据操作 N维数组样例 N维数组是机器学习和神经网络的主要数据结构 张量表示一个由数值组成的数组&#xff0c;这个数组可能有多个维度。 具有一个轴的张量对应数学上的向量&#xff08;vector&#xff09;&#xff1b; 具有两个轴的张量对应数学上的矩阵&#xff08;…

MySQL高阶1843-可疑银行账户

目录 题目 准备数据 ​分析数据 实现 总结 题目 如果一个账户在 连续两个及以上 月份的 总收入 超过最大收入&#xff08;max_income&#xff09;&#xff0c;那么认为这个账户 可疑。 账户当月 总收入 是当月存入资金总数&#xff08;即 transactions 表中 type 字段的…

【Unity-UGUI组件拓展】| Image 组件拓展,支持FIlled和Slice功能并存

🎬【Unity-UGUI组件拓展】| Image 组件拓展,支持FIlled和Slice功能并存一、组件介绍二、组件拓展方法三、完整代码💯总结🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏…

C / C++的内存管理

前言 Hello&#xff0c;我又回来了&#xff0c;今天我们将继续学习C部分&#xff0c;今天我们将承接前面的知识&#xff0c;继续学习C的内存管理&#xff0c;今天的内容较为重要&#xff0c;所以我们废话不多说&#xff0c;我们还是按例三连上车&#xff0c;开始我们今天内容&…

Python中lambda表达式的使用——完整通透版

文章目录 一、前言二、 基本语法三、举个简单的例子&#xff1a;四、常见应用场景1. 用于排序函数sort() 方法简介lambda 表达式的作用详细解释进一步扩展总结 2、与 map、filter、reduce 等函数结合1、 map() 函数示例&#xff1a;将列表中的每个数字平方 2、 filter() 函数示…