这篇文章也是个回帖,继续用来偷懒发blog
=====================================================
一位朋友给了下面这段代码(在他给的代码中略作修改,避免了引入Random类、Integer装箱类导致不必要的因素),提出了2个问题:1.for (int i = 0, n = list.size(); i < n; i++)的写法是否会比for (int i = 0; i < list.size(); i++)更快?2.为何这段代码在Server VM下测出来的速度比Client VM还慢?
public class Client1 {
public static void main(String[] args) {
List<Object> list = new ArrayList<Object>();
Object obj = new Object();
// 填充数据
for (int i = 0; i < 200000; i++) {
list.add(obj);
}
long start;
start = System.nanoTime();
// 初始化时已经计算好条件
for (int i = 0, n = list.size(); i < n; i++) {
}
System.out.println("判断条件中计算:" + (System.nanoTime() - start) + " ns");
start = System.nanoTime();
// 在判断条件中计算
for (int i = 0; i < list.size(); i++) {
}
System.out.println("判断条件中计算:" + (System.nanoTime() - start) + " ns");
}
}
首先来看看在代码最终执行时,for (int i = 0, n = list.size(); i < n; i++)的写法是否会比for (int i = 0; i < list.size(); i++)更快,用于测试的虚拟机是:
D:\_DevSpace\jdk1.7.0\fastdebug\bin>java -version
java version "1.7.0-ea-fastdebug"
Java(TM) SE Runtime Environment (build 1.7.0-ea-fastdebug-b127)
Java HotSpot(TM) Client VM (build 20.0-b06-fastdebug, mixed mode)
调试参数就加了一个-XX:+PrintAssembly,用于输出JIT后的汇编代码,虚拟机默认是Client。
for (int i = 0, n = list.size(); i < n; i++)的循环体是
0x01fcd554: inc %edx ; OopMap{[60]=Oop off=245}
;*if_icmplt
; - Client1::main@63 (line 17)
0x01fcd555: test %eax,0x1b0100 ; {poll}
0x01fcd55b: cmp %eax,%edx
;; 124 branch [LT] [B5]
0x01fcd55d: jl 0x01fcd554 ;*if_icmplt
; - Client1::main@63 (line 17)
变量i放在edx中,变量n放在eax中,inc指令对应i++(其实被优化成++i了),test指令是在回边处进行safepoint轮询(safepoint是用于进入GC,停顿线程),cmp是比较n和i的值,jl就是当i<n的时候进行跳转,跳转的地址是回到inc指令。
for (int i = 0; i < list.size(); i++)的循环体是:
0x01b6d610: inc %esi
;; block B7 [110, 118]
0x01b6d611: mov %esi,0x50(%esp)
0x01b6d615: mov 0x3c(%esp),%esi
0x01b6d619: mov %esi,%ecx ;*invokeinterface size
; - Client1::main@113 (line 23)
0x01b6d61b: mov %esi,0x3c(%esp)
0x01b6d61f: nop
0x01b6d620: nop
0x01b6d621: nop
0x01b6d622: mov $0xffffffff,%eax ; {oop(NULL)}
0x01b6d627: call 0x01b2b210 ; OopMap{[60]=Oop off=460}
;*invokeinterface size
; - Client1::main@113 (line 23)
; {virtual_call}
0x01b6d62c: nop ; OopMap{[60]=Oop off=461}
;*if_icmplt
; - Client1::main@118 (line 23)
0x01b6d62d: test %eax,0x160100 ; {poll}
0x01b6d633: mov 0x50(%esp),%esi
0x01b6d637: cmp %eax,%esi
;; 224 branch [LT] [B8]
0x01b6d639: jl 0x01b6d610 ;*if_icmplt
; - Client1::main@118 (line 23)
可以看到,除了上面原有的几条指令外,确实还多了一次invokeinterface方法调用(这里发生了方法调用,但是基本上没有分派的开销,因为inline cache是能起作用的),执行的方法是size,方法接收者是list对象,除此之外,其他指令都和上面的循环体一致。所以至少在HotSpot Client VM中,第一种循环的写法是能提高性能的。
但是这个结论并不是所有情况都能成立,譬如这里把list对象从ArrayList换成一个普通数组,把list.size()换成list.length。那将可以观察到两种写法输出的循环体是完全一样的(都和前面第一段汇编的循环一样),因为虚拟机不能保证ArrayList的size()方法调用一次和调用N次是否会产生不同的影响,但是对数组的length属性则可以保证这一点。也就是for (int i = 0, n = list.length; i < n; i++)和for (int i = 0; i < list.length; i++)的性能是没有什么差别的。
再来看看ServerVM和ClientVM中这2段代码JIT后的差别,本想直接对比汇编代码,但ServerVM经过Reordering后,代码就完全混乱了,很难和前面的比较,不过我们还是可以注意到两者编译过程的不同:
这是Client VM的编译过程
VM option '+PrintCompilation'
169 1 java.lang.String::hashCode (67 bytes)
172 2 java.lang.String::charAt (33 bytes)
174 3 java.lang.String::indexOf (87 bytes)
179 4 java.lang.Object::<init> (1 bytes)
185 5 java.util.ArrayList::add (29 bytes)
185 6 java.util.ArrayList::ensureCapacityInternal (26 bytes)
186 1% Client1::main @ 21 (79 bytes)
这是Server VM的编译过程
VM option '+PrintCompilation'
203 1 java.lang.String::charAt (33 bytes)
218 2 java.util.ArrayList::add (29 bytes)
218 3 java.util.ArrayList::ensureCapacityInternal (26 bytes)
221 1% Client1::main @ 21 (79 bytes)
230 1% made not entrant Client1::main @ -2 (79 bytes)
231 2% Client1::main @ 51 (79 bytes)
233 2% made not entrant Client1::main @ -2 (79 bytes)
233 3% Client1::main @ 65 (79 bytes)
ServerVM中OSR编译发生了3次,丢弃了其中2次(made not entrant的输出),换句话说,就是main()的每个循环JIT编译器都要折腾一下子。当然这并不是ServerVM看起来比ClientVM看起来慢的唯一原因。ServerVM的优化目的是为了长期执行生成尽可能高度优化的执行代码,为此它会进行各种努力:譬如丢弃以前的编译成果、在解释器或者低级编译器(如果开启多层编译的话)收集性能信息等等,这些手段在代码实际执行时是必要和有效的,但是在microbenchmark中就会显得很多余并且有副作用。因此写microbenchmark来测试Java代码的性能,经常会出现结果失真。
分享到:
相关推荐
这个 Java 循环语句练习题文档旨在帮助初学者熟悉 Java 语言的循环语句,包括 for 循环、while 循环、do-while 循环等。通过练习题的解答,读者可以熟悉 Java 语言的基本语法和循环语句的使用方法。 一、for 循环...
PB脚本中SQL语句写法与SQL中语句写法对照 PB脚本中SQL语句写法与SQL中语句写法对照是非常重要的...PB脚本中SQL语句写法与SQL中语句写法对照是非常重要的知识点,它们之间的差异和相似之处都需要我们认真研究和学习。
SQL Server Native Client 10.0 是微软推出的一款专门用于与SQL Server 2008及后续版本交互的客户端库,它集成了ODBC(Open Database Connectivity)和OLE DB(Object Linking and Embedding, Database)接口。...
1. **性能优化**:SQL Server Native Client 10.0 针对SQL Server进行了优化,提供了快速的数据传输和查询执行。 2. **支持新特性**:包括对SQL Server 2008引入的新特性如FILESTREAM、透明数据加密(TDE)、Change ...
SQL Server Native Client 11(简称sqlncli_11)是Microsoft开发的一款用于与SQL Server交互的客户端库,尤其适用于需要高性能数据访问的应用程序。它整合了ODBC(Open Database Connectivity)和OLE DB(Object ...
综上所述,Microsoft SQL Server 2005 Native Client是一个全面的数据访问组件,为开发者提供了多种方式来连接和操作SQL Server,同时提供了高级功能和性能优化,确保了应用程序与数据库之间的高效通信。无论是在...
SQL循环语句主要包括WHILE循环和FOR循环,它们允许我们在满足特定条件时重复执行一段代码块。在PL/SQL和T-SQL中,还有BEGIN-END结构来定义代码块。 1. WHILE循环:在满足特定条件时反复执行代码块,直到条件不再...
Microsoft SQL Server 2008 Native Client是在Windows 10操作系统上使用的一种数据库连接组件,它为应用程序提供了与SQL Server交互的能力。这个组件是专为SQL Server设计的,旨在提高性能、安全性和兼容性,特别是...
SQL Server Native Client 10.0 是微软针对SQL Server数据库管理系统推出的一款客户端接口,主要用于在Windows平台上连接和操作SQL Server 2008及其R2版本。这个压缩包"SQL Server Native Client 10.0.zip"包含了...
SQL Server Native Client是微软开发的一款用于访问SQL Server数据库的客户端组件,它包含了ODBC(Open Database Connectivity)驱动程序和OLE DB提供程序。这个压缩包包含了9.0到11.0版本的32位和64位版本,适用于...
SSMS提供了一个图形界面,用于执行SQL语句、创建和修改数据库对象、监控服务器性能以及配置服务器和数据库设置。用户可以通过SSMS进行以下操作: - **连接到服务器**:输入服务器名称和身份验证信息,建立与SQL ...
它的客户端组件包括一系列实用程序和接口,使得用户能够远程连接到SQL Server服务器,执行SQL语句,管理数据库对象,如表、视图、存储过程等,以及进行数据备份和恢复操作。 1. **SQL Server Management Studio ...
### SQL语句实现跨SQL Server数据库操作实例 #### 背景介绍 在日常的数据库管理与开发工作中,经常会遇到需要在不同的SQL Server实例之间进行数据交换的情况。这些操作包括但不限于查询不同数据库中的数据、将数据...
Microsoft SQL Server(MSSQL)作为一款广泛使用的数据库管理系统,提供了丰富的工具和方法来监控其性能。本文将深入探讨MSSQL性能监控中的几个关键SQL语句,帮助数据库管理员(DBA)和开发者更好地理解和管理MSSQL...
6. **性能调优**:通过分析SQL语句的执行计划,可以识别索引缺失、统计信息不准确等问题,从而对数据库进行针对性的优化。 7. **安全性审计**:Profiler还可以用来监控数据库的安全事件,例如权限变更、登录失败等...
在IT行业中,数据库管理系统是核心组成部分,SQL Server和Oracle分别是微软和甲骨文公司推出的两款广泛应用的关系型数据库系统。在企业级应用中,有时需要在不同的数据库系统间进行数据迁移或兼容性处理,这就涉及到...
本压缩包文件“易语言源码循环语句的用法之速度比较.rar”主要探讨的是易语言中不同循环语句的性能差异,这对于优化代码和提高程序运行效率至关重要。 在易语言中,循环语句是控制程序流程的重要组成部分,用于重复...
本主题将深入探讨几种常见的循环语句,并对比它们在执行效率上的差异,这对于优化程序性能至关重要。易语言作为中国本土化的一种编程语言,同样支持多种循环结构,如“循环”,“直到”,“步进”等。以下我们将详细...
根据给定的文件标题、描述、标签以及部分内容,下面将详细介绍相关的SQL Server知识点: ### SQL语法 #### 创建数据文件 创建数据文件时,通常需要指定数据文件的逻辑名称、物理名称(包括完整的路径和文件扩展名...
7. **备份与恢复策略**:阐述了备份和恢复对性能的影响,包括不同类型的备份(完整、差异、日志)和恢复模式(简单、完整、大容量日志),以及如何平衡备份速度和数据恢复能力。 8. **索引维护与重建**:讨论了索引...