惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Cloudbric
Cloudbric
E
Exploit-DB.com RSS Feed
SecWiki News
SecWiki News
Forbes - Security
Forbes - Security
N
News | PayPal Newsroom
S
Security @ Cisco Blogs
Schneier on Security
Schneier on Security
V
V2EX - 技术
S
Secure Thoughts
W
WeLiveSecurity
Google DeepMind News
Google DeepMind News
C
CERT Recently Published Vulnerability Notes
NISL@THU
NISL@THU
S
Securelist
S
Security Archives - TechRepublic
Know Your Adversary
Know Your Adversary
V
Vulnerabilities – Threatpost
Security Latest
Security Latest
Recent Commits to openclaw:main
Recent Commits to openclaw:main
G
GRAHAM CLULEY
H
Hacker News: Front Page
Microsoft Azure Blog
Microsoft Azure Blog
I
Intezer
Google Online Security Blog
Google Online Security Blog
美团技术团队
阮一峰的网络日志
阮一峰的网络日志
T
The Exploit Database - CXSecurity.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Webroot Blog
Webroot Blog
Jina AI
Jina AI
Engineering at Meta
Engineering at Meta
P
Proofpoint News Feed
The Cloudflare Blog
I
InfoQ
L
LangChain Blog
U
Unit 42
P
Proofpoint News Feed
S
Schneier on Security
S
Security Affairs
Y
Y Combinator Blog
T
Tenable Blog
N
News and Events Feed by Topic
MyScale Blog
MyScale Blog
量子位
Google DeepMind News
Google DeepMind News
Cyberwarzone
Cyberwarzone
博客园 - 聂微东
D
Darknet – Hacking Tools, Hacker News & Cyber Security
GbyAI
GbyAI
AWS News Blog
AWS News Blog

风雪之隅

深入理解PHP7内核之OBJECT - 风雪之隅 PHP 8新特性之Attributes(注解) - 风雪之隅 博客迁移到腾讯云 - 风雪之隅 在Qcon 2015 北京上的演讲PPT - PHP7 Yar-2.1 新功能介绍 - 风雪之隅 Yaf and Phalcon, which is faster? HTTPOXY漏洞说明 - 风雪之隅 一个关于Zend O+的小分享 - 风雪之隅 在PHP中使用协程实现多任务调度 - 风雪之隅 Curl的毫秒超时的一个"Bug" - 风雪之隅 PHP8.0的Named Parameter - 风雪之隅 关于PHP,关于Realsee - 风雪之隅 PHP8新特性之match表达式 - 风雪之隅 PHP 8新特性之JIT简介 - 风雪之隅 使用SSE2指令高效实现strtolower - 风雪之隅 使用OSC52实现iTerm2远程pbcopy - 风雪之隅 Yaf 3.2 发布 - 风雪之隅 使用PHP Socket开发Yar TCP服务 - 风雪之隅 Yac 2.1 升级说明 - 风雪之隅 Yaf-3.1 10%性能提升版 - 风雪之隅 Yaconf-1.1 40%速度提升版 - 风雪之隅 PHP FFI详解 - 一种全新的PHP扩展方式 - 风雪之隅 Yaf_Loader重构测试 - 风雪之隅 PHP_INT_MIN 和 -9223372036854775808 - 风雪之隅 深入理解PHP7内核之FAST_ZPP - 风雪之隅 深入理解PHP7内核之HashTable - 风雪之隅 var_dump(1...9)输出什么? - 风雪之隅 使用内存硬盘(tmpfs)来加速你的网站 - 风雪之隅 print不是函数 - 风雪之隅 令人困惑的strtotime - 风雪之隅 深入理解PHP7内核之Reference - 风雪之隅 深入理解PHP7内核之zval - 风雪之隅 PHP的性能演进(从PHP5.0到PHP7.1的性能全评测) - 风雪之隅 让PHP7达到最高性能的几个Tips - 风雪之隅 写在PHP7发布之际的一些话 - 风雪之隅 让你的PHP7更快之Hugepage - 风雪之隅 让你的PHP7更快(GCC PGO) - 风雪之隅 Yaconf - 一个高性能的配置管理扩展 - 风雪之隅 记录一场没有胜利的局部战斗 - 风雪之隅 PHP7 VS HHVM (Wordpress) - 风雪之隅 GCC优化引起的一个"问题" - 风雪之隅 Weibo LAMP演变 - 6月在上海分享的PPT - 风雪之隅 一个小玩意PHP-Valgrind的介绍 - 风雪之隅 PHP浮点数的一个常见问题的解答 - 风雪之隅 Yac (Yet Another Cache) - 无锁共享内存Cache PDOStatement::bindParam的一个陷阱 - 风雪之隅 Mcrypt响应慢的一个原因 - 风雪之隅 一个程序员眼中的价值 - 风雪之隅 一个关于if else容易迷惑的问题 - 风雪之隅
使用SSE2指令集加速字符替换 - 风雪之隅
laruence · 2020-03-09 · via 风雪之隅

这个算是一个比较有用的性能优化技巧吧,主要是刚刚在重构Yaf_Loader的时候又用到,想着这个操作比较常见,就专门拎出来做个小分享。

我们在写代码的时候,在字符串处理的时候,可能会遇到这样的需求,就是把一个目标字符串中所有出现的某个字符a替换为另外一个字c.

比如对于Yaf_Loader中,在处理命名空间的类名的自动加载的时候,我需要把所有的\替换为_, 一般通常的写法会是:

char *pos = class_name;
size_t len = class_name_len;
while ((pos = memchr(pos, '\\', len - (pos - class_name)))) {
    *pos++ = '_';
}

而目前SIMD指令的支持已经非常普遍,尤其SSE2,基本当代的CPU都支持, 可以通过cat /proc/cpuinfo来看cpu支持的SIMD指令集:

cat /proc/cpuinfo | grep flags
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm ida arat xsaveopt pln pts dts tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms

可见我的这个CPU支持mmx, sse, sse2, ssse3, sse4.1, sse4.2,avx

回到整体,我们知道SIMD 128指令集可以一次处理16个字符,上面的代码可以通过如下代码来等效实现:

char *pos = class_name;
size_t len = class_name_len;

const __m128i slash = _mm_set1_epi8('\\');
const __m128i delta = _mm_set1_epi8('_' - '\\');

while (len >= 16) {
    __m128i op = _mm_loadu_si128((__m128i *)pos);
    __m128i eq = _mm_cmpeq_epi8(op, slash);
    if (_mm_movemask_epi8(eq)) {
        eq = _mm_and_si128(eq, delta);
        op = _mm_add_epi8(op, eq);
        _mm_storeu_si128((__m128i*)pos, op);
    }
    len -= 16;
    pos += 16;
}

if (len) {
  //用传统方式处理后续不满16个字符的部分
}

这里面核心的代码部分是:

1. __m128i eq = _mm_cmpeq_epi8(op, slash);
2. eq = _mm_and_si128(eq, delta);
3. op = _mm_add_epi8(op, eq);
4. _mm_storeu_si128((__m128i*)pos, op);

我来一行一行解释,假设我们现在要处理的字符串是"G\Namespace\package\classname:

  • 第一行:拿16个字符和字符'\\'做比较,如果某位相等,则16位结果中对应的byte就为0xff(-1), 否则就为0, 那么对于:
    G\Namespace\pack
    

    来说,会得到结果:

    0 -1 000000000 -1 0000
    
  • 第二行: 如果对比的结果不全为零的话,就进行到这行,这行的核心思想是因为ascii码’_’(95)和‘\\‘(92)之间差了3,所以我们通过and指令得到如下结果:
       0 -1 000000000 -1 0000
    &  3  3 333333333  3 3333
    -------------------------
       0  3 000000000  3 0000
    
  • 第三行: 我们把刚刚的delta结果加会到原始字符串中去,也就是:
       G \ Namespace \ pack
    +  0 3 000000000 3 0000
    ----------------------
       G _ Namespace _ pack
    
  • 第四行:把结果写回内存

这样以来,我就可以用一条指令同时检测16个字符,效率会大大提升。我们来做个简单的测试,测试脚本在这里:replace_chr.c,
下载下来以后,用-O2编译,在我的开发机上跑的结果是(结果会根据字符串中出现’\\’的位置和数量不同而有些许差异):

| Length |  Nomal |  SSE2  |  RAT |
-----------------------------------
|      4 |   5259 |   5397 |   3% |
|      8 |   2847 |   3045 |   7% |
|     16 |   1752 |    750 | -57% |
|     32 |   1557 |    843 | -46% |
|     64 |   1212 |    672 | -45% |
|    128 |   1149 |    594 | -48% |
|    256 |   1005 |    480 | -52% |
|    512 |    561 |    330 | -41% |

从结果上可以看到,在字符串长度小于16的时候,SSE2版本的速度略逊于普通版本,但是当字符串长度大于16以后,SSE2的版本的优势就非常明显了。

好了,最后要总结下,与其说是针对这个特定的字符替换的问题,我其实更主要对是想分享这种使用SIMD解决问题的思路,如何把类似的问题抽象为这样的”批量操作”。比如在之前在开发PHP7的时候,我也为PHP7引入过采用SIMD指令来实现快速的base64_encode/decode函数,这个性能提升很明显,因为被操作的字符串一般都很长,有兴趣的可以参看Base64 Encode with SSSE3 , 以后我有空了也可以分享下那个例子是怎么做的。

最后,关于SIMD指令集的速查,可以看这里:Intel Intrinsics Guide.