Java 线程工作组性能与嵌入式循环性能

标签 java multithreading collections

我最近正在做一些java性能测试,当我运行这个测试时,我感到非常震惊。我想测试并看看通过在工作组线程中进行统计会得到什么样的性能差异......就在那时我得到了这个非常令人惊讶的结果。

这是测试代码:

import org.joda.time.DateTime;
import org.joda.time.Interval;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;

/**
 * Created by siraj on 1/2/16.
 */
public class WorkerPoolTest {
    int SAMPLE_LIMIT = 1000;
    DecimalFormat df = new DecimalFormat("#.####");

    public static void main(String[] args){

        int nTestElements = 100000;

        System.out.println("\tLinear\t\t\tNon-Linear");
        for (int i = 0;i<25;i++){
//            System.out.println("Linear test " + (i+1));
            System.out.print((i + 1));
            new WorkerPoolTest(false, nTestElements, false);
//            System.out.println("Non-linear test " + (i+1));
            new WorkerPoolTest(true, nTestElements, false);
            System.out.println();
        }


        System.out.println("Done test");
    }

    WorkerPoolTest(boolean useWorkerThreads, int testLimit, boolean outPutSampleResults){
        DateTime start = new DateTime();
//        System.out.println(start);
        startWorkerThreads(useWorkerThreads, testLimit, outPutSampleResults);
        DateTime end = new DateTime();
//        System.out.println(end);
        System.out.print("\t " +
                df.format( ((double) (new Interval(start, end).toDurationMillis()) /1000) ) + "\t\t");
    }

    private void startWorkerThreads(boolean userWorkerThreads, int testLimit, boolean outPutSampleResults){
        ArrayList<WDataObject> data = new ArrayList<>();

        if (userWorkerThreads){

            try {
                // do fast test
                ExecutorService pool = Executors.newFixedThreadPool(6);
                int nSeries = 2;
                Set<Future<WDataObject>> set = new HashSet<>();
                for (int i = 1; i <= testLimit; i ++){
                    Callable worker = new Worker(i);
                    Future<WDataObject> future = pool.submit(worker);
                    set.add(future);
                }
                for (Future<WDataObject> wdo : set){
                        data.add(wdo.get());
                }
                Collections.sort(data);
                if (outPutSampleResults)
                    for (WDataObject ob: data)
                    {
                        System.out.println(ob.toString());
                    }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }else{
            // do linear test.

            for (int i = 1; i <= testLimit; i ++){
                WDataObject ob = new WDataObject(i);
                for (int s = 1; s <= SAMPLE_LIMIT; s++){
                    ob.dataList.add((double)i / (double)s);
                }
                data.add(ob);
            }
            if (outPutSampleResults)
                for (WDataObject ob: data)
                {
                    System.out.println(ob.toString());
                }
        }
    }

    class Worker implements Callable{
        int i;
        Worker(int i){
            this.i = i;
        }

        @Override
        public WDataObject call() throws Exception {
            WDataObject ob = new WDataObject(i);
            for (int s = 1; s <= SAMPLE_LIMIT; s++){
                ob.dataList.add((double)i / (double)s);
            }
            return ob;
        }
    }

    class WDataObject implements Comparable<WDataObject>{
        private final int id;

        WDataObject(int id){
            this.id = id;
        }

        ArrayList<Double> dataList = new ArrayList<>();

        public Integer getID(){
            return id;
        }

        public int getId(){
            return id;
        }

        public String toString(){
            String result = "";
            for (double data: dataList) {
                result += df.format(data) + ",";
            }
            return result.substring(0, result.length()-1);
        }

        @Override
        public int compareTo(WDataObject o) {
            return getID().compareTo(o.getID());
        }
    }
}

这是运行该程序的输出示例...

    Linear      |   Non-Linear
1    45.735     |    15.043     
2    24.732     |    16.559     
3    15.666     |    17.553     
4    18.068     |    17.154     
5    16.446     |    19.036     
6    17.912     |    18.051     
7    16.093     |    17.618     
8    13.185     |    17.2       
9    19.961     |    26.235     
10   16.809     |    17.815     
11   15.809     |    18.098     
12   18.45      |    19.265     

线性计算模型使用单线程时怎么会出现这种情况?另外,我运行了这个测试并观察了我的系统监视器,并注意到在运行单个嵌入式循环时,我的所有计算机核心都以最大强度使用。这里发生了什么?为什么线性计算算法随着后续迭代变得更快,为什么它有时会优于同一作业的线程非线性版本?

此代码示例使用 Joda Time 进行时间戳记。

另外,我很难用这个编辑器放入制表符空格,结果使用了制表符空格。您可以在代码中看到它。

最佳答案

您的测试真正衡量的是......对象分配性能。

每次您执行 ob.dataList.add((double) i / (double) s); 时,您正在自动装箱,并且正在创建一个新的 Double 对象。而且由于您要将其添加到转义本地范围的列表中,因此 HotSpot 编译器无法进行堆栈分配作为优化。所以它必须在堆上分配,这是一个相对昂贵的操作,需要线程之间进行一些协调,因此会降低你的多线程性能。

让你的算法更加真实的第 1 步:替换你的 ArrayList<Double> dataList = new ArrayList<>();与:

double[] dataList = new double[SAMPLE_LIMIT];

之后,您的“非线性”版本的性能始终优于线性版本 2 倍。

其次,除法是一种非常便宜的操作,因此在任何情况下,您主要测量内存写入,并且无论您使用多少个线程,内存总线吞吐量都是有限的。

如果你用这样的代码替换当前的代码:

double sum = 0;
for (int s = 1; s <= SAMPLE_LIMIT; s++) {
    sum += (double) i / (double) s;
}
ob.dataList[0] = sum;

然后您会发现非线性版本的性能比线性版本高出 4 到 6 倍,这正是您对固定大小为 6 的线程池所期望的结果。

关于Java 线程工作组性能与嵌入式循环性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34571927/

相关文章:

php - 在 Laravel 中将数组转换为 Eloquent 模型

java - 更快的javac/ant?

java - 解决向double Arraylist添加新值时Java堆空间错误

java - 在三个不同线程中同步三个步骤?

java - 为什么在单独的线程池中将阻塞操作与非阻塞操作分开是一个更好的主意,而不是在一个线程池中执行所有操作?

android - ORMLite 中的集合

java - JTable 行的默认颜色

java - 在 Java 中将 JSON 转换为 XML

c# - 异步方法在当前线程(主线程)启动AsyncCallback

java - 如何在 Java 中创建一个新列表