java - 为什么这段代码在有锁的情况下运行得更快?

标签 java performance

一些背景:我创建了一个人为的示例来向我的团队演示 VisualVM 的使用。特别是,一种方法有一个不必要的 synchronized关键字,我们看到线程池中的线程阻塞,它们不需要在那里。但是删除该关键字产生了下面描述的令人惊讶的效果,下面的代码是最简单的情况,我可以将原始示例简化为重现问题,并使用 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();
            }
        });
    }
}
}
ReentrantLock实际上是不必要的。需要同步的线程之间没有共享状态。锁定到位后,我们预期会观察到线程池中的线程阻塞。随着锁被移除,我们预计不会再有阻塞,并且所有线程都能够完全并行运行。

取消锁定的意外结果是代码始终需要更长的时间才能完成,在我的机器(四核 i7)上增加了 15-25%。分析代码显示线程中没有任何阻塞或等待的迹象,总 CPU 使用率只有 50% 左右,在内核上分布相对均匀。

第二个意外的是,这也取决于generator的类型使用的。如果我使用 GaussianRandomGeneratorUniformRandomGenerator而不是 StableRandomGenerator ,观察到预期结果 - 通过删除 lock(),代码运行速度更快(大约 10%) .

如果线程不阻塞,CPU 处于合理水平,并且不涉及 IO,这如何解释?我真正掌握的唯一线索是 StableRandomGenerator确实调用了很多三角函数,所以显然比高斯或均匀生成器更占用 CPU,但为什么我没有看到 CPU 被最大化?

编辑:另一个重点(感谢 Joop) - 制作 generator Runnable 本地(即每个线程一个)显示正常的预期行为,其中添加锁会使代码减慢约 50%。所以奇怪行为的关键条件是 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 .将调用同步到随机生成器时,不会观察到相同的行为。

最佳答案

here相同的问题.

您实际上是在测量具有共享状态的 PRNG 内的争用。
JDKRandomGenerator基于 java.util.Random其中有 seed在所有工作线程之间共享。线程竞争更新 seedcompare-and-set loop .

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

关于java - 为什么这段代码在有锁的情况下运行得更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28708058/

相关文章:

java - HttpClient 从 C# 复制到 Java

java - 罗马数字的字符串替换

android - 应用程序中有效 fragment 的数量?

c++ - 这个素数生成器是低效的 C++ 吗?

node.js - 每秒奇怪的结果测试操作

java - 如何在其他 .war 文件中引用特定 .war 的 jar 文件

java - 如何实现Java windows服务包装器YAJSW的停止和启动逻辑?

javascript - 异步 Promise 阻塞 DOM

java - 使用jsoup连接到不受信任的证书

linux - 需要性能计数器信息