为什么这个代码在锁定时运行得更快?

Rev*_*ngo 13 java performance

一些背景:我创建了一个人为的例子来向我的团队展示VisualVM的使用.特别是,一个方法有一个不必要的synchronized关键字,我们看到线程池中的线程阻塞,他们不需要.但删除该关键字具有下面描述的令人惊讶的效果,下面的代码是最简单的情况我可以减少原始示例以重现问题,并且使用a ReentrantLock也会产生相同的效果.

请考虑以下代码(https://gist.github.com/revbingo/4c035aa29d3c7b50ed8b上的完整可运行代码示例- 您需要将Commons Math 3.4.1添加到类路径中).它创建100个任务,并将它们提交给5个线程的线程池.在任务中,创建两个500x500随机值矩阵,然后相乘.

public class Main {
private static ExecutorService exec = Executors.newFixedThreadPool(5);

private final static int MATRIX_SIZE = 500;
private static UncorrelatedRandomVectorGenerator generator = 
            new UncorrelatedRandomVectorGenerator(MATRIX_SIZE, new StableRandomGenerator(new JDKRandomGenerator(), 0.1d, 1.0d));

private static ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) throws Exception {

    for(int i=0; i < 100; i++) {

        exec.execute(new Runnable() {
            @Override
            public void run() {
                double[][] matrixArrayA = new double[MATRIX_SIZE][MATRIX_SIZE];
                double[][] matrixArrayB = new double[MATRIX_SIZE][MATRIX_SIZE];
                for(int j = 0; j< MATRIX_SIZE; j++) {
                    matrixArrayA[j] = generator.nextVector();
                    matrixArrayB[j] = generator.nextVector();
                }

                RealMatrix matrixA = MatrixUtils.createRealMatrix(matrixArrayA);
                RealMatrix matrixB = MatrixUtils.createRealMatrix(matrixArrayB);

                lock.lock();
                matrixA.multiply(matrixB);
                lock.unlock();
            }
        });
    }
}
}
Run Code Online (Sandbox Code Playgroud)

ReentrantLock实际上是不必要的.需要同步的线程之间没有共享状态.在锁定到位的情况下,我们期望观察线程池阻塞中的线程.删除锁后,我们预计不会再发现阻塞,并且所有线程都能够并行完全运行.

取消锁定的意外结果是代码在我的机器(四核i7)上完成需要更长的时间才能完成15-25%.对代码进行概要分析表明线程中没有任何阻塞或等待的迹象,并且总CPU使用率仅为50%左右,在核心上相对均匀地分布.

第二个意想不到的事情是,这也取决于所使用的类型generator.如果我使用a GaussianRandomGeneratorUniformRandomGenerator代替,StableRandomGenerator则会观察到预期结果 - 通过删除代码,代码运行得更快(大约10%)lock().

如果线程没有阻塞,CPU处于合理的水平,并且没有涉及IO,如何解释?我真正拥有的唯一线索是它StableRandomGenerator确实调用了许多三角函数,因此显然比高斯或统一生成器的CPU密集程度高得多,但为什么我没有看到CPU被最大化?

编辑:另一个重点(感谢Joop) - 使generatorRunnable本地化(即每个线程一个)显示正常的预期行为,其中添加锁会使代码减慢大约50%.因此,奇怪行为的关键条件是a)使用a StableRandomGenerator,和b)在线程之间共享该生成器.但据我所知,该生成器是线程安全的.

编辑2:虽然这个问题表面上非常类似于链接的重复问题,答案似乎是合理的,几乎肯定是一个因素,但我还是不相信它就像那样简单.让我质疑的事情:

1)问题只能通过同步multiply()操作来显示,该操作不会进行任何调用Random.我的直接想法是,同步最终会在某种程度上错开线程,因此"意外地"提高了性能Random#next().但是,同步调用generator.nextVector()(理论上以"正确"的方式具有相同的效果)不会重现问题 - 同步会降低代码的速度,如您所料.

2)只有观察到问题StableRandomGenerator,即使其他实现NormalizedRandomGenerator也使用了JDKRandomGenerator(正如指出的只是一个包装java.util.Random).实际上,我RandomVectorGenerator用直接调用替换了矩阵中填充的使用Random#nextDouble,并且行为再次恢复到预期结果 - 同步代码的任何部分导致总吞吐量下降.

总之,这个问题只能通过以下方式来观察

a)使用StableRandomGenerator- 没有其他子类NormalizedRandomGenerator,也不使用JDKRandomGeneratorjava.util.Random直接显示相同的行为.

b)同步呼叫RealMatrix#multiply.将调用同步到随机生成器时,未观察到相同的行为.

apa*_*gin 4

与这里同样的问题。

您实际上是在测量具有共享状态的 PRNG 内部的争用。

JDKRandomGenerator基于java.util.Randomseed在所有工作线程之间共享。线程seed比较和设置循环中竞争更新。

那为什么lock要提高性能呢?事实上,它有助于java.util.Random通过序列化工作来减少内部争用:当一个线程执行矩阵乘法时,另一个线程用随机数填充矩阵。没有lock线程可以同时执行相同的工作。