分配延迟似乎很高,为什么?

时间:2009-11-17 20:36:02

标签: java latency allocation low-level jvm-hotspot

我有一个在低延迟环境中运行的(java)应用程序,它通常处理大约600微米(+/- 100)的指令。当然,随着我们进一步进入微秒空间,您看到的成本延迟会发生变化,现在我们已经注意到,2/3的时间用于分配2个核心域对象。

基准测试已将代码的违规部分与现有参考文件中的对象构造隔离开来,即基本上是一组引用(每个类中约15个)和一些新的列表,但请参阅下面的确切说明在这里测量什么。

每个人一直需要~100微米,这对我来说是莫名其妙的,我试图找出原因。一个快速的基准测试表明,一个类似大小的对象充满了大约需要2-3微米的新对象,显然这种基准测试充满了困难,但认为它可能有用作基线。

这里有2个问题

  • 如何调查这种行为?
  • 缓慢分配的解释是什么?

请注意,所涉及的硬件是Sun X4600上的Solaris 10 x86,带有8 *双核opteron @ 3.2GHz

我们看过的内容包括

  • 检查PrintTLAB统计信息,显示几个慢速分配,因此不存在争用。
  • PrintCompilation建议这些代码中的一个不是JIT友好的,虽然Solaris似乎在这里有一些不寻常的行为(即对现代的linux,没有类似于solaris10的linux,现在就可以替换) / LI>
  • LogCompilation ...有点难以解析,至少可以说这是一项持续的工作,到目前为止没有什么明显的。
  • JVM版本... 6u6和6u14一致,未尝试6u18或最新7

任何&所有的想法都赞赏

关于各种帖子的评论摘要,试图让事情更清楚

  • 我测量的成本是创建通过Builder构建的对象的总成本(如these之一)并且其私有构造函数几次调用新的ArrayList以及设置对现有的引用对象。测量的成本包括设置构建器的成本以及构建器到域对象的转换
  • 编译(通过热点)有明显的影响,但它仍然相对较慢(在这种情况下编译从100微升到~60)
  • 在我的天真基准测试中编译(通过热点)需要将分配时间从~2micros降低到~300ns
  • 延迟不会因年轻的收集算法(ParNew或平行清除)而异。

5 个答案:

答案 0 :(得分:3)

由于你的问题更多是关于如何调查问题而不是“我的问题是什么”,我会坚持使用一些工具来试用。

一个非常有用的工具,可以更好地了解正在发生的事情以及何时发生BTrace。它类似于DTrace,但是纯Java工具。在那个笔记上我假设你知道DTrace,如果不是,那也是有用的,如果不是钝的。这些将为您提供有关JVM和OS中发生的事情以及何时的可见性。

哦,在你的原帖中要澄清另一件事。你在跑什么收藏家?我假设您遇到高延迟问题,您正在使用像CMS这样的低暂停收集器。如果有,你试过任何调整吗?

答案 1 :(得分:3)

当您多次重复同一任务时,您的CPU往往会非常高效地运行。这是因为您的缓存未命中时间和CPU的预热并不是一个因素。你也可能没有考虑你的JVM温暖时间。

如果在JVM和/或CPU未预热时尝试相同的操作。你会得到一个非常不同的结果。

尝试在测试之间执行相同的操作25次(小于编译阈值)和睡眠(100)。您应该期望看到更高的时间,更接近您在实际应用中看到的内容。

您的应用的行为会有所不同,但说明我的观点。我发现等待IO可能比普通睡眠更具破坏性。

当您执行基准测试时,您应该尽量确保比较类似。

import java.io.*;
import java.util.Date;

/**
Cold JVM with a Hot CPU took 123 us average
Cold JVM with a Cold CPU took 403 us average
Cold JVM with a Hot CPU took 314 us average
Cold JVM with a Cold CPU took 510 us average
Cold JVM with a Hot CPU took 316 us average
Cold JVM with a Cold CPU took 514 us average
Cold JVM with a Hot CPU took 315 us average
Cold JVM with a Cold CPU took 545 us average
Cold JVM with a Hot CPU took 321 us average
Cold JVM with a Cold CPU took 542 us average
Hot JVM with a Hot CPU took 44 us average
Hot JVM with a Cold CPU took 111 us average
Hot JVM with a Hot CPU took 32 us average
Hot JVM with a Cold CPU took 96 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 80 us average
Hot JVM with a Hot CPU took 26 us average
Hot JVM with a Cold CPU took 90 us average
Hot JVM with a Hot CPU took 25 us average
Hot JVM with a Cold CPU took 98 us average
 */
public class HotColdBenchmark {
    public static void main(String... args) {
        // load all the classes.
        performTest(null, 25, false);
        for (int i = 0; i < 5; i++) {
            // still pretty cold
            performTest("Cold JVM with a Hot CPU", 25, false);
            // still pretty cold
            performTest("Cold JVM with a Cold CPU", 25, true);
        }

        // warmup the JVM
        performTest(null, 10000, false);
        for (int i = 0; i < 5; i++) {
            // warmed up.
            performTest("Hot JVM with a Hot CPU", 25, false);
            // bit cold
            performTest("Hot JVM with a Cold CPU", 25, true);
        }
    }

    public static long performTest(String report, int n, boolean sleep) {
        long time = 0;
        long ret = 0;
        for (int i = 0; i < n; i++) {
            long start = System.nanoTime();
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(new Date());
                oos.close();
                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
                Date d = (Date) ois.readObject();
                ret += d.getTime();
                time += System.nanoTime() - start;
                if (sleep) Thread.sleep(100);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        if (report != null) {
            System.out.printf("%s took %,d us average%n", report, time / n / 1000);
        }
        return ret;
    }
}

答案 2 :(得分:2)

内存分配可能会导致副作用。是否有可能内存分配导致堆被压缩?您是否查看了内存分配是否导致GC同时运行?

您是否单独计算了创建新ArrayLists所需的时间?

答案 3 :(得分:2)

对于在通用操作系统上运行的通用VM,即使使用如此强大的硬件,也没有希望获得微秒延迟保证。大容量吞吐量是您所希望的最佳选择。如果需要,可以切换到实时虚拟机(我说的是RTSJ和所有 ...)

......我的两分钱

答案 4 :(得分:2)

只是一些猜测:

我的理解是,Java VM以不同于长期对象的方式处理短期对象的内存。对我来说似乎是合理的,在一个对象从一个函数本地引用变为在全局堆中引用的那一点上将是一件大事。它现在必须由GC跟踪,而不是在功能退出时可用于清理。

或者可能是从一个引用到多个引用转到单个对象必须更改GC记帐。只要对象具有单个引用,就很容易清理。多个引用可以具有引用循环和/或GC可能必须在所有其他对象中搜索引用。