性能解释:使用未使用的变量,代码运行得更

时间:2012-08-07 14:36:50

标签: java multithreading performance jvm

我之前做过一些性能测试,无法解释我获得的结果。

在运行下面的测试时,如果我取消注释private final List<String> list = new ArrayList<String>();,性能会显着提高。在我的机器上,测试在该字段存在时以70-90毫秒运行,而在注释时为650毫秒。

我还注意到,如果我将print语句更改为System.out.println((end - start) / 1000000);,则没有变量的测试运行时间为450-500毫秒而不是650毫秒。变量存在时无效。

我的问题:

  1. 考虑到我甚至不使用该变量,任何人都可以解释有或没有变量的近10个因素吗?
  2. 该打印声明如何改变性能(特别是在 性能测量窗口之后)?
  3. ps:当按顺序运行时,3个场景(带有变量,没有变量,带有不同的print语句)都需要大约260ms。

    public class SOTest {
    
        private static final int ITERATIONS = 10000000;
        private static final int THREADS = 4;
    
        private volatile long id = 0L;
        //private final List<String> list = new ArrayList<String>();
    
        public static void main(String[] args) throws Exception {
            ExecutorService executor = Executors.newFixedThreadPool(THREADS);
            final List<SOTest> objects = new ArrayList<SOTest>();
            for (int i = 0; i < THREADS; i++) {
                objects.add(new SOTest());
            }
    
            //warm up
            for (SOTest t : objects) {
                getRunnable(t).run();
            }
    
            long start = System.nanoTime();
    
            for (SOTest t : objects) {
                executor.submit(getRunnable(t));
            }
            executor.shutdown();
            executor.awaitTermination(10, TimeUnit.SECONDS);
    
            long end = System.nanoTime();
            System.out.println(objects.get(0).id + " " + (end - start) / 1000000);
        }
    
        public static Runnable getRunnable(final SOTest object) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < ITERATIONS; i++) {
                        object.id++;
                    }
                }
            };
            return r;
        }
    }
    

    编辑

    请参阅下面的3个方案的10次运行结果:

    • 没有变量,使用短打印声明
    • 没有变量,使用长打印语句(打印其中一个对象)
    • 顺序运行(1个线程)
    • 使用变量
    1   657 473 261 74
    2   641 501 261 78
    3   651 465 259 86
    4   585 462 259 78
    5   639 506 259 68
    6   659 477 258 72
    7   653 479 259 82
    8   645 486 259 72
    9   650 457 259 78
    10  639 487 272 79
    

4 个答案:

答案 0 :(得分:10)

明确(错误)分享

由于内存中的布局,对象共享缓存行... 已经解释了很多次(甚至在这个网站上):这里有一个good source供进一步阅读。该问题适用于C#just as much(或C / C ++)

当您通过添加注释掉的行来填充对象时,共享会更少,您会看到性能提升。

编辑:我错过了第二个问题:


  

该print语句如何改变性能(特别是从那以后)   它出现在性能测量窗口之后)?

我想没有足够的变暖,打印GC和编译日志,这样你就可以确定没有干扰并且代码实际上是编译的。 java -server需要10k次迭代,最好不要在主循环中全部生成良好的代码。

答案 1 :(得分:2)

您正在尝试执行硬件的微妙效果。您SOTest对象的内存非常小,因此所有4个实例都可以放入内存中的同一缓存行。由于您使用的是volatile,因此会导致不同内核之间的缓存丢弃(只有一个内核可能会使缓存行变脏)。

当你在ArrayList中发表评论时,内存布局会发生变化(在SOTest实例之间创建ArrayList),而volatile字段现在会进入不同的缓存行。 CPU的问题消失了,因此性能急剧上升。

证明:注释掉ArrayList并改为:

long waste1, waste2, waste3, waste4, waste5, waste6, waste7, waste8;

这会将您的SOTest对象扩大64个字节(奔腾处理器上一个缓存行的大小)。性能现在与.List中的ArrayList相同。

答案 2 :(得分:1)

这只是一个想法,我不知道如何验证它,但这可能与缓存有关。随着ArrayList的出现,你的对象变得更大,因此它们中较少的数量适合某些给定的缓存内存区域,从而导致更多的缓存未命中。

在实际可以尝试的事情上是使用不同大小的ArrayLists,从而改变类实例的内存占用量,看它是否对性能产生影响。

答案 3 :(得分:1)

非常有趣的旅程。这更像是“这是我的结果答案”。我怀疑/希望其他人会提出更好的回应。

你显然正在寻找一些有趣的优化点。我怀疑在objects.get(0).id语句中添加println会删除对id字段使用的一些优化。除++之外,id没有其他用法,因此优化器可能会优化对volatile id的一些访问,从而提高速度。只需使用id访问long x = objects.get(0).id;字段,效果就会相同。

List字段更有趣。如果添加了字段private String foo = new String("weofjwe");不是,如果private String foo = "weofjwe";没有创建对象,则会发生相同的性能提升,因为"..."是在编译时完成的。我肯定 final是相关的,但似乎不是。我只能推测这与构造函数优化有关,添加了new导致优化被停止,尽管我会volatile会更有效地完成。