java - 在 c4.large AWS 实例中运行的 Java 应用程序性能低下

标签 java multithreading amazon-web-services amazon-ec2 parallel-processing

我正在尝试使用 Java 1.8 和 Ubuntu 在 AWS 上的 c4.large(具有两个内核的机器)实例中的两个线程中执行计算。添加第二个线程后,每个线程的计算速度从 26 秒减慢到 34 秒。我检查了内核的使用情况,添加第二个线程后,第二个内核的使用率为 100%。 在具有双核处理器的本地计算机上,两个线程不会减慢线程速度。

c4.large instance:
Thread 0 start
Thread 0 time: 26 seconds
Thread 1 start
Thread 0 time: 29 seconds
Thread 1 time: 34 seconds
Thread 0 time: 34 seconds
Thread 1 time: 34 seconds
Thread 0 time: 34 seconds

如何改进以下代码或更改系统配置以提高性能?

import java.io.IOException;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.DoubleUnaryOperator;
import java.util.stream.DoubleStream;

public class TestCalculate {

    private Random rnd = ThreadLocalRandom.current();

    private DoubleStream randomPoints(long points, double a, double b) {
        return  rnd.doubles(points)
                .limit(points)
                        .map(d -> a + d * (b - a));
    }

    public static void main(String[] args) throws SecurityException, IOException {
        DoubleUnaryOperator du = x -> (x * Math.sqrt(23.35 * x * x) / Math.sqrt(34.54653324234324 * x) / Math.sqrt(213.3123)) * Math.sqrt(1992.34513213124 / x) / 88392.3 * x + 3.234324;


        for (int i=0 ; i < 2; i++){
            int j = i ;
            new Thread(() -> {
                TestCalculate test = new TestCalculate();
                int x = 0;
                System.out.println("Thread "+j+" start");
                long start = System.currentTimeMillis();
                while (x++ < 4) {
                    double d = test.randomPoints(500_000_000l, 2, 10).map(du).sum();
                    long end = (System.currentTimeMillis() - start) / 1000;
                    System.out.println("Thread "+j+" time: "+end+" seconds, result: "+d);
                    start = System.currentTimeMillis();
                }
            }).start();

            try {
                Thread.sleep(40_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

最佳答案

在亚马逊上 instance types page你找到这张便条:

Each vCPU is a hyperthread of an Intel Xeon core except for T2.

由于您的 c4.large 实例有 2 个 vCPU,您真正得到的是单个 CPU 核心的两个超线程,而不是两个独立的核心。鉴于此,完全可以预期运行两个线程不会使吞吐量翻倍,因为两个线程都在争夺同一核心上的资源。添加第二个线程时,您看到吞吐量增加了约 53%,这实际上意味着这段代码对超线程非常友好,因为第二个超线程的平均加速通常被认为在 30% 的范围内。

您可以在本地重现此结果,但在我的 Skylake CPU 上,超线程惩罚显然要低得多。当我运行 slightly modified version0 TestCalculate 通过将它限制在我的 4 核、8 超线程上的两个不同的物理内核,如下所示:

taskset -c 0,1 java stackoverflow.TestCalculate

我得到以下结果:

Thread 0 start
Thread 0: time:  2.21 seconds, result: 161774948.858291
Thread 0: time:  2.18 seconds, result: 161774943.838121
Thread 0: time:  2.18 seconds, result: 161774946.789039
Thread 1 start
Thread 1: time:  2.18 seconds, result: 161774945.535877
Thread 0: time:  2.18 seconds, result: 161774947.073892
Thread 1: time:  2.18 seconds, result: 161774937.356786
Thread 0: time:  2.18 seconds, result: 161774940.460682
Thread 1: time:  2.18 seconds, result: 161774944.699141
Thread 0: time:  2.18 seconds, result: 161774941.643486
Thread 0 stop
Thread 1: time:  2.18 seconds, result: 161774943.018521
Thread 1: time:  2.18 seconds, result: 161774941.866168
Thread 1: time:  2.18 seconds, result: 161774944.035612
Thread 1 stop

也就是说,当添加第二个线程时,当每个线程都可以在不同的内核上运行时,会有近似“完美”的扩展:每个线程的性能都相同到小数点后两位。

另一方面,当我运行仅限于相同物理内核1的进程时,如下所示:

taskset -c 0,4 java stackoverflow.TestCalculate

我得到以下结果:

Thread 0 start
Thread 0: time:  2.22 seconds, result: 161774949.278913
Thread 0: time:  2.19 seconds, result: 161774932.329415
Thread 0: time:  2.18 seconds, result: 161774943.604470
Thread 1 start
Thread 0: time:  2.31 seconds, result: 161774951.630203
Thread 1: time:  2.31 seconds, result: 161774951.695466
Thread 0: time:  2.31 seconds, result: 161774939.631680
Thread 1: time:  2.31 seconds, result: 161774943.523282
Thread 0: time:  2.32 seconds, result: 161774948.153244
Thread 0 stop
Thread 1: time:  2.32 seconds, result: 161774956.985513
Thread 1: time:  2.18 seconds, result: 161774950.335522
Thread 1: time:  2.18 seconds, result: 161774941.739148
Thread 1: time:  2.18 seconds, result: 161774946.275329
Thread 1 stop

因此在同一核心上运行时速度降低了 6%。这意味着此代码对超线程非常友好,因为 6% 的减速意味着您通过添加超线程获得了 94% 的 yield ! Skylake 有几个微架构改进,专门帮助超线程场景,这也许可以解释您的 c4.large 结果(Haswell 架构)和我的结果之间的差异。您可以尝试使用 EC2 C5 实例,因为它们使用 Skylake 架构:如果跌幅小得多,则可以证实这一理论。


0 进行了修改,使迭代时间缩短了 10 倍,并在使用单个线程进行 3 次迭代后确定性地启动第二个线程。

1 在我的机器上,逻辑 CPU 0 和 4、1 和 5 等属于同一个物理内核。

关于java - 在 c4.large AWS 实例中运行的 Java 应用程序性能低下,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49845511/

相关文章:

java - 如何在 RecyclerView 的一项中找到两个文本 block ?

Java 8 Lambda - 在顺序读取中按索引透视数据表

java - 批量删除具有给定属性(或多个属性)的所有实体

linux - Linux 上的 DB2 Express

amazon-web-services - Amazon EC2 ELB 警报 - 哪个实例运行状况不佳?

PHP AWS SDK 创建 S3Client 的 fatal error

java - Hibernate 的动态命名表

c++ - Boost (v1.33.1) 线程中断

java - 如何使公共(public)静态非同步 getInstance() 方法将私有(private)静态引用变量的多个实例返回给对象?

c++ - std::condition_variable和std::mutex到底如何工作?