原文https://jcdav.is/2016/09/01/How-the-JVM-compares-your-strings/

魔幻的String.compareTo

我们之前可能已经见过Java的String的比较方法,它会找出第一个不同的字符之间的距离,没找到不同,就返回较两个字符串长度之差

public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value; int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}

但是你知道除了上面的实现外还有第二种秘密实现吗?String.compareTo是少数非常重要的方法之一,为此虚拟机工程师专门为它手写了汇编风格的代码(译注:这些代码会被汇编器转换为机器代码,所以实际上是指用汇编风格写机器代码

# {method} 'compare' '(Ljava/lang/String;Ljava/lang/String;)I' in 'Test'
# parm0: rsi:rsi = 'java/lang/String'
# parm1: rdx:rdx = 'java/lang/String'
# [sp+0x20] (sp of caller)
7fe3ed1159a0: mov %eax,-0x14000(%rsp)
7fe3ed1159a7: push %rbp
7fe3ed1159a8: sub $0x10,%rsp
7fe3ed1159ac: mov 0x10(%rsi),%rdi
7fe3ed1159b0: mov 0x10(%rdx),%r10
7fe3ed1159b4: mov %r10,%rsi
7fe3ed1159b7: add $0x18,%rsi
7fe3ed1159bb: mov 0x10(%r10),%edx
7fe3ed1159bf: mov 0x10(%rdi),%ecx
7fe3ed1159c2: add $0x18,%rdi
7fe3ed1159c6: mov %ecx,%eax
7fe3ed1159c8: sub %edx,%ecx
7fe3ed1159ca: push %rcx
7fe3ed1159cb: cmovle %eax,%edx
7fe3ed1159ce: test %edx,%edx
7fe3ed1159d0: je 0x00007fe3ed115a6f
7fe3ed1159d6: movzwl (%rdi),%eax
7fe3ed1159d9: movzwl (%rsi),%ecx
7fe3ed1159dc: sub %ecx,%eax
7fe3ed1159de: jne 0x00007fe3ed115a72
7fe3ed1159e4: cmp $0x1,%edx
7fe3ed1159e7: je 0x00007fe3ed115a6f
7fe3ed1159ed: cmp %rsi,%rdi
7fe3ed1159f0: je 0x00007fe3ed115a6f
7fe3ed1159f6: mov %edx,%eax
7fe3ed1159f8: and $0xfffffff8,%edx
7fe3ed1159fb: je 0x00007fe3ed115a4f
7fe3ed1159fd: lea (%rdi,%rax,2),%rdi
7fe3ed115a01: lea (%rsi,%rax,2),%rsi
7fe3ed115a05: neg %rax
7fe3ed115a08: vmovdqu (%rdi,%rax,2),%xmm0
7fe3ed115a0d: vpcmpestri $0x19,(%rsi,%rax,2),%xmm0
7fe3ed115a14: jb 0x00007fe3ed115a40
7fe3ed115a16: add $0x8,%rax
7fe3ed115a1a: sub $0x8,%rdx
7fe3ed115a1e: jne 0x00007fe3ed115a08
7fe3ed115a20: test %rax,%rax
7fe3ed115a23: je 0x00007fe3ed115a6f
7fe3ed115a25: mov $0x8,%edx
7fe3ed115a2a: mov $0x8,%eax
7fe3ed115a2f: neg %rax
7fe3ed115a32: vmovdqu (%rdi,%rax,2),%xmm0
7fe3ed115a37: vpcmpestri $0x19,(%rsi,%rax,2),%xmm0
7fe3ed115a3e: jae 0x00007fe3ed115a6f
7fe3ed115a40: add %rax,%rcx
7fe3ed115a43: movzwl (%rdi,%rcx,2),%eax
7fe3ed115a47: movzwl (%rsi,%rcx,2),%edx
7fe3ed115a4b: sub %edx,%eax
7fe3ed115a4d: jmp 0x00007fe3ed115a72
7fe3ed115a4f: mov %eax,%edx
7fe3ed115a51: lea (%rdi,%rdx,2),%rdi
7fe3ed115a55: lea (%rsi,%rdx,2),%rsi
7fe3ed115a59: dec %edx
7fe3ed115a5b: neg %rdx
7fe3ed115a5e: movzwl (%rdi,%rdx,2),%eax
7fe3ed115a62: movzwl (%rsi,%rdx,2),%ecx
7fe3ed115a66: sub %ecx,%eax
7fe3ed115a68: jne 0x00007fe3ed115a72
7fe3ed115a6a: inc %rdx
7fe3ed115a6d: jne 0x00007fe3ed115a5e
7fe3ed115a6f: pop %rax
7fe3ed115a70: jmp 0x00007fe3ed115a73
7fe3ed115a72: pop %rcx
7fe3ed115a73: add $0x10,%rsp
7fe3ed115a77: pop %rbp
7fe3ed115a78: test %eax,0x17ed6582(%rip)
7fe3ed115a7e: retq

上面的代码由macroAssembler_x86.cppMacroAssembler::string_compare生成,里面有详细的注释。值得注意的是其实如果CPU支持AVX256指令集,它还有一个更魔幻的版本,不过这里不会介绍,只关注上面的实现。

PCMPESTRI是什么

pcmpestri是SSE4.2中引入的指令,属于pcmpxstrx向量化字符串比较指令家族。它通过一个控制字节(Control byte)复杂的功能,由于它们很复杂,x86指令集手册专门用一个小节来描述它,为了易于理解甚至还提供了一个flow图

看起来就像是把C语言代码放到CISC指令集里面一样!

控制字节的每个bit的功能如下:

-------0b 128-bit sources treated as 16 packed bytes.
-------1b 128-bit sources treated as 8 packed words.
------0-b Packed bytes/words are unsigned.
------1-b Packed bytes/words are signed.
----00--b Mode is equal any.
----01--b Mode is ranges.
----10--b Mode is equal each.
----11--b Mode is equal ordered.
---0----b IntRes1 is unmodified.
---1----b IntRes1 is negated (1’s complement).
--0-----b Negation of IntRes1 is for all 16 (8) bits.
--1-----b Negation of IntRes1 is masked by reg/mem validity.
-0------b Index of the least significant, set, bit is used
(regardless of corresponding input element validity).
IntRes2 is returned in least significant bits of XMM0.
-1------b Index of the most significant, set, bit is used
(regardless of corresponding input element validity).
Each bit of IntRes2 is expanded to byte/word.
0-------b This bit currently has no defined effect, should be 0.
1-------b This bit currently has no defined effect, should be 0.

(如果想要深入了解,可以参见Intel Instruction Set Reference Section 4.1)

compareTo使用0x19(译注:'0b11001'),即对每8个packed words使用equal each模式(逐个相等比较)比较,结果取反。这个怪物指令使用4个寄存器作为输入:两个字符串作为参数,加上%rax%rdx指定它们的长度( PCMPESTRI中的E表示显示指定长度——与之相对的pcmpistri和pcmpistrm表示用null作为结尾符,即不显示指定长度)。结果(IntRes2)会放到%ecx。有时候这些不够的情况下pcmpxstrx家族的指令还会设置一些flag:

CFlag – Reset if IntRes2 is equal to zero, set otherwise
ZFlag – Set if absolute-value of EDX is < 16 (8), reset otherwise
SFlag – Set if absolute-value of EAX is < 16 (8), reset otherwise
OFlag – IntRes2[0]
AFlag – Reset
PFlag – Reset

不过这些都不在我们的讨论范围内,让我们仔细看看循环里面的代码,以及一些初始化动作

7fe3ed1159f6: mov    %edx,%eax
7fe3ed1159f8: and $0xfffffff8,%edx
7fe3ed1159fd: lea (%rdi,%rax,2),%rdi
7fe3ed115a01: lea (%rsi,%rax,2),%rsi
7fe3ed115a05: neg %rax
7fe3ed115a08: vmovdqu (%rdi,%rax,2),%xmm0
7fe3ed115a0d: vpcmpestri $0x19,(%rsi,%rax,2),%xmm0
7fe3ed115a14: jb 0x00007fe3ed115a40
7fe3ed115a16: add $0x8,%rax
7fe3ed115a1a: sub $0x8,%rdx
7fe3ed115a1e: jne 0x00007fe3ed115a08

%rax是较短字符串长度,%rdx~0x7求与 (即最大循环次数的8倍)。然后它会比较指向两个字符串数组(%rsi%rdi)的指针,由于循环前对%rax取反,所以循环实际上是反向进行的。

它加载第一个字符串的8个字符到%xmm0寄存器,然后与第二个字符串的8个字符比较,如果CFlag设置了就跳出(即不同的字符已经找到,下标在%ecx中设置了),然后比较两个字符串的长度寄存器,并检测是否是最后一次迭代(即%rdx为0了)。但是一个负数怎么可能是正确的长度?额,忘记说了,pcmpestri使用长度的绝对值。

在循环之后,还有一个fallthrough分支,如果最短字符串剩下的字符不能被8整除了,那就使用这个分支处理剩下的字符,还有一个final分支,用来处理一个字符串是另一个的子字符串或者完全相同字符串的情况。

更合适的乐趣

如果上面对你来说不是很复杂,那么可以看看更魔幻的indexOf实现(有两个版本,取决于待匹配字符串的长度),它使用控制字节0x0d,即equal ordered模式进行匹配。

「译」JVM是如何使用那些你从未听过的x86魔幻指令实现String.compareTo的的更多相关文章

  1. jvm系列(十):如何优化Java GC「译」

    本文由CrowHawk翻译,是Java GC调优的经典佳作. 本文翻译自Sangmin Lee发表在Cubrid上的"Become a Java GC Expert"系列文章的第三 ...

  2. jvm系列(七):如何优化Java GC「译」

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...

  3. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

  4. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  5. 「译」JavaScript 的怪癖 1:隐式类型转换

    原文:JavaScript quirk 1: implicit conversion of values 译文:「译」JavaScript 的怪癖 1:隐式类型转换 译者:justjavac 零:提要 ...

  6. iOS 9,为前端世界都带来了些什么?「译」 - 高棋的博客

    2015 年 9 月,Apple 重磅发布了全新的 iPhone 6s/6s Plus.iPad Pro 与全新的操作系统 watchOS 2 与 tvOS 9(是的,这货居然是第 9 版),加上已经 ...

  7. 「译」forEach循环中你不知道的3件事

    前言 本文925字,阅读大约需要7分钟. 总括: forEach循环中你不知道的3件事. 原文地址:3 things you didn't know about the forEach loop in ...

  8. 「译」JUnit 5 系列:架构体系

    原文地址:http://blog.codefx.org/design/architecture/junit-5-architecture/ 原文日期:29, Mar, 2016 译文首发:Linesh ...

  9. 「译」Graal JIT编译器是如何工作的

    原文Understanding How Graal Works - a Java JIT Compiler Written in Java,讲了jvmci和ideal graph的基本概念以及一些优化 ...

随机推荐

  1. JS入门系列(2)-原型-实例属性

    下面的例子中,在构造器内部定义了like,然后再原型上也定义了like.通过下面的测试表明: 在构造器内部创建的实例方法会阻挡原型上定义的同名方法 初始化操作的优先级如下: 首先,通过原型给对象实例添 ...

  2. 同步工具类—— CountDownLatch

    本博客系列是学习并发编程过程中的记录总结.由于文章比较多,写的时间也比较散,所以我整理了个目录贴(传送门),方便查阅. 并发编程系列博客传送门 CountDownLatch简介 CountDownLa ...

  3. Alpha-release 总结

    因组员一周来事情较多,因此博客更新停滞了一个星期.这周我们已经开始了第二个release的相关工作,首先先对上一个release的工作进行简短总结. 团队在上个星期进行了alpha-release版本 ...

  4. G - Greg and Array CodeForces - 296C 差分+线段树

    题目大意:输入n,m,k.n个数,m个区间更新标记为1~m.n次操作,每次操作有两个数x,y表示执行第x~y个区间更新. 题解:通过差分来表示某个区间更新操作执行的次数.然后用线段树来更新区间. #i ...

  5. 068.Python框架Django之DRF视图集使用

    一 视图集与路由的使用 使用视图集ViewSet,可以将一系列逻辑相关的动作放到一个类中: list() 提供一组数据 retrieve() 提供单个数据 create() 创建数据 update() ...

  6. JavaScript思维导图很全(W3C上的!!!!很重要快来看!)

  7. SQL入门,就这么简单

    随着时代的发展,人类活动产生的信息越来越多,大家常说,现在这个时代是大数据时代.在这样一个前提下,数据的存储成为我们必须要认真对待和研究的问题了.SQL(Structured Query Langua ...

  8. 移动端Vue组件库-Vant学习

    全局引入 import Vant from 'vant'; //嫌麻烦就全部一次导出,虽然包会稍微有点大 import 'vant/lib/index.css'; //注意导入全局的这个css,否则布 ...

  9. lua实现游戏抽奖的几种方法

    ^_^内容原创,禁止转载  假设配置如下: local reward_pool = { {weight = , item = {, num = }}, {weight = , item = {, nu ...

  10. 关于flex弹性布局

    http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html