Java 哪个更快?在更多可运行程序中减少工作量,还是在更少可运行程序中增加工作量?(执行人服务)

Java 哪个更快?在更多可运行程序中减少工作量,还是在更少可运行程序中增加工作量?(执行人服务),java,multithreading,performance,concurrency,threadpool,Java,Multithreading,Performance,Concurrency,Threadpool,我正试图找出如何从多线程应用程序中获得最大性能。 我创建了一个线程池,如下所示: ExecutorService executor = Executors.newFixedThreadPool(8); // I have 8 CPU cores. 我的问题是,我应该只将工作划分为8个可运行/可调用项(与线程池中的线程数相同),还是应该将其划分为1000000个可运行/可调用项 for (int i = 0; i < 1000000; i++) { Callable<L

我正试图找出如何从多线程应用程序中获得最大性能。
我创建了一个线程池,如下所示:

ExecutorService executor = Executors.newFixedThreadPool(8); // I have 8 CPU cores.  
我的问题是,我应该只将工作划分为8个可运行/可调用项(与线程池中的线程数相同),还是应该将其划分为1000000个可运行/可调用项

for (int i = 0; i < 1000000; i++) 
{
    Callable<Long> worker = new MyCallable();  // Each worker does little work.
    Future<Long> submit = executor.submit(worker);
}

long sum = 0;

for (Future<Long> future : list) 
    sum += future.get();  // Much more overhead from the for loops
for(int i=0;i<1000000;i++)
{
Callable worker=new MyCallable();//每个worker只做很少的工作。
未来提交=执行人提交(工人);
}
长和=0;
用于(未来:列表)
sum+=future.get();//for循环的开销要大得多

for (int i = 0; i < 8; i++) 
{
    Callable<Long> worker = new MyCallable();  // Each worker does much more work.
    Future<Long> submit = executor.submit(worker);
}

long sum = 0;

for (Future<Long> future : list) 
    sum += future.get();  // Negligible overhead from the for loops
for(int i=0;i<8;i++)
{
Callable worker=new MyCallable();//每个worker都要做更多的工作。
未来提交=执行人提交(工人);
}
长和=0;
用于(未来:列表)
sum+=future.get();//for循环的开销可以忽略不计
划分成1000000个可调用项对我来说似乎比较慢,因为实例化所有这些可调用项并在for循环中从中收集结果会带来开销。另一方面,如果我有8个可调用项,这个开销可以忽略不计。由于我只有8个线程,我不能同时运行1000000个可调用线程,因此没有性能提升

我是对还是错


顺便说一句,我可以测试这些情况,但操作非常简单,我猜编译器会意识到这一点并进行一些优化。因此,结果可能具有误导性。我想知道哪种方法更适合像图像处理应用程序这样的应用程序。

这个问题没有直接的答案,因为它取决于很多因素,如您的代码、应用程序loigc、最大值、可能的并发性、硬件等

但是考虑并发时,你应该考虑下面的事情,

  • 每个可运行的线程都需要一个专用于该线程的堆栈,因此,如果创建大量线程,线程中的内存消耗将超过实际应用程序使用量

  • 线程应该执行独立和并行的任务

    找出可以在没有任何依赖关系的情况下实际并行执行的代码补丁,否则线程将不会有多大帮助

  • 什么是硬件配置

    可以实现的线程的最大并发执行数等于cpu核的总数。若内核数量较少,线程数量巨大,那个么切换任务比实际线程更活跃(使用cpu)。这会严重影响性能


  • 总而言之,您的第二种方法对我来说很好,但如果可能的话,可以找到更多的并行性,您可以将其扩展到20-30。

    也许这段代码有帮助。它将使用fork-join池计算斐波那契数。使用fork-join,我们可以递归地细分一个问题,并组合每个递归级别的结果。从理论上讲,我们可以在fork-join池中递归到fib(0),但这将是低效的。因此,我们引入了一个递归限制,在这里我们停止细分任务,并计算当前任务中的其余部分。此代码将记录fib(x)所用的时间,并计算n到x的每个fib(n)的单线程时间。对于每个递归限制,它将测量创建了多少任务以及每个任务平均运行了多长时间

    通常,最佳点是任务大小大于1µs,但是我们这里的简单斐波那契任务几乎不需要内存/缓存。对于具有更高缓存污染的数据密集型任务,交换机成本更高,并发任务可能会污染共享缓存

    import java.util.concurrent.*;
    import java.util.concurrent.atomic.*;
    
    public class FibonacciFork extends RecursiveTask<Long> {
    
        private static final long serialVersionUID = 1L;
    
        public FibonacciFork( long n) {
            super();
            this.n = n;
        }
    
        static ForkJoinPool fjp = new ForkJoinPool( Runtime.getRuntime().availableProcessors());
    
        static long fibonacci0( long n) {
            if ( n < 2) {
                return n;
            }
            return fibonacci0( n - 1) + fibonacci0( n - 2);
        }
    
        static int  rekLimit = 8;
        private static long stealCount;
        long    n;
        private long forkCount;
        private static AtomicLong forks = new AtomicLong( 0);
    
        static class Result {
            long    durMS;
            int rekLimit;
        }
    
        public static void main( String[] args) {
    
            int fiboArg = 49;
            BenchLogger.sysinfo( "Warmup");
            long    singleNS[] = getSingleThreadNanos( 20, 5e9);
            BenchLogger.sysinfo( "Warmup complete");
            singleNS = getSingleThreadNanos( fiboArg, 1e9);
            BenchLogger.sysinfo( "Single Thread Times complete");
            Result[] results = new Result[ fiboArg + 1];
            for ( int rekLimit = 2;  rekLimit <= fiboArg;  rekLimit++) {
                results[ rekLimit] = new Result();
                runWithRecursionLimit( rekLimit, fiboArg, singleNS[ rekLimit], results[ rekLimit]);
            }
            System.out.println( "CSV results for Fibo " + fiboArg + "\n" + "RekLimit\t" + "Jobs ns\t" + "time ms");
            for ( int rekLimit = 2;  rekLimit <= fiboArg;  rekLimit++) {
                System.out.println( rekLimit + "\t" + singleNS[ rekLimit] + "\t" + results[ rekLimit].durMS);
            }
        }
    
        private static long[] getSingleThreadNanos( final int n, final double minRuntimeNS) {
            final long timesNS[] = new long[ n + 1];
            ExecutorService es = Executors.newFixedThreadPool( Math.max( 1, Runtime.getRuntime().availableProcessors() / 8));
            for ( int i = 2;  i <= n;  i++) {
                final int arg = i;
                Runnable runner = new Runnable() {
                    @Override
                    public void run() {
                        long    start = System.nanoTime();
                        long result = fibonacci0( arg);
                        long    end = System.nanoTime();
                        double  durNS = end - start;
                        long        ntimes = 1;
                        double fact = 1;
                        while ( durNS < minRuntimeNS) {
                            long    oldNTimes = ntimes;
                            if ( durNS > 0) {
                                ntimes = Math.max( 1, ( long) ( oldNTimes * fact * minRuntimeNS / durNS));
                            } else {
                                ntimes *= 2;
                            }
                            start = System.nanoTime();
                            for ( long i = 0;  i < ntimes;  i++) {
                                result = fibonacci0( arg);
                            }
                            end = System.nanoTime();
                            durNS = end - start;
                            fact *= 1.1;
                        }
                        timesNS[ arg] = ( long) ( durNS / ntimes);
                        System.out.println( "Single Fib(" + arg + ")=" + result + " in " + ( timesNS[ arg] / 1e6) + "ms (" + ntimes + " loops in " + (durNS / 1e6)
                                + " ms)");
                    }
                };
                es.execute( runner);
            }
            es.shutdown();
            try {
                es.awaitTermination( 1, TimeUnit.HOURS);
            } catch ( InterruptedException e) {
                BenchLogger.sysinfo( "Single Timeout");
            }
            return timesNS;
        }
    
        private static void runWithRecursionLimit( int r, int arg, long singleThreadNanos, Result result) {
            rekLimit = r;
            long    start = System.currentTimeMillis();
            long    fiboResult = fibonacci( arg);
            long    end = System.currentTimeMillis();
            // Steals zählen
            long    currentSteals = fjp.getStealCount();
            long    newSteals = currentSteals - stealCount;
            stealCount = currentSteals;
            long    forksCount = forks.getAndSet( 0);
            final long durMS = end-start;
            System.out.println( "Fib(" + arg + ")=" + fiboResult + " in " + durMS + "ms, recursion limit: " + r +
                    " at " + ( singleThreadNanos / 1e6) + "ms, steals: " + newSteals + " forks " + forksCount);
            result.durMS = durMS;
            result.rekLimit = r;
        }
    
        static long fibonacci( final long arg) {
            FibonacciFork   task = new FibonacciFork( arg);
            long result = fjp.invoke( task);
            forks.set( task.forkCount);
            return result;
        }
    
        @Override
        protected Long compute() {
            if ( n <= rekLimit) {
                return fibonacci0( n);
            }
            FibonacciFork   ff1 = new FibonacciFork( n-1);
            FibonacciFork   ff2 = new FibonacciFork( n-2);
            ff1.fork();
            long    r2 = ff2.compute();
            long    r1 = ff1.join();
            forkCount = ff2.forkCount + ff1.forkCount + 1;
            return r1 + r2;
        }
    }
    
    import java.util.concurrent.*;
    导入java.util.concurrent.atomic.*;
    公共类FibonacciFork扩展递归任务{
    私有静态最终长serialVersionUID=1L;
    公共腓肠神经分叉(长n){
    超级();
    这个,n=n;
    }
    静态ForkJoinPool fjp=新的ForkJoinPool(Runtime.getRuntime().availableProcessors());
    静态长光纤通道0(长n){
    if(n<2){
    返回n;
    }
    返回fibonacci0(n-1)+fibonacci0(n-2);
    }
    静态int-rekLimit=8;
    私人静态长时间抢购;
    长n;
    私人长叉计数;
    私有静态原子长分叉=新原子长(0);
    静态类结果{
    长杜姆斯;
    int rekLimit;
    }
    公共静态void main(字符串[]args){
    int-fiboArg=49;
    BenchLogger.sysinfo(“预热”);
    long singleNS[]=getSingleThreadNanos(20,5e9);
    BenchLogger.sysinfo(“预热完成”);
    singleNS=getSingleThreadNanos(fiboArg,1e9);
    sysinfo(“单线程完成时间”);
    结果[]结果=新结果[fiboArg+1];
    
    对于(int-rekLimit=2;rekLimit,这个问题有两个方面

    首先是Java技术方面的知识。由于您对此有一些答案,我将总结以下基本知识:

    • 如果您有N个内核,那么N个线程将为您提供最佳结果,只要每个任务仅限于CPU(即不涉及i/O)
    • 每个
      线程
      所做的工作应该比任务所需的工作要多,即N个线程计数到10会慢得多,因为创建和管理额外
      线程
      的开销比并行计数到10的好处要高
    • 您需要确保任何同步开销都低于正在完成的工作,即使用N个
      线程
      调用
      同步
      增量方法会慢得多
    • 线程
      确实会占用资源,最常见的是内存。线程越多,就越难估计内存使用情况,可能会影响GC计时(很少,但我见过这种情况发生)
    其次,你有调度理论。你需要考虑你的程序在做什么?
    • 通常使用
      线程