原文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. 一个hql 关键字member(非mysql)引起的 vo 数据 保存数据库错误

    2015-03-19 14:16:29,285 ERROR [Thread-3] (DAOHelper.java:312) - updateByEntityPK:com.agileeagle.dao. ...

  2. JAVA开发中相对路径,绝对路径全面总结

    JAVA开发中相对路径,绝对路径全面总结 博客分类: Java综合 JavaJSP应用服务器Servlet网络应用 1.基本概念的理解 绝对路径:绝对路径就是你的主页上的文件或目录在硬盘上真正的路径, ...

  3. 【python实现卷积神经网络】padding2D层实现

    代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...

  4. three.js - 一个javascript 3D代码库

    这个项目的目的是用最简单的开发模式创建一个轻量级的3 d代码库,这个js库提供了canvas,svg,css3d和webgl这四种渲染方式. 下载地址: 下载地址:https://github.com ...

  5. k3s-初体验

    k3s安装步骤 1.准备工作 关闭swap交换分区 swapoff -a 关闭防火墙 systemctl stop firewalld.service 2.下载启动k3s包 https://githu ...

  6. day28作业

    import os import uuid import pickle from conf import settings class School: def __init__(self,name,a ...

  7. L20 梯度下降、随机梯度下降和小批量梯度下降

    airfoil4755 下载 链接:https://pan.baidu.com/s/1YEtNjJ0_G9eeH6A6vHXhnA 提取码:dwjq 梯度下降 (Boyd & Vandenbe ...

  8. 74HC595芯片的特性及使用方法和点评

    一 它能干什么?   74HC595是一个8位串行输入.平行输出的位移缓存器:平行输出为三态输出.在SCK的上升沿,单行数据由SDL输人到内部的8位位移缓存器,并由Q7‘输出,而平行输出则是在LCK的 ...

  9. python基础--str.split

    string = 'This +is -a /string' process = string.split('-') process1 = string.split('-')[-1]#-1和-2可能存 ...

  10. Nginx知多少系列之(七)负载均衡策略

    目录 1.前言 2.安装 3.配置文件详解 4.工作原理 5.Linux下托管.NET Core项目 6.Linux下.NET Core项目负载均衡 7.负载均衡策略 8.加权轮询(round rob ...