[讨论] 请问R大 有没有什么工具可以查看正在运行的类的c/汇编代码

jinnianshilongnian 2012-11-01
比如《Effective Java》这本书的第232页
引用
import java.util.concurrent.*;

public class StopThread {
    private static boolean stopRequested;

    public static void main(String[] args) throws Exception {
        Thread t = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
           }
        }); 
        t.start();
        TimeUnit.SECONDS.sleep(1); 
        stopRequested = true;
    }
}


当使用 java -server (Hotspot Server VM)启动时会发生”代码提升“ 造成活性失败,书上的解释是:
while (!stopRequested) 
   i++;
}

被提升为
if (!stopRequested) {
    while (true)
      i++;
}



想具体看看是不是这样的。麻烦指点下
caoxudong818 2012-11-01
我不是R大,我不会武功~~~~~


=================华丽丽的分割线======================

打印编译信息的参数是-XX:+PrintCompilation,但不包括编译后的代码。若要看编译后的代码,需要加参数-XX:+PrintAssembly,再加上HSDIS插件(组内分享就有)。

其他还有一些参数和插件使用信息,参见wiki

坐等R大补充。
jinnianshilongnian 2012-11-01
caoxudong818 写道
我不是R大,我不会武功~~~~~


=================华丽丽的分割线======================

打印编译信息的参数是-XX:+PrintCompilation,但不包括编译后的代码。若要看编译后的代码,需要加参数-XX:+PrintAssembly,再加上HSDIS插件(组内分享就有)。

其他还有一些参数和插件使用信息,参见wiki

坐等R大补充。

哈哈哈,好 我试试   好久木见你活动了
caoxudong818 2012-11-01
jinnianshilongnian 写道
caoxudong818 写道
我不是R大,我不会武功~~~~~


=================华丽丽的分割线======================

打印编译信息的参数是-XX:+PrintCompilation,但不包括编译后的代码。若要看编译后的代码,需要加参数-XX:+PrintAssembly,再加上HSDIS插件(组内分享就有)。

其他还有一些参数和插件使用信息,参见wiki

坐等R大补充。

哈哈哈,好 我试试   好久木见你活动了



老了,岁月不饶人啊。
jinnianshilongnian 2012-11-01
下载了
java version "1.6.0_25-ea-fastdebug
汇编出来了,不过看的头大,再补补汇编知识

现在两种情况(hotspot vm  -server)
while(!stopRequested) { //无限循环
                    i++;
        }

while(!stopRequested) {//在我的机器上循环到4000多over
                    i++;
                    System.out.println(i);
        }

回去仔细看下汇编 争取搞懂
RednaxelaFX 2012-11-01
看汇编的办法上面老曹说的就对了。在这个群组里之前有过好几帖问过这个的,其中我在这边有回复:http://hllvm.group.iteye.com/group/topic/21769

我现在手上有的、自己用的hsdis插件有hsdis-i386.dll、hsdis-i386.so、hsdis-amd64.so。本来想把Windows/x64的也build一个出来,但弄起来真麻烦,我在Ubuntu上装MinGW/x64的cross compiler总是不成功,总之就是怪怪的。

如果真的有人要hsdis-amd64.dll的话我可以去找找看有没有同事有现成的…

========================== 正题分界线 ==========================

为了回答您的问题翻出了《Effective Java, Second Edition》,原文版。这上下文是Item 66对吧?我这边看到的页码是260-261
这种技术文翻译成中文之后为啥就这么别扭呢…你引用的那段话都没错,原文就是那个意思。而且HotSpot Server Compiler确实做了书中所描述的那种优化。

如果是我要调查那个静态变量的读有没有被当作循环不变量提升到循环之外,我不会先从PrintAssembly看汇编来着手,而是会用更直接的方式去看C2(HotSpot Server Compiler)到底做了什么。

如果是我想验证某个优化是否真的在起作用,可能会找个fastdebug版的HotSpot VM,用 -XX:PrintIdealGraphLevel=2 -XX:PrintIdealGraphFile=ideal.xml 这样的参数来获取C2编译方法时内部数据结构的状态,然后用IdealGraphVisualizer(IGV)将输出可视化,观察某些优化是否发生了。

另外,-XX:+PrintOptoAssembly所输出的近似汇编的日志也比真的汇编包含更多上层信息,更便于理解。

实际操作了一下,结果贴在Github了:https://gist.github.com/3992456
包括-XX:PrintIdealGraphLevel=2输出的Ideal graph记录,以及-XX:+PrintOptoAssembly输出的日志。请参考。

请从前面的链接下载IdealGraphVisualizer,用它来打开我放在Github上的ideal.xml。可以看到这个日志记录了C2在编译StopThread$1.run()过程中若干个阶段的状态:


双击这个菜单里的阶段名就会在IGV的主界面显示出C2编译到这个阶段时的中间代码(Intermediate Representation)的图。C2所采用的IR是一种名为“Ideal”的静态单赋值(SSA)形式的程序依赖图(Program Dependence Graph)。它的设计理念虽然很简单,但实际编译过程中产生的图相当复杂,这边就不详细说了,大家有兴趣的话可以慢慢探索。

---------------------------------------------------------

如何探索呢?继续前面的例子,

先打开“After Parsing”这个阶段的图。C2编译一个Java方法时,需要先把字节码解析(parse)为C2的IR,然后才可以继续做分析和优化,最终生成代码。“After Parsing”就是C2刚完成parse过程,把这个方法完整的IR图建立好的时候;这个状态代表了C2对要编译的方法的初步认识。

从IGV右边的工具栏里勾选上“C2 Only Control Flow”:

这样可以把图里的数据流节点暂时过滤掉,只看控制流节点。通过控制流图我们可以比较轻松的把图里的各个基本块(basic block)与原本的Java代码对应起来,方便后续的理解。

每个节点代表一个“值”。节点上方的是输入,下方的是输出。
打开“C2 Basic Coloring”,可以看到紫色的边是数据依赖边,红色的是控制依赖边;有种特殊的数据依赖边被标记为绿色,那些是“memory”依赖。

这样在主界面里看到的图是:

(点击看大图)
这个控制流图里,每个大的浅蓝色方块就是一个“基本块”,每个方块的左上角注有IGV为它编的号(B1、B2那些就是)。
在同一个基本块内的节点之间只有数据依赖关系,或者说这些节点的控制依赖相同。

(插点题外话:前面也提到了,C2的Ideal graph是一种PDG,控制流图和数据流图是融合在同一层次上的。在这种图里其实没有显式的基本块,只有Region节点来充当基本块的头。IGV所画出来的基本块是从Ideal graph里重新计算出来的,而不是Ideal graph自身就显式记录的,这点需要明确一下。
大家有可能会发现IGV显示的图里,同一个方法的同一个基本块在不同阶段可能被标上了不同的编号。这正是因为IGV对每个阶段的图都重新计算了基本块的范围及编号。)

如果带上-XX:+PrintCompilation来跑这测试,会看到:
    903   1%      StopThread$1::run @ 2 (15 bytes)

这行日志里的百分号说明了这是一个OSR(on-stack replacement)编译。关于PrintCompilation日志格式的说明可以参考我以前写的文档。近期内我会把这个笔记搬到HotSpotInternals wiki上去。
这里我是用JDK 6 update 25 fastdebug版来跑的。请参考文档里对应版本的部分。

C2对OSR编译会有些特殊处理,例如说:编译出来的代码的入口点是在OSR入口点而不是原本方法最开头的地方;被编译的方法在OSR入口点之前的部分会被完全忽略掉,不进行编译,等等。
相应的,在这个例子里我们可以看到:
·B3是这次编译的实质入口,也就是OSR入口点,同时包含了循环条件判断的一份拷贝;
·B3有两个后继基本块,B2和B4。如果B3末尾的循环条件满足那么会跳到B2,否则跳到B4;
·B4是方法的末尾,也就是负责返回的那块;
·B2是一个用来标记循环起点的loop predicate的块,跟-XX:+UseLoopPredicate涉及的优化相关,这里可以先不管它的具体内容;
·B1的唯一前趋基本块就是B2,实际上也是跟loop predicate相关的优化,也暂时不管;
·B5是我们所关注的循环的主体,循环条件融合在了B5的末尾。

如果读了上面的文字觉得晕头转向,那改为用伪代码表示的话,就是:
B3:
  // OSR入口点要调用的一个辅助函数
  OSR_migration_end();
  // 循环条件:
  if stopRequested then
    goto B4 // 结束
  else
    goto B2 // 进入循环

B2:
  // loop predicate相关
  if loop predicate成立 then
    goto B5
  else
    goto B1

B1:
  // loop predicate失败,请求退回到解释器里继续执行,并且未来可能重新编译
  // 具体内容省略

B5:
  // 这是循环的主体
  i++
  // 循环条件:
  if stopRequested then
    goto B4 // 结束
  else
    goto B5 // 继续循环

B4:
  return

这样写容易理解些了不?

从IGV右侧工具栏的“Control Flow”一栏可以看到更抽象的控制流图:

留意到这图的B5右边有个“奇怪”的箭头。实际上这是一个从自己出发、指向自己的回环边,也就表现了B5包含有自己到自己的循环。

控制流弄清楚之后可以把“C2 Only Control Flow”的过滤去掉,重新观察完整的图来了解细节。
对细节感兴趣的同学可以在IGV的搜索框里搜“LoadUB”,

这是用来从内存读一个unsigned byte的节点,把它们提取出来:

就可以看到分别位于B3和B5的循环条件中对stopRequested的访问了。

通过观察“After Parsing”时的Ideal graph,我们可以知道编译至此循环体里还保留这那个循环条件。也就是说条件尚未被看作循环不变量提升到循环之外。

---------------------------------------------------------

然后我们再打开“After Parsing”之后的一个阶段,“Iter GVN 1”。
在这个图里搜“LoadUB”,会发现只有一个结果了:

留下的这个LoadUB是在B3里的;B5此时就变成只有:
B5:
  // 这里是循环的主体
  i++
  goto B5

也就是说此时C2已经把原本B5里对stopRequested的访问看作是跟B3的那个等价,于是就把B5的那个去除了。

在IGV里用图的diff也可以很方便的发现B5里的LoadUB被消除掉了。假设当前在看的图是“After Parsing”,那么在左侧的Outline窗口里:

选择“Differnce to current graph”,可以看到图之间的“差异”,像这样:

图里红色的节点就是被消除了的节点,虚线的边也是已不复存在的边。

B5里的LoadUB是在Iterative GVN(Global Value Numbering)阶段被削除的。由PhaseIterGVN::optimize()输出的-XX:+TraceIterativeGVN的日志可以更清楚的看到这点
< 		< 95	LoadUB	=== _ _ _  [[]]   [3400095]
> bool		 49	LoadUB	=== _  7  48  [[ 51  97 ]]  @precise klass StopThread: 0x00000000060c4968:Constant:exact+600 *, name=stopRequested, idx=4; #bool !orig=[95] !jvms: StopThread::access$000 @ bci:0 StopThread$1::run @ bci:2

这里第一行是已经被削除的节点的信息,第二行是替换它的节点的信息。
一个节点被削除的时候输入和输出边都会被切断,所以被削除的这个编号为95的LoadUB节点的输入变成了_ _ _(也就是3个输入边都是NULL),输出也空了;在被削除前95这个节点本来是:
95    LoadUB === _  76  48  [[ 97 ]]

正好在做IterativeGVN过程中,节点76被削除然后替换为节点7了:
< 		< 76	Phi	=== _ _ _  [[]]   [3200076]
> memory	 7	Parm	===  3  [[ 65  105  105  26  27  49  95  83 ]] Memory  Memory: @BotPTR *+bot, idx=Bot; !orig=[76] !jvms: StopThread$1::run @ bci:2

然后节点95就变成了:
95    LoadUB === _  7  48  [[ 97 ]]

这个节点的输入就跟节点49完全一样了。所以节点95接下来也就被GVN(全局值标号)看作跟节点49重复而削除掉了。

到此,无限循环就已经形成了。

---------------------------------------------------------

后面还有些进一步优化。最终生成的代码可以参考PrintOptoAssembly的输出
010   B1: #	B3 B2 <- BLOCK HEAD IS JUNK   Freq: 1
010   	pushq   rbp
	subq    rsp, #48	# Create frame
	nop	# nop for patch_verified_entry
016   	movl    RBX, [RDX]	# int
018   	movq    RCX, RDX	# spill
01b   	call_leaf,runtime  OSR_migration_end
        No JVM State Info
        # 
028   	movq    R10, precise klass StopThread: 0x0000000005fb9698:Constant:exact *	# ptr
032   	movzbl  R11, [R10 + #600 (32-bit)]	# ubyte ! Field StopThread.stopRequested
03a   	testl   R11, R11
03d   	jne,s   B3  P=0.000000 C=13654.000000
03d
03f   B2: #	B2 <- B1 B2  top-of-loop Freq: 1e-035
03f   	incl    RBX	# int
041   	testl   rax, [rip + #offset_to_poll_page]	# Safepoint: poll for GC        # StopThread$1::run @ bci:11  L[0]=_ L[1]=RBX
        # OopMap{off=65}
047   	jmp,s   B2
047
049   B3: #	N50 <- B1  Freq: 5e-007
049   	addq	rsp, 48	# Destroy frame
	popq	rbp
	testl	rax, [rip + #offset_to_poll_page]	# Safepoint: poll for GC
	
054   	ret
054

这里的基本块跟“After Parsing”时IGV标记出来的基本块的对应关系是:
After Parsing PrintOptoAssembly
B3 B1
B5 B2
B4 B3

用伪代码来表示PrintOptoAssembly的输出:
B1:
  create_stack_frame()
  OSR_migration_end()
  if stopRequested then
    goto B3
  else
    goto B2

B2:
  i++
  safepoint_poll()
  goto B2

B3:
  destroy_stack_frame()
  safepoint_poll()
  return

B2就是那个无限循环的本体了。

========================== 正题分界线 ==========================

例子就写到这里。还有人对细节感兴趣的话我再看看要不要补充点啥。
xiaoyu 2012-11-01
从汇编来看, 还真 hoisting, 这...真危险..

(哈哈, 对楼下R大的回复..哈哈...sorry, 我不是有意占楼的)
RednaxelaFX 2012-11-01
xiaoyu 写道
从跟汇编上来, 还真 hoisting, 这...真危险..

啊我还没回复完你就回复了…
嗯,是真有hoisting。所以必要的同步要自己注意好。
jinnianshilongnian 2012-11-01
while(!stopRequested) { //无限循环  说明这种提升了。
    i++;
}

while(!stopRequested) {//在我的机器上循环到4000多结束  说明这种没有提升。
    i++;
    System.out.println(i);
}

不管哪种情况,都得考虑同步/volatile

我晚上回去先看看 IGV的文档。
官网资料下载较慢 我把它传到我们服务器了,方便下载。
Visualization of Program Dependence Graphs.pdf
igv_latest.zip
hellhell 2012-11-02
R大,有个问题,请问你的IdealGraphVisualizer是如何配置的?我显示的node就一个光秃秃的名称,比如AddP什么,没有下面的详细信息。

看到选项里有个Node Text,请问这里应如何配置,谢谢了。
Global site tag (gtag.js) - Google Analytics