我正在尝试使用ASM检测java同步块。问题是在检测之后,同步块的执行时间需要更多的时间。在Linux机器上,它从2毫秒增加到200毫秒。
我通过识别MonitorEnter和MonitorExit操作码来实现这一点。
我试着在三个层次上进行测试: 1.在MonitorEnter之前2.在MonitorEnter之后3.在MonitorExit之前。1和3一起工作很好,但当我使用2时,执行时间会急剧增加。
即使我们检测另一条SOP语句,这条语句只打算执行一次,它也会提供更高的值。下面是示例代码(质数,10个循环):
for(int w=0;w<10;w++){
synchronized(s){
long t1 = System.currentTimeMillis();
long num = 2000;
for (long i = 1; i < num; i++) {
long p = i;
int j;
for (j = 2; j < p; j++) {
long n = p % i;
}
}
long t2 = System.currentTimeMillis();
System.out.println("Time>>>>>>>>>>>> " + (t2-t1) );
}
这里的插装代码(这里的System.currentMilliSeconds()给出了插装发生的时间,它不是执行时间的度量,执行时间来自obove SOP语句):
public void visitInsn(int opcode)
{
switch(opcode)
{
// Scenario 1
case 194:
visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io /PrintStream;");
visitLdcInsn("TIME Arrive: "+System.currentTimeMillis());
visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
break;
// scenario 3
case 195:
visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
visitLdcInsn("TIME exit : "+System.currentTimeMillis());
visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
break;
}
super.visitInsn(opcode);
// scenario 2
if(opcode==194)
{
visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
visitLdcInsn("TIME enter: "+System.currentTimeMillis());
visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
}
}
我不能找到它发生的原因和如何纠正它。
提前谢谢。
发布于 2013-11-26 13:23:17
原因在于用于运行代码的JVM的内部结构。我假设这是一个HotSpot JVM,但是下面的答案对于大多数其他实现都是同样正确的。
如果您触发以下代码:
int result = 0;
for(int i = 0; i < 1000; i++) {
result += i;
}
这将由Java编译器直接转换成Java字节码,但是在运行时,JVM会很容易地看到这段代码什么也没做。执行此代码不会对外部(应用程序)世界产生任何影响,那么为什么JVM要执行它呢?这正是编译器优化为您做的事情。
但是,如果您触发以下代码:
int result = 0;
for(int i = 0; i < 1000; i++) {
System.out.println(result);
}
Java运行时不能再优化您的代码。整个循环必须始终运行,因为System.out.println(int)
方法总是在做一些实际的事情,这样代码的运行速度就会变慢。
现在让我们看一下你的例子。在您的第一个示例中,您基本上编写了以下代码:
synchronized(s) {
// do nothing useful
}
Java运行时可以很容易地删除整个代码块。这意味着:将不会有同步!在第二个示例中,您将编写以下代码:
synchronized(s) {
long t1 = System.currentTimeMillis();
// do nothing useful
long t2 = System.currentTimeMillis();
System.out.println("Time>>>>>>>>>>>> " + (t2-t1));
}
这意味着有效的代码可能如下所示:
synchronized(s) {
long t1 = System.currentTimeMillis();
long t2 = System.currentTimeMillis();
System.out.println("Time>>>>>>>>>>>> " + (t2-t1));
}
这里重要的是,优化后的代码将被有效地同步,这是在执行时间方面的一个重要区别。基本上,您正在测量同步某些内容所花费的时间(如果JVM意识到s
没有锁定在您的代码中的其他位置,那么在几次运行后可能会优化掉这个时间)(流行语:临时优化,如果将来加载的代码也将在s
上同步,则可能会取消优化)。
你真应该读一读这篇文章:
例如,您的测试错过了一次预热,因此您还在测量JVM将用于字节码到机器码优化的时间。
顺便说一句:在String
上同步几乎总是一个坏主意。您的字符串可能是也可能不是interned,这意味着您不能绝对确定它们的身份。这意味着,同步可能起作用,也可能不起作用,您甚至可能对代码的其他部分进行同步。
https://stackoverflow.com/questions/20193548
复制相似问题