2

嗨,伙计们,所以我得到了这段代码

public class Padding {

  static class Pair {

        volatile long c1;
        // UN-comment this line and see how perofmance is boosted * 2
      //  long q1; //Magic dodo thingy

        volatile long c2;

  }

  static Pair p = new Pair();

  static class Worker implements Runnable {

        private static final int INT = Integer.MAX_VALUE/8;
        private boolean b;
        Worker(boolean b) {
              this.b = b;
        }

        public void run() {
              long start = System.currentTimeMillis();
              if (b) {
                    for (int i = 0; i < INT; i++) {
                          p.c1++;
                          res += Math.random();
                    }
              } else {
                    for (int i = 0; i < INT; i++) {
                          p.c2++;
                          res += Math.random();
                    }
              }
              long end = System.currentTimeMillis();
              System.out.println("took: " + (end-start) + " Result:" + p.c1+p.c2);
        }

  }


  public static void main(String[] args) {
        System.out.println("Starting....");
        Thread t1 = new Thread(new Worker(true));
        Thread t2 = new Thread(new Worker(false));

        t1.start();
        t2.start();


  }

}

因此,如果我运行它大约需要 11 秒,但如果我取消注释 qa1 它会在 3 秒内运行。我试图在互联网上找到一些东西,但没有足够的信息出现。据我了解,它与 JVM 优化有关,而长 q1 可能会使内存(或缓存)分布变得更好。无论如何,我的问题是有人知道我在哪里可以阅读更多有关它的信息。谢谢

4

1 回答 1

10

您的示例中的性能因错误共享而降低-c1并且c2实例被放置在同一缓存行中,并且线程需要在每次不同字段的增量时将值刷新/加载到主内存/从主内存中刷新/加载值,以在相互缓存行复制失效后保持缓存一致性。

在您的情况下,在进入另一个高速缓存行之后立即声明一个更长q1的字段就足够了(其大小仅适用于该系列)。在它缓存管理变得更加有效之后 - 线程可以使用不同的缓存行并且不会使其他线程的缓存行的副本无效。c1c264 bytesx86

有许多文章专门讨论这个问题的硬件性质(以及避免它的软件方法)。长期以来,通过“足迹填充”解决方案(如您的)处理错误共享一直很棘手 - Java 平台保证运行时的字段顺序和缓存行填充将完全符合您在类声明中的期望。即使是“次要”平台更新或切换到另一个 VM 实现也可能会破坏解决方案(因为字段 - 尤其是未使用的虚拟对象 - 是优化的主题)。

这就是引入JEP - 142. 此注解允许您配置类的哪些字段应放置在不同的缓存行上。但现在它只是一个虚拟机提示,没有任何绝对保证可以在所有情况下避免错误共享,因此您应该仔细查看代码并验证其行为(当然,如果您的应用程序对错误共享问题很敏感)@ContendedJava 8

于 2016-03-28T08:53:23.523 回答