1

我有一些练习,其中一个是关于并发的。这个主题对我来说是新的,但是我花了 6 个小时终于解决了我的问题。但是我对相应API的了解很差,所以我需要建议:我的解决方案是正确的还是可能有更合适的方法。

所以,我必须实现下一个接口:

public interface PerformanceTester {    
    /**
     * Runs a performance test of the given task.
     * @param task which task to do performance tests on
     * @param executionCount how many times the task should be executed in total
     * @param threadPoolSize how many threads to use
     */
    public PerformanceTestResult runPerformanceTest(
            Runnable task,
            int executionCount, 
            int threadPoolSize) throws InterruptedException;
}

其中PerformanceTestResult包含总时间(整个性能测试总共花费了多长时间)、最短时间(最短的单次执行花费了多长时间)和最长时间(最长的单次执行花费了多长时间)。

所以,我今天学到了很多新东西——关于线程池、类型Executors、、等等。ExecutorServiceFutureCompletionService

如果我有 Callable task,我可以做下一个:

  1. 在程序结束时返回当前时间call()
  2. 创建一些数据结构(可能是一些 Map)来存储开始时间和Future对象,由fixedThreadPool.submit(task)(执行此executionCount时间,循环)重新调整;
  3. 执行后,我可以从结束时间中减去开始时间Future

(在Callable 任务的情况下这是正确的方法吗?)

但!我只有Runnable task,所以我继续寻找。我什至创建FutureListener implements Callable<Long>,必须返回时间,何时Future.isDone(),但对我来说接缝有点疯狂(我必须加倍线程数)。

因此,最终我注意到CompletionServicetype 带有有趣的方法take(),它检索并删除代表下一个已完成任务的 Future,如果还没有则等待。,以及使用ExecutorCompletionService的非常好的示例。还有我的解决方案。

public class PerformanceTesterImpl implements PerformanceTester {


@Override
public PerformanceTestResult runPerformanceTest(Runnable task,
        int executionCount, int threadPoolSize) throws InterruptedException {
    long totalTime = 0;
    long[] times = new long[executionCount];

    ExecutorService pool = Executors.newFixedThreadPool(threadPoolSize);

    //create list of executionCount tasks 
    ArrayList<Runnable> solvers = new ArrayList<Runnable>();
    for (int i = 0; i < executionCount; i++) {
        solvers.add(task);
    }

    CompletionService<Long> ecs = new ExecutorCompletionService<Long>(pool);

    //submit tasks and save time of execution start
    for (Runnable s : solvers)
        ecs.submit(s, System.currentTimeMillis());

    //take Futures one by one in order of completing
    for (int i = 0; i < executionCount; ++i) {
        long r = 0;
        try {
            //this is saved time of execution start
            r = ecs.take().get();
        } catch (ExecutionException e) {
            e.printStackTrace();
            return null;
        }
        //put into array difference between current time and start time
        times[i] = System.currentTimeMillis() - r;
        //calculate sum in array
        totalTime += times[i];
    }

    pool.shutdown();
    //sort array to define min and max
    Arrays.sort(times);        

    PerformanceTestResult performanceTestResult = new PerformanceTestResult(
            totalTime, times[0], times[executionCount - 1]);
    return performanceTestResult;
}
}

那么,你能说什么呢?感谢您的回复。

4

2 回答 2

3

我会使用 System.nanoTime() 获得更高分辨率的时间。您可能希望忽略前 10,000 个测试,以确保 JVM 已预热。

我不会费心创建一个可运行列表并将其添加到执行器。相反,我只想将它们添加到执行者中。

使用 Runnable 不是问题,因为您得到了Future<?>回报。

注意:确定任务在队列中花费的时间可能会对时间产生很大影响。而不是从创建任务时开始花费时间,您可以拥有任务时间本身并以纳秒为单位返回 Long 时间。时间安排的完成方式应反映您所考虑的用例。


一种将 Runnable 任务转换为自身计时的简单方法。

finla Runnable run = ...
ecs.submit(new Callable<Long>() {
    public Long call() {
         long start = System.nanoTime();
         run.run();
         return System.nanoTime() - start;
    }
});
于 2013-03-08T22:57:56.440 回答
1

在 JVM 中编写性能测试时有很多错综复杂的地方。您可能并不担心它们,因为这是一个练习,但如果您是这个问题,可能会有更多信息: 如何在 Java 中编写正确的微基准测试?

也就是说,您的代码中似乎没有任何明显的错误。如果您想全面审查您的代码,您可能想在流量较低的代码审查网站上询问:http: //codereview.stackexchange.com

于 2013-03-08T22:59:11.097 回答