在多个CPU上进行缩放处理时出现奇怪的行为

时间:2016-03-07 06:40:12

标签: java performance scalability

我正在学习表演,同时在许多CPU上扩展java代码。为此,我编写了一个简单的程序,在一个线程上运行50000个fibonacci,然后在两个线程上运行2 * 50000,在三个线程上运行3 * 50000,依此类推,直到达到目标主机的CPU数量。

这是我的代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiThreadScalability {

    static final int MAX_THREADS = 4;
    static final int NB_RUN_PER_THREAD = 50000;
    static final int FIBO_VALUE = 25;

    public static void main(String[] args) {
        MultiThreadScalability multiThreadScalability = new MultiThreadScalability();
        multiThreadScalability.runTest();
    }


    private void runTest() {
        int availableProcs = Runtime.getRuntime().availableProcessors();
        System.out.println(availableProcs + " processors available");

        for (int i = 1 ; i <= availableProcs ; i++) {
            System.out.println("Running scalability test for " + i + " threads");
            long timeInMillisecs = runTestForThreads(i);
            System.out.println("=> " + timeInMillisecs + " milli-seconds");
        }
    }


    private long runTestForThreads(int threadsNumber) {
        final int nbRun = NB_RUN_PER_THREAD * threadsNumber;
        ExecutorService executor = Executors.newFixedThreadPool(threadsNumber);

        long startTime = System.currentTimeMillis();

        for (int i = 0 ; i < nbRun ; i++) {
            Runnable worker = new Runnable()
            {
                public void run()
                {
                    fibo(FIBO_VALUE);
                }
            };

            executor.execute(worker);
        }

        executor.shutdown();

        while (!executor.isTerminated())
        {}

        return (System.currentTimeMillis() - startTime);
    }


    private static long fibo(int n) {
        if (n < 2) {
            return (n);
        }

        return (fibo(n - 1) + fibo(n - 2));
    }

}

在给定的条件下,我预计 - 与线程数无关 - 执行时间保持不变。

我在充满电的机器上运行它,我有以下输出:

48 processors available
Running scalability test for 1 threads
=> 34199 milli-seconds
Running scalability test for 2 threads
=> 34141 milli-seconds
Running scalability test for 3 threads
=> 34009 milli-seconds
Running scalability test for 4 threads
=> 34000 milli-seconds
Running scalability test for 5 threads
=> 34034 milli-seconds
Running scalability test for 6 threads
=> 34086 milli-seconds
Running scalability test for 7 threads
=> 34094 milli-seconds
Running scalability test for 8 threads
=> 34673 milli-seconds
Running scalability test for 9 threads
=> 35297 milli-seconds
Running scalability test for 10 threads
=> 35486 milli-seconds
Running scalability test for 11 threads
=> 35913 milli-seconds
Running scalability test for 12 threads
=> 36324 milli-seconds
Running scalability test for 13 threads
=> 35722 milli-seconds
Running scalability test for 14 threads
=> 35750 milli-seconds
Running scalability test for 15 threads
=> 35634 milli-seconds
Running scalability test for 16 threads
=> 35970 milli-seconds
Running scalability test for 17 threads
=> 37914 milli-seconds
Running scalability test for 18 threads
=> 36560 milli-seconds
Running scalability test for 19 threads
=> 36720 milli-seconds
Running scalability test for 20 threads
=> 37028 milli-seconds
Running scalability test for 21 threads
=> 37381 milli-seconds
Running scalability test for 22 threads
=> 37529 milli-seconds
Running scalability test for 23 threads
=> 37632 milli-seconds
Running scalability test for 24 threads
=> 39942 milli-seconds
Running scalability test for 25 threads
=> 40090 milli-seconds
Running scalability test for 26 threads
=> 41238 milli-seconds
Running scalability test for 27 threads
=> 42336 milli-seconds
Running scalability test for 28 threads
=> 43377 milli-seconds
Running scalability test for 29 threads
=> 44394 milli-seconds
Running scalability test for 30 threads
=> 46245 milli-seconds
Running scalability test for 31 threads
=> 45928 milli-seconds
Running scalability test for 32 threads
=> 47490 milli-seconds
Running scalability test for 33 threads
=> 47674 milli-seconds
Running scalability test for 34 threads
=> 48775 milli-seconds
Running scalability test for 35 threads
=> 56456 milli-seconds
Running scalability test for 36 threads
=> 50557 milli-seconds
Running scalability test for 37 threads
=> 51393 milli-seconds
Running scalability test for 38 threads
=> 52971 milli-seconds
Running scalability test for 39 threads
=> 53077 milli-seconds
Running scalability test for 40 threads
=> 54015 milli-seconds
Running scalability test for 41 threads
=> 55924 milli-seconds
Running scalability test for 42 threads
=> 55560 milli-seconds
Running scalability test for 43 threads
=> 56554 milli-seconds
Running scalability test for 44 threads
=> 57073 milli-seconds
Running scalability test for 45 threads
=> 65193 milli-seconds
Running scalability test for 46 threads
=> 58549 milli-seconds
Running scalability test for 47 threads
=> 59302 milli-seconds
Running scalability test for 48 threads
=> 60662 milli-seconds

直到24个线程,时间仍然几乎相同。它变得越来越慢You can see it on this graph

我寻求帮助以了解为什么会发生这种“休息”

最后但并非最不重要的是,我运行测试的主机的CPU配置如下:

$ cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 46
model name      : Intel(R) Xeon(R) CPU           E7540  @ 2.00GHz
stepping        : 6
cpu MHz         : 1997.885
cache size      : 18432 KB
physical id     : 0
siblings        : 12
core id         : 0
cpu cores       : 6
apicid          : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 11
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat p
se36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall nx rdtscp lm constant_tsc id
a nonstop_tsc pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr sse4_1 sse4_2 popcnt lah
f_lm
bogomips        : 3995.77
clflush size    : 64
cache_alignment : 64
address sizes   : 44 bits physical, 48 bits virtual
power management: [8]

在这里,我看到核心的实际数量只有6. Runtime.getRuntime()。availableProcessors()不返回pysical CPU的数量,而是返回“超线程”的数量:48

你认为它可以解释我在24个线程中观察到的“突破”吗?

4 个答案:

答案 0 :(得分:4)

在我看来,您的机器有4个Intel E7540 CPU,每个CPU有6个内核和12个线程,总共有24个内核和48个线程。因此它可以同时执行24条指令。

48个线程指的是超线程功能,它是为了充分利用线程必须获取内存以继续时发生的微暂停而构建的。由于您的测试不能访问最里面循环中的任何新内存,因此受限于24个内核。

是的,核心数与线程数解释了它。

答案 1 :(得分:1)

首先,这种类型的特设测量难以正确进行。让我们检查你的方法:

  1. 你在运行什么版本的java?给这个JVM的运行时标志是什么?
  2. Fibonacci可能不是最好的CPU测试,因为它会加载堆栈 - 尽管你已经将它设置为n = 25。此外,您的CPU或JVM可能会自动执行一些优化,因为您一遍又一遍地执行相同的操作。我建议您模块化并测试一些不同的负载生成功能!随机种子矩阵的矩阵乘法是一个合理的想法或一些加密函数,如scrypt哈希相当于/ dev / urandom。
  3. 您是否在多个物理CPU的不同情况下在许多独立运行中看到此中断?这样的1次测试不足以让您质疑曲线的形状。让我们在提问之前尝试10或20个测试(我知道你可能有,但我没有看到数据)。
  4. CPU有很多不同的组件! ALU可以进行简单的数学运算,有几个不同的缓存层,主控制器单元以及可能还有许多其他未知的小位来优化某些操作(嘿,Intell自从我详细研究了这个以来肯定做了一些改进),所有这些都可能导致负载扩展到不同类型的操作。
  5. 操作系统负责调度线程,并且可以使用许多不同的算法来执行此调度功能。图中的任何下降或尖峰都可能是操作系统以不同方式执行此调度的结果(您正在测试极其复杂系统的行为,因此存在此类噪声并非不合理。)

答案 2 :(得分:0)

运行FIBO的Threading任务是如此CPU密集型,在第一个线程之后,其他线程几乎没有机会启动。

如果你要创建它们然后让它们一次启动,你可能会看到一些改进,但我对此表示怀疑。

线程的好处是允许在占用一个特定资源时发生其他处理,但在您的示例中,唯一需要征税的资源是CPU。

答案 3 :(得分:0)

启用超线程后,您将拥有1个运行2&#34;硬件线程的物理核心&#34;并且每个都算作逻辑处理器。

因此,您的机器有24个物理内核和48个逻辑CPU(使用命令lscpu更容易看到)

现在,两个硬件线程共享物理核心的资源。

有些资源是重复,共享和分区的。

它取决于CPU,但通常是寄存器状态和指令队列是重复的,L1高速缓存和执行单元是共享的,存储/重新排序缓冲区是分区的。

通常超线程只有在CPU花费大量时间等待(缓存)内存时才有优势。

操作系统应尽量避免在有可用的physcal核心时加载超线程兄弟。

您的示例是高度CPU密集型的,一旦操作系统开始在同一物理CPU核心上调度两个软件线程,那么2个线程就会开始争夺相同的执行单元,从而降低了可扩展性。