除非将 Thread.sleep(1) 放入线程的 运行() 方法中,否则超过 2 个线程的工作速度比 1 或 2 个线程慢
More than 2 threads working slower than 1 or 2 threads unless Thread.sleep(1) is put in the run() method of a thread
我要执行的任务是使用多个线程在设定的时间间隔内查找数字的 Collatz 序列,并查看与一个线程相比有多少改进。
然而,无论我选择 2 个线程,一个线程总是更快(编辑。2 个线程更快,但不是很多,而 4 个线程比 1 个线程慢,我不知道为什么。(我什至可以说线程越多越慢)。我希望有人能解释一下。也许我做错了什么。
下面是我到目前为止编写的代码。我正在使用 ThreadPoolExecutor 来执行任务(一个任务 = 一个 Collatz 序列用于间隔中的一个数字)。
科拉茨 class:
public class ParallelCollatz implements Runnable {
private long result;
private long inputNum;
public long getResult() {
return result;
}
public void setResult(long result) {
this.result = result;
}
public long getInputNum() {
return inputNum;
}
public void setInputNum(long inputNum) {
this.inputNum = inputNum;
}
public void run() {
//System.out.println("number:" + inputNum);
//System.out.println("Thread:" + Thread.currentThread().getId());
//int j=0;
//if(Thread.currentThread().getId()==11) {
// ++j;
// System.out.println(j);
//}
long result = 1;
//main recursive computation
while (inputNum > 1) {
if (inputNum % 2 == 0) {
inputNum = inputNum / 2;
} else {
inputNum = inputNum * 3 + 1;
}
++result;
}
// try {
//Thread.sleep(10);
//} catch (InterruptedException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
//}
this.result=result;
return;
}
}
和主要的 class 我 运行 线程(是的,现在我创建了两个具有相同数字的列表,因为在 运行 一个线程之后初始值丢失了):
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
ThreadPoolExecutor executor2 = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);
List<ParallelCollatz> tasks = new ArrayList<ParallelCollatz>();
for(int i=1; i<=1000000; i++) {
ParallelCollatz task = new ParallelCollatz();
task.setInputNum((long)(i+1000000));
tasks.add(task);
}
long startTime = System.nanoTime();
for(int i=0; i<1000000; i++) {
executor.execute(tasks.get(i));
}
executor.shutdown();
boolean tempFirst=false;
try {
tempFirst =executor.awaitTermination(5, TimeUnit.HOURS);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("tempFirst " + tempFirst);
long endTime = System.nanoTime();
long durationInNano = endTime - startTime;
long durationInMillis = TimeUnit.NANOSECONDS.toMillis(durationInNano); //Total execution time in nano seconds
System.out.println("laikas " +durationInMillis);
List<ParallelCollatz> tasks2 = new ArrayList<ParallelCollatz>();
for(int i=1; i<=1000000; i++) {
ParallelCollatz task = new ParallelCollatz();
task.setInputNum((long)(i+1000000));
tasks2.add(task);
}
long startTime2 = System.nanoTime();
for(int i=0; i<1000000; i++) {
executor2.execute(tasks2.get(i));
}
executor2.shutdown();
boolean temp =false;
try {
temp=executor2.awaitTermination(5, TimeUnit.HOURS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("temp "+ temp);
long endTime2 = System.nanoTime();
long durationInNano2 = endTime2 - startTime2;
long durationInMillis2 = TimeUnit.NANOSECONDS.toMillis(durationInNano2); //Total execution time in nano seconds
System.out.println("laikas2 " +durationInMillis2);
例如 运行 一个线程在 3280 毫秒内完成。 运行 两个线程 3437 毫秒。我是否应该考虑另一种并发结构来计算每个元素?
编辑
澄清。我不是要并行化单个序列,而是当每个数字都有它的序列时的数字间隔。(这与其他数字无关)
EDIT2
今天我运行在一台6核12逻辑处理器的好PC上运行程序,问题依旧。有谁知道问题出在哪里?我也更新了我的代码。由于某种原因,4 个线程的性能比 2 个线程差。(甚至比 1 个线程差)。我也应用了答案中给出的内容,但没有改变。
另一个编辑
我注意到,如果我在我的 ParallelCollatz 方法中放置一个 Thread.sleep(1),那么性能会随着线程数的增加而逐渐增加。也许这个细节告诉某人哪里出了问题?但是,如果没有 Thread.Sleep(1) 2 个线程执行最快,1 个线程排在第 2 位,而其他线程则挂起相似的毫秒数,但比 1 个和 2 个线程都慢。
新编辑
我还尝试在 Runnable class 的 运行() 方法中放置更多任务(用于计算不是 1 而是 10 或 100 Collatz 序列的循环),以便线程本身可以做更多的工作。不幸的是,这也没有帮助。
也许我错误地启动了任务?有人有什么想法吗?
编辑
因此,似乎在向 运行 方法添加更多任务后稍微修复了它,但对于更多线程,问题仍然存在 8+。我仍然想知道造成这种情况的原因是创建和 运行 线程比执行任务需要更多时间吗?或者我应该用这个问题创建一个新的 post 吗?
你不是在等待你的任务完成,只是测量将它们提交给执行者所花费的时间。
executor.shutdown()
不等待所有任务获取 finished.You 之后需要调用 executor.awaitTermination
.
executor.shutdown();
executor.awaitTermination(5, TimeUnit.HOURS);
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#shutdown()
更新
我认为我们的测试方法存在缺陷。我在我的机器上重复了你的测试,(1 个处理器,2 个内核,4 个逻辑处理器)并且从 运行 运行 测量的时间差异很大。
我认为主要有以下原因:
- JVM 启动和 JIT 编译时间。一开始,代码是运行ning在解释模式下。
- 忽略计算结果。我不知道 JIT 删除了什么以及我们实际测量的是什么。
- 代码中的打印行
为了对此进行测试,我将您的测试转换为 JMH。
特别是:
- 我将 运行nable 转换为可调用的,并且我 return 结果的总和以防止内联(或者,您可以使用 JMH 的 BlackHole)
- 我的任务没有状态,我将所有移动部件移至局部变量。不需要 GC 来清理任务。
- 我仍然在每一轮中创建执行者。这并不完美,但我决定保持原样。
下面得到的结果和我的预期一致:一个核心在主线程等待,工作在一个核心上进行,数量大致相同。
Benchmark Mode Cnt Score Error Units
SpeedTest.multipleThreads avgt 20 559.996 ± 20.181 ms/op
SpeedTest.singleThread avgt 20 562.048 ± 16.418 ms/op
更新代码:
public class ParallelCollatz implements Callable<Long> {
private final long inputNumInit;
public ParallelCollatz(long inputNumInit) {
this.inputNumInit = inputNumInit;
}
@Override
public Long call() {
long result = 1;
long inputNum = inputNumInit;
//main recursive computation
while (inputNum > 1) {
if (inputNum % 2 == 0) {
inputNum = inputNum / 2;
} else {
inputNum = inputNum * 3 + 1;
}
++result;
}
return result;
}
}
和基准测试本身:
@State(Scope.Benchmark)
public class SpeedTest {
private static final int NUM_TASKS = 1000000;
private static List<ParallelCollatz> tasks = buildTasks();
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@SuppressWarnings("unused")
public long singleThread() throws Exception {
ThreadPoolExecutor executorOneThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
return measureTasks(executorOneThread, tasks);
}
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@SuppressWarnings("unused")
public long multipleThreads() throws Exception {
ThreadPoolExecutor executorMultipleThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
return measureTasks(executorMultipleThread, tasks);
}
private static long measureTasks(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
long sum = runTasksInExecutor(executor, tasks);
return sum;
}
private static long runTasksInExecutor(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
List<Future<Long>> futures = new ArrayList<>(NUM_TASKS);
for (int i = 0; i < NUM_TASKS; i++) {
Future<Long> f = executor.submit(tasks.get(i));
futures.add(f);
}
executor.shutdown();
boolean tempFirst = false;
try {
tempFirst = executor.awaitTermination(5, TimeUnit.HOURS);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
long sum = 0l;
for (Future<Long> f : futures) {
sum += f.get();
}
//System.out.println(sum);
return sum;
}
private static List<ParallelCollatz> buildTasks() {
List<ParallelCollatz> tasks = new ArrayList<>();
for (int i = 1; i <= NUM_TASKS; i++) {
ParallelCollatz task = new ParallelCollatz((long) (i + NUM_TASKS));
tasks.add(task);
}
return tasks;
}
}
我要执行的任务是使用多个线程在设定的时间间隔内查找数字的 Collatz 序列,并查看与一个线程相比有多少改进。
然而,无论我选择 2 个线程,一个线程总是更快(编辑。2 个线程更快,但不是很多,而 4 个线程比 1 个线程慢,我不知道为什么。(我什至可以说线程越多越慢)。我希望有人能解释一下。也许我做错了什么。
下面是我到目前为止编写的代码。我正在使用 ThreadPoolExecutor 来执行任务(一个任务 = 一个 Collatz 序列用于间隔中的一个数字)。
科拉茨 class:
public class ParallelCollatz implements Runnable {
private long result;
private long inputNum;
public long getResult() {
return result;
}
public void setResult(long result) {
this.result = result;
}
public long getInputNum() {
return inputNum;
}
public void setInputNum(long inputNum) {
this.inputNum = inputNum;
}
public void run() {
//System.out.println("number:" + inputNum);
//System.out.println("Thread:" + Thread.currentThread().getId());
//int j=0;
//if(Thread.currentThread().getId()==11) {
// ++j;
// System.out.println(j);
//}
long result = 1;
//main recursive computation
while (inputNum > 1) {
if (inputNum % 2 == 0) {
inputNum = inputNum / 2;
} else {
inputNum = inputNum * 3 + 1;
}
++result;
}
// try {
//Thread.sleep(10);
//} catch (InterruptedException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
//}
this.result=result;
return;
}
}
和主要的 class 我 运行 线程(是的,现在我创建了两个具有相同数字的列表,因为在 运行 一个线程之后初始值丢失了):
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(1);
ThreadPoolExecutor executor2 = (ThreadPoolExecutor)Executors.newFixedThreadPool(4);
List<ParallelCollatz> tasks = new ArrayList<ParallelCollatz>();
for(int i=1; i<=1000000; i++) {
ParallelCollatz task = new ParallelCollatz();
task.setInputNum((long)(i+1000000));
tasks.add(task);
}
long startTime = System.nanoTime();
for(int i=0; i<1000000; i++) {
executor.execute(tasks.get(i));
}
executor.shutdown();
boolean tempFirst=false;
try {
tempFirst =executor.awaitTermination(5, TimeUnit.HOURS);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("tempFirst " + tempFirst);
long endTime = System.nanoTime();
long durationInNano = endTime - startTime;
long durationInMillis = TimeUnit.NANOSECONDS.toMillis(durationInNano); //Total execution time in nano seconds
System.out.println("laikas " +durationInMillis);
List<ParallelCollatz> tasks2 = new ArrayList<ParallelCollatz>();
for(int i=1; i<=1000000; i++) {
ParallelCollatz task = new ParallelCollatz();
task.setInputNum((long)(i+1000000));
tasks2.add(task);
}
long startTime2 = System.nanoTime();
for(int i=0; i<1000000; i++) {
executor2.execute(tasks2.get(i));
}
executor2.shutdown();
boolean temp =false;
try {
temp=executor2.awaitTermination(5, TimeUnit.HOURS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("temp "+ temp);
long endTime2 = System.nanoTime();
long durationInNano2 = endTime2 - startTime2;
long durationInMillis2 = TimeUnit.NANOSECONDS.toMillis(durationInNano2); //Total execution time in nano seconds
System.out.println("laikas2 " +durationInMillis2);
例如 运行 一个线程在 3280 毫秒内完成。 运行 两个线程 3437 毫秒。我是否应该考虑另一种并发结构来计算每个元素?
编辑 澄清。我不是要并行化单个序列,而是当每个数字都有它的序列时的数字间隔。(这与其他数字无关)
EDIT2
今天我运行在一台6核12逻辑处理器的好PC上运行程序,问题依旧。有谁知道问题出在哪里?我也更新了我的代码。由于某种原因,4 个线程的性能比 2 个线程差。(甚至比 1 个线程差)。我也应用了答案中给出的内容,但没有改变。
另一个编辑 我注意到,如果我在我的 ParallelCollatz 方法中放置一个 Thread.sleep(1),那么性能会随着线程数的增加而逐渐增加。也许这个细节告诉某人哪里出了问题?但是,如果没有 Thread.Sleep(1) 2 个线程执行最快,1 个线程排在第 2 位,而其他线程则挂起相似的毫秒数,但比 1 个和 2 个线程都慢。
新编辑 我还尝试在 Runnable class 的 运行() 方法中放置更多任务(用于计算不是 1 而是 10 或 100 Collatz 序列的循环),以便线程本身可以做更多的工作。不幸的是,这也没有帮助。 也许我错误地启动了任务?有人有什么想法吗?
编辑 因此,似乎在向 运行 方法添加更多任务后稍微修复了它,但对于更多线程,问题仍然存在 8+。我仍然想知道造成这种情况的原因是创建和 运行 线程比执行任务需要更多时间吗?或者我应该用这个问题创建一个新的 post 吗?
你不是在等待你的任务完成,只是测量将它们提交给执行者所花费的时间。
executor.shutdown()
不等待所有任务获取 finished.You 之后需要调用 executor.awaitTermination
.
executor.shutdown();
executor.awaitTermination(5, TimeUnit.HOURS);
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#shutdown()
更新 我认为我们的测试方法存在缺陷。我在我的机器上重复了你的测试,(1 个处理器,2 个内核,4 个逻辑处理器)并且从 运行 运行 测量的时间差异很大。
我认为主要有以下原因:
- JVM 启动和 JIT 编译时间。一开始,代码是运行ning在解释模式下。
- 忽略计算结果。我不知道 JIT 删除了什么以及我们实际测量的是什么。
- 代码中的打印行
为了对此进行测试,我将您的测试转换为 JMH。 特别是:
- 我将 运行nable 转换为可调用的,并且我 return 结果的总和以防止内联(或者,您可以使用 JMH 的 BlackHole)
- 我的任务没有状态,我将所有移动部件移至局部变量。不需要 GC 来清理任务。
- 我仍然在每一轮中创建执行者。这并不完美,但我决定保持原样。
下面得到的结果和我的预期一致:一个核心在主线程等待,工作在一个核心上进行,数量大致相同。
Benchmark Mode Cnt Score Error Units
SpeedTest.multipleThreads avgt 20 559.996 ± 20.181 ms/op
SpeedTest.singleThread avgt 20 562.048 ± 16.418 ms/op
更新代码:
public class ParallelCollatz implements Callable<Long> {
private final long inputNumInit;
public ParallelCollatz(long inputNumInit) {
this.inputNumInit = inputNumInit;
}
@Override
public Long call() {
long result = 1;
long inputNum = inputNumInit;
//main recursive computation
while (inputNum > 1) {
if (inputNum % 2 == 0) {
inputNum = inputNum / 2;
} else {
inputNum = inputNum * 3 + 1;
}
++result;
}
return result;
}
}
和基准测试本身:
@State(Scope.Benchmark)
public class SpeedTest {
private static final int NUM_TASKS = 1000000;
private static List<ParallelCollatz> tasks = buildTasks();
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@SuppressWarnings("unused")
public long singleThread() throws Exception {
ThreadPoolExecutor executorOneThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
return measureTasks(executorOneThread, tasks);
}
@Benchmark
@Fork(value = 1, warmups = 1)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@SuppressWarnings("unused")
public long multipleThreads() throws Exception {
ThreadPoolExecutor executorMultipleThread = (ThreadPoolExecutor) Executors.newFixedThreadPool(4);
return measureTasks(executorMultipleThread, tasks);
}
private static long measureTasks(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
long sum = runTasksInExecutor(executor, tasks);
return sum;
}
private static long runTasksInExecutor(ThreadPoolExecutor executor, List<ParallelCollatz> tasks) throws InterruptedException, ExecutionException {
List<Future<Long>> futures = new ArrayList<>(NUM_TASKS);
for (int i = 0; i < NUM_TASKS; i++) {
Future<Long> f = executor.submit(tasks.get(i));
futures.add(f);
}
executor.shutdown();
boolean tempFirst = false;
try {
tempFirst = executor.awaitTermination(5, TimeUnit.HOURS);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
long sum = 0l;
for (Future<Long> f : futures) {
sum += f.get();
}
//System.out.println(sum);
return sum;
}
private static List<ParallelCollatz> buildTasks() {
List<ParallelCollatz> tasks = new ArrayList<>();
for (int i = 1; i <= NUM_TASKS; i++) {
ParallelCollatz task = new ParallelCollatz((long) (i + NUM_TASKS));
tasks.add(task);
}
return tasks;
}
}