【问题标题】:Ways to improve performance consistency提高性能一致性的方法
【发布时间】:2011-12-19 15:48:19
【问题描述】:

在以下示例中,一个线程通过消费者正在接收的 ByteBuffer 发送“消息”。最佳性能非常好,但并不一致。

public class Main {
    public static void main(String... args) throws IOException {
        for (int i = 0; i < 10; i++)
            doTest();
    }

    public static void doTest() {
        final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(64 * 1024);
        final ByteBuffer readBuffer = writeBuffer.slice();
        final AtomicInteger readCount = new PaddedAtomicInteger();
        final AtomicInteger writeCount = new PaddedAtomicInteger();

        for(int i=0;i<3;i++)
            performTiming(writeBuffer, readBuffer, readCount, writeCount);
        System.out.println();
    }

    private static void performTiming(ByteBuffer writeBuffer, final ByteBuffer readBuffer, final AtomicInteger readCount, final AtomicInteger writeCount) {
        writeBuffer.clear();
        readBuffer.clear();
        readCount.set(0);
        writeCount.set(0);

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                byte[] bytes = new byte[128];
                while (!Thread.interrupted()) {
                    int rc = readCount.get(), toRead;
                    while ((toRead = writeCount.get() - rc) <= 0) ;
                    for (int i = 0; i < toRead; i++) {
                        byte len = readBuffer.get();
                        if (len == -1) {
                            // rewind.
                            readBuffer.clear();
//                            rc++;
                        } else {
                            int num = readBuffer.getInt();
                            if (num != rc)
                                throw new AssertionError("Expected " + rc + " but got " + num) ;
                            rc++;
                            readBuffer.get(bytes, 0, len - 4);
                        }
                    }
                    readCount.lazySet(rc);
                }
            }
        });
        t.setDaemon(true);
        t.start();
        Thread.yield();
        long start = System.nanoTime();
        int runs = 30 * 1000 * 1000;
        int len = 32;
        byte[] bytes = new byte[len - 4];
        int wc = writeCount.get();
        for (int i = 0; i < runs; i++) {
            if (writeBuffer.remaining() < len + 1) {
                // reader has to catch up.
                while (wc - readCount.get() > 0) ;
                // rewind.
                writeBuffer.put((byte) -1);
                writeBuffer.clear();
            }
            writeBuffer.put((byte) len);
            writeBuffer.putInt(i);
            writeBuffer.put(bytes);
            writeCount.lazySet(++wc);
        }
        // reader has to catch up.
        while (wc - readCount.get() > 0) ;
        t.interrupt();
        t.stop();
        long time = System.nanoTime() - start;
        System.out.printf("Message rate was %.1f M/s offsets %d %d %d%n", runs * 1e3 / time
                , addressOf(readBuffer) - addressOf(writeBuffer)
                , addressOf(readCount) - addressOf(writeBuffer)
                , addressOf(writeCount) - addressOf(writeBuffer)
        );
    }

    // assumes -XX:+UseCompressedOops.
    public static long addressOf(Object... o) {
        long offset = UNSAFE.arrayBaseOffset(o.getClass());
        return UNSAFE.getInt(o, offset) * 8L;
    }

    public static final Unsafe UNSAFE = getUnsafe();
    public static Unsafe getUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }

    private static class PaddedAtomicInteger extends AtomicInteger {
        public long p2, p3, p4, p5, p6, p7;

        public long sum() {
//            return 0;
            return p2 + p3 + p4 + p5 + p6 + p7;
        }
    }
}

打印同一数据块的时间。末尾的数字是对象的相对地址,表明它们在缓存中的布局每次都相同。运行 10 次较长时间的测试表明,给定的组合重复产生相同的性能。

Message rate was 63.2 M/s offsets 136 200 264
Message rate was 80.4 M/s offsets 136 200 264
Message rate was 80.0 M/s offsets 136 200 264

Message rate was 81.9 M/s offsets 136 200 264
Message rate was 82.2 M/s offsets 136 200 264
Message rate was 82.5 M/s offsets 136 200 264

Message rate was 79.1 M/s offsets 136 200 264
Message rate was 82.4 M/s offsets 136 200 264
Message rate was 82.4 M/s offsets 136 200 264

Message rate was 34.7 M/s offsets 136 200 264
Message rate was 39.1 M/s offsets 136 200 264
Message rate was 39.0 M/s offsets 136 200 264

每组缓冲区和计数器都进行了 3 次测试,这些缓冲区似乎给出了相似的结果。所以我相信这些缓冲区在内存中的布局方式我没有看到。

有什么东西可以更频繁地提供更高的性能吗?它看起来像缓存冲突,但我看不出这可能发生在哪里。

顺便说一句:M/s 每秒有数百万条消息,可能比任何人都需要的多,但最好了解如何使其始终保持快速。


编辑:使用同步等待和通知使结果更加一致。但不会更快。

Message rate was 6.9 M/s
Message rate was 7.8 M/s
Message rate was 7.9 M/s
Message rate was 6.7 M/s
Message rate was 7.5 M/s
Message rate was 7.7 M/s
Message rate was 7.3 M/s
Message rate was 7.9 M/s
Message rate was 6.4 M/s
Message rate was 7.8 M/s

编辑:通过使用任务集,如果我锁定两个线程以更改同一个内核,我可以使性能保持一致。

Message rate was 35.1 M/s offsets 136 200 216
Message rate was 34.0 M/s offsets 136 200 216
Message rate was 35.4 M/s offsets 136 200 216

Message rate was 35.6 M/s offsets 136 200 216
Message rate was 37.0 M/s offsets 136 200 216
Message rate was 37.2 M/s offsets 136 200 216

Message rate was 37.1 M/s offsets 136 200 216
Message rate was 35.0 M/s offsets 136 200 216
Message rate was 37.1 M/s offsets 136 200 216

If I use any two logical threads on different cores, I get the inconsistent behaviour

Message rate was 60.2 M/s offsets 136 200 216
Message rate was 68.7 M/s offsets 136 200 216
Message rate was 55.3 M/s offsets 136 200 216

Message rate was 39.2 M/s offsets 136 200 216
Message rate was 39.1 M/s offsets 136 200 216
Message rate was 37.5 M/s offsets 136 200 216

Message rate was 75.3 M/s offsets 136 200 216
Message rate was 73.8 M/s offsets 136 200 216
Message rate was 66.8 M/s offsets 136 200 216

编辑:触发 GC 似乎会改变行为。这些显示了在中途手动触发 GC 对相同缓冲区+计数器的重复测试。

faster after GC

Message rate was 27.4 M/s offsets 136 200 216
Message rate was 27.8 M/s offsets 136 200 216
Message rate was 29.6 M/s offsets 136 200 216
Message rate was 27.7 M/s offsets 136 200 216
Message rate was 29.6 M/s offsets 136 200 216
[GC 14312K->1518K(244544K), 0.0003050 secs]
[Full GC 1518K->1328K(244544K), 0.0068270 secs]
Message rate was 34.7 M/s offsets 64 128 144
Message rate was 54.5 M/s offsets 64 128 144
Message rate was 54.1 M/s offsets 64 128 144
Message rate was 51.9 M/s offsets 64 128 144
Message rate was 57.2 M/s offsets 64 128 144

and slower

Message rate was 61.1 M/s offsets 136 200 216
Message rate was 61.8 M/s offsets 136 200 216
Message rate was 60.5 M/s offsets 136 200 216
Message rate was 61.1 M/s offsets 136 200 216
[GC 35740K->1440K(244544K), 0.0018170 secs]
[Full GC 1440K->1302K(244544K), 0.0071290 secs]
Message rate was 53.9 M/s offsets 64 128 144
Message rate was 54.3 M/s offsets 64 128 144
Message rate was 50.8 M/s offsets 64 128 144
Message rate was 56.6 M/s offsets 64 128 144
Message rate was 56.0 M/s offsets 64 128 144
Message rate was 53.6 M/s offsets 64 128 144

编辑:使用@BegemoT 的库打印使用的核心 ID,我在 3.8 GHz i7(家用 PC)上得到以下信息

注意:偏移量是错误的 8 倍。由于堆大小很小,JVM 不会像处理更大的堆(但小于 32 GB)那样将引用乘以 8。

writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 54.4 M/s offsets 3392 3904 4416
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#6]
Message rate was 54.2 M/s offsets 3392 3904 4416
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 60.7 M/s offsets 3392 3904 4416

writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 25.5 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 25.9 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 26.0 M/s offsets 1088 1600 2112

writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 61.0 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 61.8 M/s offsets 1088 1600 2112
writer.currentCore() -> Core[#0]
reader.currentCore() -> Core[#5]
Message rate was 60.7 M/s offsets 1088 1600 2112

您可以看到正在使用相同的逻辑线程,但性能在运行之间有所不同,但在运行中却没有(在运行中使用相同的对象)


我发现了问题。这是一个内存布局问题,但我可以看到一种简单的方法来解决它。 ByteBuffer 无法扩展,因此您无法添加填充,因此我创建了一个我丢弃的对象。

    final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(64 * 1024);
    final ByteBuffer readBuffer = writeBuffer.slice();
    new PaddedAtomicInteger();
    final AtomicInteger readCount = new PaddedAtomicInteger();
    final AtomicInteger writeCount = new PaddedAtomicInteger();

没有这个额外的填充(未使用的对象),结果在 3.8 GHz i7 上看起来像这样。

Message rate was 38.5 M/s offsets 3392 3904 4416
Message rate was 54.7 M/s offsets 3392 3904 4416
Message rate was 59.4 M/s offsets 3392 3904 4416

Message rate was 54.3 M/s offsets 1088 1600 2112
Message rate was 56.3 M/s offsets 1088 1600 2112
Message rate was 56.6 M/s offsets 1088 1600 2112

Message rate was 28.0 M/s offsets 1088 1600 2112
Message rate was 28.1 M/s offsets 1088 1600 2112
Message rate was 28.0 M/s offsets 1088 1600 2112

Message rate was 17.4 M/s offsets 1088 1600 2112
Message rate was 17.4 M/s offsets 1088 1600 2112
Message rate was 17.4 M/s offsets 1088 1600 2112

Message rate was 54.5 M/s offsets 1088 1600 2112
Message rate was 54.2 M/s offsets 1088 1600 2112
Message rate was 55.1 M/s offsets 1088 1600 2112

Message rate was 25.5 M/s offsets 1088 1600 2112
Message rate was 25.6 M/s offsets 1088 1600 2112
Message rate was 25.6 M/s offsets 1088 1600 2112

Message rate was 56.6 M/s offsets 1088 1600 2112
Message rate was 54.7 M/s offsets 1088 1600 2112
Message rate was 54.4 M/s offsets 1088 1600 2112

Message rate was 57.0 M/s offsets 1088 1600 2112
Message rate was 55.9 M/s offsets 1088 1600 2112
Message rate was 56.3 M/s offsets 1088 1600 2112

Message rate was 51.4 M/s offsets 1088 1600 2112
Message rate was 56.6 M/s offsets 1088 1600 2112
Message rate was 56.1 M/s offsets 1088 1600 2112

Message rate was 46.4 M/s offsets 1088 1600 2112
Message rate was 46.4 M/s offsets 1088 1600 2112
Message rate was 47.4 M/s offsets 1088 1600 2112

与丢弃的填充对象。

Message rate was 54.3 M/s offsets 3392 4416 4928
Message rate was 53.1 M/s offsets 3392 4416 4928
Message rate was 59.2 M/s offsets 3392 4416 4928

Message rate was 58.8 M/s offsets 1088 2112 2624
Message rate was 58.9 M/s offsets 1088 2112 2624
Message rate was 59.3 M/s offsets 1088 2112 2624

Message rate was 59.4 M/s offsets 1088 2112 2624
Message rate was 59.0 M/s offsets 1088 2112 2624
Message rate was 59.8 M/s offsets 1088 2112 2624

Message rate was 59.8 M/s offsets 1088 2112 2624
Message rate was 59.8 M/s offsets 1088 2112 2624
Message rate was 59.2 M/s offsets 1088 2112 2624

Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624

Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.9 M/s offsets 1088 2112 2624
Message rate was 60.6 M/s offsets 1088 2112 2624

Message rate was 59.6 M/s offsets 1088 2112 2624
Message rate was 60.3 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624

Message rate was 60.9 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624
Message rate was 60.5 M/s offsets 1088 2112 2624

Message rate was 60.7 M/s offsets 1088 2112 2624
Message rate was 61.6 M/s offsets 1088 2112 2624
Message rate was 60.8 M/s offsets 1088 2112 2624

Message rate was 60.3 M/s offsets 1088 2112 2624
Message rate was 60.7 M/s offsets 1088 2112 2624
Message rate was 58.3 M/s offsets 1088 2112 2624

不幸的是,在 GC 之后,始终存在对象无法以最佳方式布局的风险。解决此问题的唯一方法可能是向原始类添加填充。 :(

【问题讨论】:

  • 你看过垃圾收集器在做什么吗?
  • -verbosegc 不打印任何内容。产生的垃圾很少。 ;)
  • 这个PaddedAtomicInteger 想法对我来说是新的。我假设目标是膨胀AtomicInteger,这样不同的实例就不会出现在同一个缓存行中。有没有人写过我能读到的关于这个想法的文章?
  • @TomAnderson,你可以试试这个测试,删除填充字段和方法。由于不一致,这有点难以看出,但是在较长的运行中,您会得到更低的最坏情况和最佳时机。您还可以看到偏移量(最后两个数字)变为200 216 而不是200 264
  • @jtahlborn,在 Java 堆和本机/非托管 C 内存上运行的 CPU 指令是相同的。如果lazySet(或只是volatile write)保证了存储-存储屏障语义(即,必须在指令完成时写入所有内容),那么内存在哪里都无关紧要。 其实我不确定你认为哪个回复不明智,是给彼得的还是给你自己的

标签: java performance memory concurrency jvm


【解决方案1】:

我不是处理器缓存领域的专家,但我怀疑您的问题本质上是缓存问题或其他一些内存布局问题。重复分配缓冲区和计数器而不清理旧对象可能会导致您定期获得非常糟糕的缓存布局,这可能会导致您的性能不一致。

使用您的代码并制作一些模组,我已经能够使性能保持一致(我的测试机器是 Intel Core2 Quad CPU Q6600 2.4GHz w/Win7x64 - 所以不太一样,但希望足够接近以获得相关结果) .我以两种不同的方式完成了此操作,两者的效果大致相同。

首先,将缓冲区和计数器的创建移到 doTest 方法之外,以便它们只创建一次,然后在每次通过测试时重复使用。现在你得到了一个分配,它很好地位于缓存中并且性能是一致的。

获得相同重用但使用“不同”缓冲区/计数器的另一种方法是在 performTiming 循环之后插入 gc:

for ( int i = 0; i < 3; i++ )
    performTiming ( writeBuffer, readBuffer, readCount, writeCount );
System.out.println ();
System.gc ();

这里的结果或多或少是相同的 - gc 让缓冲区/计数器被回收,下一次分配最终重用相同的内存(至少在我的测试系统上)并且您最终在缓存中具有一致的性能(我还添加了实际地址的打印以验证相同位置的重复使用)。我的猜测是,如果没有导致重用的清理,您最终会分配一个不适合缓存的缓冲区,并且您的性能会在它被交换时受到影响。我怀疑您可以按照分配顺序做一些奇怪的事情(就像您可以通过将计数器分配移动到缓冲区前面来使我的机器上的性能变差)或者如果您不想从先前的循环中消除缓冲区,则在每次运行周围创建一些死区以“清除”缓存.

最后,正如我所说,处理器缓存和内存布局的乐趣不是我的专业领域,所以如果解释具有误导性或错误 - 很抱歉。

【讨论】:

  • 一次创建缓冲区的问题是,您确实会获得始终如一的好或坏性能。你不会知道是哪个。我想始终接近最佳性能。即使您以良好的性能开始,GC 可以移动对象也可以改变性能特征。
  • 我喜欢填充缓冲区的想法,这可能也很有用。它没有解释 GC 改变性能的情况,因为缓冲区在直接内存中(只有堆中的部分被改变)
【解决方案2】:

你正忙着等待。这在用户代码中总是一个坏主意。

读者:

while ((toRead = writeCount.get() - rc) <= 0) ;

作者:

while (wc - readCount.get() > 0) ;

【讨论】:

  • +1。这就是 wait()notify()notifyAll() 的用途。
  • 忙等待的原因是为了避免放弃核心并使其上下文切换。这会显着增加延迟。使用 wait/notify 稍微慢一些,但没有我预期的那么慢。
  • 使用等待/通知使性能更加一致,但至少慢 4 倍。
  • @PeterLawrey - 您尝试过 Lock/Condition 构造吗?在某些版本的 jvm 中,它们的性能更好。
  • @z5h,wait/notify 和 Lock/Conditions 对于这段代码来说都是糟糕的想法(越糟糕)。 Park/Unpark 是在一些繁忙的旋转之后去的方式,可能是 Thread.yeild 和后退。
【解决方案3】:

作为性能分析的一般方法:

  • 试试jconsole。启动您的应用程序,并在其运行时在单独的终端窗口中键入 jconsole。这将打开 Java 控制台 GUI,它允许您连接到正在运行的 JVM,并查看性能指标、内存使用情况、线程数和状态等。
  • 基本上,您必须弄清楚速度波动与您看到的 JVM 所做的事情之间的相关性。调出您的任务管理器并查看您的系统是否实际上正忙于做其他事情(由于内存不足而分页到磁盘,忙于繁重的后台任务等)并将其并排放置也可能会有所帮助 -在jconsole 窗口旁边。
  • 另一种选择是使用 -Xprof 选项启动 JVM,该选项基于每个线程输出各种方法所花费的相对时间。前任。 java -Xprof [your class file]
  • 最后还有JProfiler,但它是一个商业工具,如果这对你很重要的话。

【讨论】:

  • 这台机器规格相当高,4.6 GHz i7 和 16 GB 内存,没有其他运行。报告的速度似乎不会随着运行时间的延长而改变,这表明涉及到一些随机缓存或线程布局因素。 (即,您会期望大多数随机因素会随着越来越长的运行而平均化)
  • 我不会在这种测试中使用分析器,它会影响性能。此外,几乎没有任何代码可以分析。
  • 这只是一种通用方法,性能被性能分析器“杀死”并不重要——这个想法是找出你的代码在哪里花费你的时间。当然,这种观察会有开销,但不会使结果无效。
  • 它可以使结果无效,因为负载下的应用程序在被 jvmti 人为减慢时的行为可能与没有受到这种胁迫时的行为完全不同。我认为探查器不是解决这类性能问题的正确工具。
  • 分析器有两种工作方式:采样和代码注入。采样很糟糕,因为它依赖于安全点来收集任何堆栈跟踪,即它依赖于 JVM 将放置安全点并且通常它不会显示任何有用的信息。代码注入更加糟糕,它改变了 JVM 编译代码的方式并扼杀了很多优化。简而言之,简单地分析低级别的东西是行不通的。
【解决方案4】:

编辑:似乎触发 GC 会改变行为。这些 通过手动触发在相同的缓冲区+计数器上显示重复测试 GC 中途。

GC 意味着到达一个安全点,这意味着所有线程都已停止执行字节码并且 GC 线程有工作要做。这可能会产生各种副作用。例如,在没有任何明确的 cpu 亲和性的情况下,您可能会在不同的核心上重新开始执行,或者缓存行可能已被刷新。你能跟踪你的线程在哪些内核上运行吗?

这些是什么 CPU?您是否对电源管理做过任何事情以防止它们掉入较低的 p 和/或 c 状态?可能有 1 个线程被调度到处于不同 p 状态的内核上,因此显示不同的性能配置文件。

编辑

我尝试在运行 x64 linux 和 2 个稍微旧的四核至强 (E5504) 的工作站上运行您的测试,它在运行 (~17-18M/s) 内通常是一致的,有时运行速度要慢得多,这似乎通常与线程相对应迁移。我没有严格地绘制这个。因此,您的问题似乎是特定于 CPU 架构的。您提到您正在运行 4.6GHz 的 i7,这是一个错字吗?我认为 i7 在3.5GHz with a 3.9Ghz turbo mode(早期版本为 3.3GHz 至 3.6GHz turbo)达到顶峰。无论哪种方式,您确定您没有看到涡轮模式启动然后退出的神器吗?您可以尝试在禁用 turbo 的情况下重复测试。

其他几点

  • 填充值都是0,你确定没有对未初始化的值进行特殊处理吗?您可以考虑使用 LogCompilation 选项来了解 JIT 如何处理该方法
  • Intel VTune 可免费进行 30 天评估,如果这是缓存行问题,那么您可以使用它来确定主机上的问题

【讨论】:

  • 在每个测试中,“后台”线程都是一个新线程。但是,结果仍然大致相同(除非调用 GC 来移动对象)
  • i7 是超频的,有一个比电源更大的散热器。 ;)
【解决方案5】:

您实际上是如何将线程固定到内核的? taskset 不是将线程固定到核心的最佳方式,因为它只是将进程固定到核心——并且它的所有线程都将共享这个核心。回想一下,java 有许多内部线程来满足自己的需要,所以它们都会在你将它们绑定到的内核上竞争。

要获得更一致的结果,您可以使用 JNA 仅从您需要的线程调用 sched_setaffinity()。它只会将您的基准测试线程固定到精确的内核上,而其他 java 线程将分布在其他空闲内核上,对您的代码行为的影响较小。

顺便说一句,在对高度优化的并发代码进行基准测试时,我也遇到过性能不稳定的类似问题。看起来,在接近硬件限制的情况下,有太多东西会严重影响性能。您应该以某种方式调整您的操作系统,以使您的代码有可能使其成为最佳状态,或者只是使用许多实验并使用数学来获得平均值和置信区间。

【讨论】:

  • 我不认为线程亲和性是主要问题的部分原因是,如果我对不同的线程使用相同的缓冲区/对象,我会得到相同的结果,直到触发 GC。在 GC 之后,每个测试都会重复获取新的计时。
  • 好吧,如果 GC 是主要问题,那么紧缩似乎是什么原因——由于 GC 可能会进行内存碎片整理,到处移动对象,这可能是新对象布局不足的原因之一-- CPU 缓存并不像“缓存行”和“错误共享”那么简单——还有缓存关联性之类的东西。例如,readCount 和 writeCount 虽然是填充的,但可以放在这样的内存区域上,这些内存区域通过有限关联缓存映射到同一缓存行......
  • 另外,您可以查看 Cliff Click 的帖子 azulsystems.com/blog/cliff/…(向下滚动,直到他谈到 Disruptor)。 Disruptor ring buffer 与您的代码非常相似(共享缓冲区 + 易失性读/写上的 membars 以强制线程之间的数据传输——它们甚至使用lazySet 进行易失性写入优化),并且 Cliff 观察到与您一样的 3 倍性能不稳定性问题,所以他的描述可以帮助你理解问题。但他主要声称线程亲和力是原因。
  • 3x 性能差异悬崖报价是比较同一套接字或不同套接字上的两个线程。在上述情况下,我的机器只有一个套接字(但我们的服务器有多个套接字,taskset 可能是将进程限制为一个套接字的答案)
  • 另外,你没有尝试增加填充——例如增加到 128 字节吗?
【解决方案6】:

在运行完整 GC 时肯定会带来一些不一致,但这种情况并不常见。尝试将堆栈大小 (Xss) 修改为 32M,看看是否有帮助。此外,尝试在每次测试结束时清除 2 个缓冲区,以使 GC 更容易知道可以收集内容。有趣的是,您使用了已弃用且绝对不推荐的 thread.stop()。我也建议改变它。

【讨论】:

  • 我不确定热清除直接字节缓冲区是否有助于 GC。 clear() 实际上只设置了两个字段。 stop() 是为了确保线程已经停止。您怀疑这是性能问题吗?
  • 不带stop,只能指向corruption of state,不推荐。我过去曾看到 clear() 帮助,因此提出了建议。我的直觉是调整 Xss 会对你有所帮助。
  • 如果在任何时候抛出 ThreadError 可能会使同步或锁定的对象处于不一致状态,则会导致损坏状态。代码确实存在这个问题,因为它很简单,并且在测试后所有状态都被重置。 (99% 的真实程序都会有这个问题)
猜你喜欢
  • 1970-01-01
  • 2018-11-06
  • 2011-06-27
  • 2019-03-20
  • 1970-01-01
  • 1970-01-01
  • 2012-01-22
  • 2012-04-27
  • 2014-11-20
相关资源
最近更新 更多