与单线程相比,c++/java的多线程性能结果参差不齐

Odd c++/java multi threading performance results compared to single thread

本文关键字:多线程 性能 结果 参差不齐 java 单线程 c++      更新时间:2023-10-16

从2天起,我就一直在努力理解c++线程池性能与单个线程相比发生了什么,然后我决定在java上也这样做,这时我注意到c++和java上的行为是相同的。。基本上,我的代码很简单。

package com.examples.threading
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
public class ThreadPool {
final static AtomicLong lookups = new AtomicLong(0);
final static AtomicLong totalTime = new AtomicLong(0);
public static class Task implements Runnable 
{
    int start = 0;
    Task(int s) {
        start = s;
    }
    @Override
    public void run() 
    {
       for (int j = start ; j < start + 3000; j++ ) {
        long st = System.nanoTime();
        boolean a = false;
        long et = System.nanoTime();
        totalTime.getAndAdd((et - st));
        lookups.getAndAdd(1l);
       }
    }
}

 public static void main(String[] args) 
{
    // change threads from 1 -> 100 then you will get different numbers
    ExecutorService executor = Executors.newFixedThreadPool(1);
    for (int i = 0; i <= 1000000; i++) 
    {
        if (i % 3000 == 0) {
            Task task = new Task(i);
            executor.execute(task);
            System.out.println("in time " + (totalTime.doubleValue()/lookups.doubleValue()) + " lookups: " + lookups.toString());
        }
    }
    executor.shutdown();
    while (!executor.isTerminated()) {
        ;
    }
    System.out.println("in time " + (totalTime.doubleValue()/lookups.doubleValue()) + " lookups: " + lookups.toString());
}
}

现在,当您使用不同的池号(比如说100个线程)运行相同的代码时,总运行时间将发生变化

一个线程:及时查找36.9149361274451:1002000
100个螺纹:及时查找141.47934530938124:1002000

问题是,代码是一样的,为什么总的运行时间不同,这里到底发生了什么。。

这里有几个明显的可能性。

一种是System.nanoTime可以在内部串行化,因此即使每个线程单独进行调用,它也可以在内部按顺序执行这些调用(例如,在调用到来时对其进行排队)。当nanoTime直接访问硬件时钟时,这种情况尤其可能发生,例如在Windows上(它使用Windows的QueryPerformanceCounter)。

另一个基本上按顺序执行的点是原子变量。尽管您使用的是无锁原子,但基本事实是,每个原子都必须作为原子序列执行读/修改/写。对于锁定的变量,这是通过锁定、读取、修改、写入和解锁来完成的。有了无锁,您可以消除这样做的一些开销,但您仍然面临这样一个事实,即在给定的时间只有一个线程可以成功地读取、修改和写入特定的内存位置。

在这种情况下,每个线程所做的唯一"工作"是琐碎的,永远不会使用结果,因此优化器可以(可能也会)完全消除它。所以你真正测量的是读取时钟和增加变量的时间。

为了至少恢复一些速度,您可以(例如)给线程线程自己的lookupstotalTime变量。然后,当所有线程完成时,您可以将各个线程的值加在一起,以获得每个线程的总值。

防止时序的序列化稍微困难一些(委婉地说)。至少在显而易见的设计中,对nanoTime的每次调用都直接访问一个硬件寄存器,这(至少对于大多数典型的硬件)只能顺序发生。它可以在硬件级别进行修复(提供一个高频定时器寄存器,每个内核可以直接读取,保证在内核之间同步)。这是一项不平凡的任务,(更重要的是)大多数当前的硬件都不包括这样的东西。

除此之外,在每个线程中做一些有意义的工作,这样当你在多个线程中执行时,你就有了东西,它实际上可以使用多个CPU/内核的资源来更快地运行。