【问题标题】:Why are two threads calculating slower than one?为什么两个线程计算比一个慢?
【发布时间】:2015-06-28 22:45:01
【问题描述】:

我正在开发一个用 Java 制作的基准测试应用程序。我在主线程上运行此代码两次,然后在两个单独的线程中运行一次:

if (testing == null) {
    testing = new byte[TEST_SIZE][TEST_SIZE][TEST_SIZE];
}

for (int x = 0; x < TEST_SIZE; x ++) {
    for (int y = 0; y < TEST_SIZE; y ++) {
        for (int z = 0; z < TEST_SIZE; z ++) {
            testing[x][y][z] = (byte)RANDOM.nextInt(100);
        }
    }
}

if (finished == Test.LOOP_COUNT - 1) {
    testing = null;
}

主线程上的任务完成速度比两个线程快得多,如应用程序的输出所示:

Starting test Array Handling with a single core.
Loop #1 finished in 1.820588011 seconds.
Loop #2 finished in 1.779667175 seconds.
Finished in 3 seconds.
Starting test Array Handling with multiple cores.
Loop #2 finished in 9.433253526 seconds.
Loop #1 finished in 9.465652985 seconds.
Finished in 9 seconds.

我在某处读到过一些东西,它说运行非常快的操作的两个线程的性能不如单个线程,但是处理要求更高的操作的两个线程的性能优于单个线程。我不认为是这种情况,因为每个循环都要求很高。我能想到的唯一原因是线程实际上并没有在它们自己的内核上运行。这可能是问题吗?我有一个 2 核 4 线程 Intel Core i7-3537U。

编辑:

测试类:

package net.jibini.park.tests;

import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 *
 * @author zgoethel12
 */
public abstract class Test {

public static final Random RANDOM = new Random();
public static final int LOOP_COUNT = 2;

public static final CopyOnWriteArrayList<Test> tests = new CopyOnWriteArrayList<Test>();

public int finished = 0;
public int longestTime = 0;
public double timeSum = 0;

static {
    RANDOM.setSeed(481923);
    tests.add(new TestArray());
}

public abstract String getName();

public void runTest(final boolean multithread) {

    new Thread(new Runnable() {
            @Override
            public void run() {
                finished = 0;
                longestTime = 0;
                timeSum = 0;

                if (multithread) {
                    for (int i = 0; i < LOOP_COUNT; i ++) {
                        final int f = i;
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                doLoop(f + 1);
                                Thread.currentThread().interrupt();
                            }
                        }).start();
                    }
                } else {
                    for (int i = 0; i < LOOP_COUNT; i ++) {
                        doLoop(i + 1);
                    }
                }

                while (finished < LOOP_COUNT) {
                    System.out.print("");
                }

                System.out.println("Finished in " + (multithread ? longestTime : (int)timeSum) + " seconds.");
                Thread.currentThread().interrupt();
            }
    }).start();

}

public void doLoop(int id) {

    long start = System.nanoTime();
    doTest(id);
    handleLoopFinish(id, start);

}

public abstract void doTest(int id);

public void handleLoopFinish(int id, long start) {

    long current = System.nanoTime();
    long difference = current - start;
    double seconds = (double)difference / 1000000000;
    if (seconds > longestTime) {
        longestTime = (int)seconds;
    }
    timeSum += seconds;
    System.out.println("Loop #" + id + " finished in " + seconds + " seconds.");
    finished ++;

}

}

阵列测试:

package net.jibini.park.tests;

/**
 *
 * @author zgoethel12
 */
public class TestArray extends Test {

public static final int TEST_SIZE = 512;

byte[][][] testing = null;

@Override
public void doTest(int id) {

    if (testing == null) {
        testing = new byte[TEST_SIZE][TEST_SIZE][TEST_SIZE];
    }

    for (int x = 0; x < TEST_SIZE; x ++) {
        for (int y = 0; y < TEST_SIZE; y ++) {
            for (int z = 0; z < TEST_SIZE; z ++) {
                testing[x][y][z] = (byte)RANDOM.nextInt(100);
            }
        }
    }

    if (finished == Test.LOOP_COUNT - 1) {
        testing = null;
    }

}

@Override
public String getName() {
    return "Array Handling";
}

}

【问题讨论】:

  • 显示你的多线程代码。如果您使用相同的方法,但在每个线程上触发它,那么线程必须争取写入 RAM 中相同对象的权利 - 这就是延迟
  • 你的基准很可能是错误的,例如通过忽略预热,尝试使用jmh 看看你是否仍然得到这些结果。
  • 你是如何在两个线程之间拆分作业的?
  • 您的TestArrayTest 类似乎有很多死代码(即未实际使用的变量),JIT 编译器可以简单地消除这些代码。正如@the8472 所建议的那样,对于可靠的微基准测试,您最好使用一个好的框架。

标签: java multithreading cpu-cores


【解决方案1】:

您似乎只使用了一个 RANDOM 对象。恐怕这是在两个线程之间共享的,这可能会使它们变得非常慢。

尝试使用ThreadLocalRandom

【讨论】:

  • 实际上,我正在使用 Java 6,所以我刚刚创建了新实例。
  • 您真的应该考虑升级,尤其是如果您想获得更好的性能 - JVM 内部的许多小改动都会产生巨大的影响。
【解决方案2】:

根据您选择执行多线程的库,启动第二个线程会占用大量开销,并且您似乎正在执行的操作(取决于 TEST_SIZE 的大小)可能非常有效地由一个执行CPU,因为您以连续的方式遍历内存中的数组。

我在课程作业中被告知,内置的 Thread Java 库比 ForkJoin 框架的开销要大得多,因此使用该库可能会获得更多预期的结果。

作为资源,这是我在并发课程中使用的一个不错的网站: http://homes.cs.washington.edu/~djg/teachingMaterials/spac/grossmanSPAC_forkJoinFramework.html

请小心进行热身,如下所述。它们对于确保您可以看到好处至关重要。我们尝试进行约 100 次跑步,其中约 10 次作为热身,以确保您获得一些好的平均值。由于上下文切换/其他计算机进程可能会给您的试验增加可变性,因此多次运行的平均有很大帮助!

【讨论】:

  • 感谢您的回复。我需要将线程作为基准测试的一部分,并且将TEST_SIZE 的值设置得太高会导致内存不足。我专门为它分配了 6 GB 空间。我会调查 ForkJoin。
  • 问题在于使用 Random 的单个实例。我修复了它,处理所需的时间大约减半。
猜你喜欢
  • 2016-10-29
  • 2013-11-25
  • 2014-07-21
  • 2015-03-01
  • 2011-08-14
  • 2019-02-20
  • 2016-03-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多