使用ASM进行仪表后,同步块需要更长的时间

时间:2013-11-25 12:49:00

标签: bytecode java-bytecode-asm bytecode-manipulation

我正在尝试使用ASM来监控java synchronized块。问题是在仪表之后,同步块的执行时间需要更多时间。在Linux机器上,它从2毫秒增加到200毫秒。

我通过识别MonitorEnter和MonitorExit操作码来实现这一点。

我尝试在MonitorEnter之前的MonitorEnter 2之前的三级1进行测试。在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) );
}

这里是instrumention的代码(这里System.currentMilliSeconds()给出了指令发生的时间,它没有执行时间的度量,执行时间来自oboot 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");

        }

        }

我无法找到它发生的原因以及如何纠正它。

提前致谢。

1 个答案:

答案 0 :(得分:1)

原因在于您用于运行代码的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)方法总是在做 real ,这样你的代码运行得会慢一些。

现在让我们来看看你的例子。在第一个示例中,您基本上编写了此代码:

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,这意味着你无法完全确定他们的身份。这意味着,同步可能会或可能不会起作用,甚至可能会导致代码其他部分的同步。