提高性能一致性的方法

46
在下面的例子中,一个线程通过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

每组缓冲区和计数器都进行了三次测试,这些缓冲区似乎给出了类似的结果。因此,我相信有关于这些缓冲区在内存中布局的一些问题我没有发现。
是否有任何方法可以更经常地提高性能?看起来像是缓存冲突,但我看不到这里可能发生的地方。
顺便说一下: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

很遗憾,每次垃圾回收后,对象的布局可能无法优化。唯一解决这个问题的方法可能是在原始类中添加填充 :(


@jtahlborn,内存就是内存,无论是Java管理的还是非管理的(C malloc)类型的内存,实际上并没有什么区别,最终所有的Java代码都会在CPU(汇编器)中执行。 - bestsss
@bestsss - 这个回复毫无意义,似乎完全忽略了现代计算机架构。 - jtahlborn
1
@jtahlborn,操作Java堆和本地/非托管C内存的CPU指令是相同的。如果lazySet(或只是volatile写入)保证store-store屏障语义(即一切都必须在指令完成时写入),那么内存位于哪里就无关紧要了。实际上我不确定您认为哪个回复是不明智的,是给Peter的还是给您自己的? - bestsss
@bestsss - (我是在回应你关于内存的评论)。现在你扩展了你所说的,它变得更有意义了。我理解你最初的陈述仅仅是“一旦你写入内存,它就在任何地方可见”。然而,我不同意。JVM的保证只与受管理的内存相关。你不知道JVM可能会被实现为将内存保存在文件中。你假设JVM使用系统指令来管理易失性内存管理的保证,这也会影响本地内存(这可能是正确的,但不是必需的)。 - jtahlborn
一些相关的东西在这里http://gee.cs.oswego.edu/dl/jmm/cookbook.html有一个有趣的评论在最后面:“对JNI例程的调用和返回可能需要屏障,尽管这似乎是实现质量的问题。”虽然这似乎普遍意味着,实现jmm“需要”使用本地内存屏障。 - jtahlborn
显示剩余18条评论
6个回答

24

虽然我不是处理器缓存领域的专家,但我怀疑你的问题基本上是一个缓存问题或其他内存布局问题。重复分配缓冲区和计数器,而没有清理旧的对象,可能会导致你定期出现非常糟糕的缓存布局,从而导致性能不一致。

使用你的代码并进行一些修改,我已经能够使性能保持一致(我的测试机器是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也可以移动对象并更改性能特征。 - Peter Lawrey
我喜欢填充缓冲区的想法,这也可能很有用。它没有解释当垃圾收集器更改性能时的情况,因为缓冲区在直接内存中(只有堆中的部分被更改)。 - Peter Lawrey

8

你正在繁忙等待,这在用户代码中通常是一个坏主意。

读者:

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

作者:

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

3
这就是 wait()notify()notifyAll() 的作用所在。 - Mark Bolusmjak
7
忙等待的原因是为了避免放弃核心并进行上下文切换。这可能会显着增加延迟。使用wait/notify稍微慢一些,但并没有比我预期的慢很多。 - Peter Lawrey
3
使用wait/notify可以使性能更加稳定,但速度至少变慢4倍。 - Peter Lawrey
1
@PeterLawrey - 你尝试过使用Lock/Condition构造吗?在某些版本的JVM中,它们的性能表现更好。 - jtahlborn
2
@z5h,无论是wait/notify还是Lock/Conditions都不适合这段代码(更加糟糕)。在经过一些忙碌的自旋、可能的Thread.yeild和退避后,Park/Unpark才是正确的选择。 - bestsss
忙等待所提供的平衡感真的很有趣,这正表明测试胜过理论!+1 测试它 Peter - Toby

6
您想如何将线程钉在核心上?taskset不是将线程钉在核心上的最佳方式,因为它只会将进程钉在核心上 - 所有线程都将共享这些核心。请记住,Java有许多内部线程以满足其自身需求,所以它们都将争夺您绑定到它们的核心。
为了获得更一致的结果,您可以使用JNA仅从需要的线程中调用sched_setaffinity()。它只会将您的基准测试线程固定在确切的核心上,而其他Java线程则会分布在其他空闲的核心上,对您的代码行为具有较小的影响。
顺便说一下,我在基准测试高度优化的并发代码时遇到了类似的性能不稳定问题。看起来在接近硬件极限时,有太多因素可能会显着影响性能。您应该通过某种方式调整操作系统,使其能够最佳地运行您的代码,或者只需进行许多实验,并使用数学来计算平均值和置信区间。

我不认为线程亲和性是主要问题的部分原因是,如果我在不同的线程中使用相同的缓冲区/对象,我会得到相同的结果,直到我触发GC。在GC之后,每个测试都会重复获得新的计时。 - Peter Lawrey
2
如果GC是主要问题,那么似乎压缩是原因--因为GC可能会进行内存碎片整理,将对象移动到这里和那里,它可能是新对象布局不足的原因--CPU缓存不仅仅是“缓存行”和“虚假共享”这么简单--还有缓存关联性这样的东西。例如,readCount和writeCount,尽管填充了,但可能被放置在这样的内存区域上,这些区域由有限关联性缓存映射到同一个缓存行上... - BegemoT
3
此外,您可以查看Cliff Click发表的文章http://www.azulsystems.com/blog/cliff/2011-09-23-a-pair-of-somebody-elses-concurrency-bugs(向下滚动直到他谈论Disruptor)。 Disruptor环形缓冲区与您的代码非常相似(共享缓冲区+在volatile读/写上使用membar以强制线程之间的数据传输--甚至使用lazySet进行volatile写优化),并且Cliff观察到与您一样的3倍性能不稳定性问题,因此他的描述可以帮助您了解问题。 但他主要认为线程亲和力是原因。 - BegemoT
1
3倍性能差异悬崖的引用是在比较同一插槽或不同插槽上的两个线程。在上述情况中,我的机器只有一个插槽(但我们的服务器有多个插槽,taskset可能是限制进程到一个插槽的答案)。 - Peter Lawrey
哦,我刚从你的代码中看到一件事。你有30-40M/s的两个线程坐在同一个核心上,如果通过taskset将_你的进程_附加到两个核心之间,则为30到80M/s之间的某些内容。但是谁告诉你JVM和OS实际上会为_你的基准测试线程_使用两个核心?可能是,在GC峰值负载期间,操作系统重新调度您的线程以仅共享一个核心! - BegemoT
显示剩余4条评论

6
作为性能分析的一般方法:
  • 尝试使用jconsole。启动您的应用程序,在其运行时在单独的终端窗口中键入jconsole。这将带出Java控制台GUI,使您可以连接到运行的JVM,并查看性能指标、内存使用情况、线程计数和状态等。
  • 基本上,您需要找出速度波动与JVM正在做的事情之间的关联。此外,打开任务管理器,并查看系统是否实际上只是忙于执行其他任务(由于低内存而进行磁盘分页,繁忙的后台任务等),并将其与jconsole窗口并排放置。
  • 另外一个选择是使用-Xprof选项启动JVM,它会按线程输出各个方法的相对耗时。例如:java -Xprof [your class file]
  • 最后,还有一个商业工具JProfiler,但如果那对您有影响的话。

这台机器配置相当高,拥有4.6 GHz的i7处理器和16 GB内存,并且没有其他程序在运行。报告的速度似乎不会随着长时间运行而改变,这表明涉及到一些随机缓存或线程布局因素。(即,您预计大多数随机因素会随着越来越长的运行而平均化) - Peter Lawrey
3
我不会在这种测试中使用分析器,它会降低性能。此外,几乎没有代码可以进行分析。 - Matt
这只是一种通用的方法,而且性能被分析器“杀死”并不重要 - 关键是找出你的代码花费时间的地方。当然,这种观察会有开销,但这并不使结果无效。 - jefflunt
1
它可能会使结果无效,因为在jvmti人为减速时,应用程序在负载下的行为可能与不受此类压力时的行为非常不同。我认为分析器不是解决这类性能问题的正确工具。 - Matt
2
有两种性能分析器的工作方式:采样和代码注入。采样不好用,因为它依赖于安全点来收集任何堆栈跟踪,即它取决于JVM将放置安全点的位置,通常它将显示没有有用信息。代码注入更糟糕,因为它改变了JVM编译代码的方式,并且会破坏很多优化。简而言之,对低级别的东西进行性能分析是行不通的。 - bestsss

6

编辑:触发GC会改变行为。这些测试显示了在手动触发GC后对同一缓冲区和计数器进行的重复测试。

GC是指达到安全点,这意味着所有线程都停止执行字节码并且GC线程有工作要做。这可能会产生各种副作用。例如,在没有任何显式CPU亲和力的情况下,您可能会在不同的核心上重新启动执行或刷新缓存行。您能跟踪您的线程运行在哪些核心上吗?

这些是什么CPU?您是否采取任何措施防止它们降至更低的P和/或C状态?也许一个线程正在被调度到一个处于不同P状态的核心上,因此显示出不同的性能配置文件。

编辑

我尝试在运行x64 Linux的工作站上使用两个稍旧的四核Xeons(E5504)运行您的测试,它通常在一次运行中保持一致(约17-18M / s),偶尔运行速度较慢,这似乎通常与线程迁移相对应。我没有严格绘制这个。因此,您的问题可能是特定于CPU体系结构的。您提到您正在运行一个4.6GHz的i7,这是一个打字错误吗?我认为i7最高只能达到3.5GHz和3.9Ghz turbo模式(早期版本为3.3GHz至3.6GHz turbo)。无论如何,您确定没有看到turbo模式启动然后退出的人工现象吗?您可以尝试禁用turbo重复测试以确保。

还有几点

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

在每个测试中,“背景”线程都是新的。然而,结果仍然大致相同(除非调用GC移动对象)。 - Peter Lawrey
i7已经超频了,散热器比电源还要大。 ;) - Peter Lawrey

2

当进行完整的垃圾回收时,可能会出现一些不一致性,但这种情况并不经常发生。尝试修改堆栈大小(Xss)为32M,看看是否有帮助。此外,尝试在每个测试结束时清除2个缓冲区,以使垃圾回收更容易知道可以收集哪些内容。有趣的是,您使用了已弃用且绝对不推荐使用的thread.stop()。我建议您也将其更改。


我不确定清除直接字节缓冲区会如何帮助GC。clear()实际上只设置了两个字段。stop()是为了确保线程已经停止。你怀疑这是一个性能问题吗? - Peter Lawrey
不要使用stop方法,因为它可能导致状态损坏(http://download.oracle.com/javase/1.4.2/docs/guide/misc/threadPrimitiveDeprecation.html),因此不建议使用。我曾经看到过clear()方法有所帮助,因此建议使用。我的直觉是调整Xss参数会有所帮助。 - aishwarya
1
如果在任何时候抛出ThreadError可能会使一个同步或锁定的对象处于不一致状态,这将导致破坏状态。代码确实存在这个问题,因为它是微不足道的,而且在测试后所有状态都会被重置。(99%的真实程序将存在此问题) - Peter Lawrey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接