我试图弄清楚如何从多线程应用程序中获得最大性能 我有一个我创建的线程池:
ExecutorService executor = Executors.newFixedThreadPool(8); // I have 8 CPU cores.
我的问题是,我应该将工作分成只有8个runnables / callables,这与线程池中的线程数相同,还是应该将它分成1000000 runnables / callables呢?
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 < 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
分为1000000个callables对我来说似乎较慢,因为实例化所有这些callables并从for循环中收集它们的结果。另一方面,如果我有8个callables,这个开销可以忽略不计。因为我只有8个线程,所以我不能同时运行1000000个callable,所以从那里没有性能提升。
我是对还是错?
BTW我可以测试这些情况,但操作非常简单,我想编译器意识到并进行了一些优化。所以结果可能会产生误导。我想知道哪种方法更适合图像处理应用程序。
答案 0 :(得分:5)
这个问题没有直接的答案,因为它取决于你的代码,应用程序loigc,max,可能的并发性,hw等很多东西。
但是在考虑并发时,你应该考虑以下事项,
线程应该执行独立且并行的任务。
找出可以实际并行执行的代码补丁,没有任何依赖性,否则线程无法帮助
什么是硬件配置?
您可以实现的线程的最大并发执行数等于总数。的cpu核心。如果你少了没有。核心和巨大的没有。然后切换任务比实际线程更活跃(使用cpu)。这可能会严重影响性能
总而言之,你的第二种方法看起来对我很好,但如果可能的话,找出更多的并行性,你可以将它扩展到20-30。
答案 1 :(得分:4)
这个问题有两个方面。
首先你有技术Java的东西。正如你对此有一些答案,我将总结这些基础知识:
Thread
应该完成比任务所需的工作更多的工作,即将N个线程计数到10会慢得多,因为创建和管理额外Threads
的开销高于平行计数到10的好处Threads
调用synchronized
增量方法会慢得多Threads
占用资源,最常见的是内存。你拥有的线程越多,估计你的内存使用就越困难,并且可能会影响GC的时间安排(很少见,但我已经看到它发生了)。其次你有调度理论。你需要考虑你的程序在做什么
Threads
来阻止I / O操作。如果您可以将CPU用于其他任务,则不希望编程等待网络或HDD a
,您很可能不关心平均响应时间)Futures
池时必须小心。使用多个池可能会导致死锁(如果在两个池之间引入了依赖关系)并使其难以优化(可以在池之间创建争用并且可能无法正确地调整大小)- 编辑 -
最后,如果它有所帮助,我对性能的看法是我有4个主要资源:CPU,RAM,磁盘和放大器。网络。我试图找出哪个是我的瓶颈,并使用非饱和资源进行优化。例如,如果我有大量空闲CPU和低内存,我可能会压缩我的内存数据。如果我有大量磁盘I / O和大内存,请缓存更多数据。如果网络资源(不是实际的网络连接)很慢,则使用许多线程进行并行化。一旦您在关键路径上使资源类型饱和并且无法使用其他资源来加速它,您就达到了最高性能,并且需要升级H / W以获得更快的结果。
答案 2 :(得分:1)
也许这段代码有帮助。它将使用fork-join池计算斐波纳契数。使用fork-join,我们可以递归地细分问题并组合每个递归级别的结果。从理论上讲,我们可以在fork-join池中递归到fib(0),但这样效率很低。因此,我们引入一个递归限制,我们停止细分任务并计算当前任务中的其余部分。此代码将记录fib(x)所用的时间,并计算每个fib(n)的单线程时间,n为x。对于每个递归限制,它将平均测量创建的任务数和每次运行的时长。
通常情况下,最佳点是任务大小超过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;
}
}