Я недавно проводил тестирование производительности 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.

Кроме того, мне трудно вставлять пробелы табуляции в этом редакторе, результаты использовали пробелы табуляции. Вы можете увидеть это в коде.

1
Siraj 3 Янв 2016 в 02:47

2 ответа

Лучший ответ

На самом деле ваш тест измеряет ... производительность размещения объектов.

Каждый раз, когда вы выполняете 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.

1
Erwin Bolwidt 3 Янв 2016 в 03:52

Это не ответ на ваш вопрос, а просто подтверждение того, что я получаю те же результаты.

Я удалил избыточный код и измерил одинаковое время выполнения кода в обоих случаях. Результаты не меняются при нескольких запусках и при обратном порядке, что исключает сборку мусора или любые всплески ЦП, связанные с ОС, во время тестов.

Вот модифицированный код.

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class TestPerf {
    int SAMPLE_LIMIT = 10000;
    DecimalFormat df = new DecimalFormat("#.####");
    public static final int TEST_COUNT = 10;

    public static void main(String[] args) {
        TestPerf main = new TestPerf();
        int nTestElements = 10000;

        System.out.println("\tLinear\t\t\t\tNon-Linear");
        for (int i = 0; i < TEST_COUNT; i++) {
            System.out.print((i + 1));
            main.startWorkerThreads(false, nTestElements, false);
            main.startWorkerThreads(true, nTestElements, false);
            System.out.println("");
        }

        System.out.println("Reversed tests");
        System.out.println("\tNon Linear\t\t\t\tLinear");
        for (int i = 0; i < TEST_COUNT; i++) {
            System.out.print((i + 1));
            main.startWorkerThreads(true, nTestElements, false);
            main.startWorkerThreads(false, nTestElements, false);
            System.out.println("");
        }

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

    private void startWorkerThreads(boolean userWorkerThreads, int testLimit, boolean outPutSampleResults) {

        if (userWorkerThreads) {

            try {
                // do fast test
                ExecutorService pool = Executors.newFixedThreadPool(6);

                Set<Future<Long>> futureSet = new HashSet<Future<Long>>();

                for (int i = 1; i <= testLimit; i++) {
                    Callable<Long> worker = new Worker(i);
                    futureSet.add(pool.submit(worker));
                }

                pool.shutdown();
                pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

                //checking futures after all have returned, don't want to wait on each
                long executionTime = 0;
                for(Future<Long> future : futureSet) {
                    executionTime += future.get();
                }

                System.out.printf("\tnon linear = %f\t", (executionTime / 1e9));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        } else {
            // do linear test.

            long timeDelta = 0;
            for (int i = 1; i <= testLimit; i++) {
                long startTime = System.nanoTime();

                WDataObject ob = new WDataObject(i);
                for (int s = 1; s <= SAMPLE_LIMIT; s++) {
                    ob.dataList.add((double) i / (double) s);
                }               

                long endTime = System.nanoTime();
                timeDelta += (endTime - startTime);
            }
            System.out.printf("\tlinear = %f\t",(timeDelta / 1e9));
        }
    }

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

        @Override
        public Long call() throws Exception {
            long startTime = System.nanoTime();

            WDataObject ob = new WDataObject(i);
            for (int s = 1; s <= SAMPLE_LIMIT; s++) {
                ob.dataList.add((double) i / (double) s);
            }
            long endTime = System.nanoTime();

            return (endTime - startTime);
        }

    }

    class WDataObject implements Comparable<WDataObject> {
        private final int id;
        ArrayList<Double> dataList = new ArrayList<>();

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

        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());
        }
    }
}

Примечание. В ходе этого теста исполнителю было отправлено 10 000 задач, поскольку на большее требуется много времени, но я сомневаюсь, что результаты изменятся.

Вывод

    Linear              Non-Linear
1   linear = 1.261564       non linear = 3.831899   
2   linear = 1.098359       non linear = 3.677221   
3   linear = 1.315108       non linear = 3.542210   
4   linear = 1.267752       non linear = 3.415670   
5   linear = 1.249890       non linear = 3.387447   
6   linear = 1.297200       non linear = 4.244616   
7   linear = 1.328806       non linear = 4.821367   
8   linear = 1.362364       non linear = 4.582840   
9   linear = 1.392996       non linear = 5.169028   
10  linear = 1.319172       non linear = 4.734327   
Reversed tests
    Non Linear              Linear
1   non linear = 5.033875       linear = 1.329440   
2   non linear = 4.547303       linear = 1.291331   
3   non linear = 4.613079       linear = 1.353841   
4   non linear = 4.618064       linear = 1.314747   
5   non linear = 4.580547       linear = 1.313031   
6   non linear = 5.371241       linear = 1.338901   
7   non linear = 5.194418       linear = 1.361951   
8   non linear = 4.521603       linear = 1.251608   
9   non linear = 4.474672       linear = 1.304659   
10  non linear = 4.580605       linear = 1.349442   
Done test

Изменить **

Просто подтверждаю выводы @Erwin Boldwidt

Вот код с массивом double [] вместо ArrayList

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class TestPerf {
    int SAMPLE_LIMIT = 10000;
    DecimalFormat df = new DecimalFormat("#.####");
    public static final int TEST_COUNT = 10;

    public static void main(String[] args) {
        TestPerf main = new TestPerf();
        int nTestElements = 10000;

        System.out.println("\tLinear\t\t\t\tNon-Linear");
        for (int i = 0; i < TEST_COUNT; i++) {
            System.out.print((i + 1));
            main.startWorkerThreads(false, nTestElements, false);
            main.startWorkerThreads(true, nTestElements, false);
            System.out.println("");
        }

        System.out.println("Reversed tests");
        System.out.println("\tNon Linear\t\t\t\tLinear");
        for (int i = 0; i < TEST_COUNT; i++) {
            System.out.print((i + 1));
            main.startWorkerThreads(true, nTestElements, false);
            main.startWorkerThreads(false, nTestElements, false);
            System.out.println("");
        }

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

    private void startWorkerThreads(boolean userWorkerThreads, int testLimit, boolean outPutSampleResults) {

        if (userWorkerThreads) {

            try {
                // do fast test
                ExecutorService pool = Executors.newFixedThreadPool(6);

                Set<Future<Long>> futureSet = new HashSet<Future<Long>>();

                for (int i = 1; i <= testLimit; i++) {
                    Callable<Long> worker = new Worker(i);
                    futureSet.add(pool.submit(worker));
                }

                pool.shutdown();
                pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);

                //checking futures after all have returned, don't want to wait on each
                long executionTime = 0;
                for(Future<Long> future : futureSet) {
                    executionTime += future.get();
                }

                System.out.printf("\tnon linear = %f\t", (executionTime / 1e9));
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        } else {
            // do linear test.

            long timeDelta = 0;
            for (int i = 1; i <= testLimit; i++) {
                long startTime = System.nanoTime();

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

                long endTime = System.nanoTime();
                timeDelta += (endTime - startTime);
            }
            System.out.printf("\tlinear = %f\t",(timeDelta / 1e9));
        }
    }

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

        @Override
        public Long call() throws Exception {
            long startTime = System.nanoTime();

            WDataObject ob = new WDataObject(i);
            for (int s = 1; s <= SAMPLE_LIMIT; s++) {
                ob.dataList[s-1] = (double) i / (double)s;
            }
            long endTime = System.nanoTime();

            return (endTime - startTime);
        }

    }

    class WDataObject implements Comparable<WDataObject> {
        private final int id;
        double[] dataList = new double[SAMPLE_LIMIT];

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

        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   linear = 0.954303       non linear = 1.582391   
2   linear = 0.926418       non linear = 1.581830   
3   linear = 0.600321       non linear = 1.454271   
4   linear = 0.599520       non linear = 1.606025   
5   linear = 0.608767       non linear = 1.529756   
6   linear = 0.592436       non linear = 1.546165   
7   linear = 0.587736       non linear = 1.525757   
8   linear = 0.593176       non linear = 1.599800   
9   linear = 0.586822       non linear = 1.452616   
10  linear = 0.613389       non linear = 1.497857   
Reversed tests
    Non Linear              Linear
1   non linear = 1.654733       linear = 0.591032   
2   non linear = 1.554027       linear = 0.600774   
3   non linear = 1.492715       linear = 0.587769   
4   non linear = 1.574326       linear = 0.603979   
5   non linear = 1.536751       linear = 0.590862   
6   non linear = 1.628588       linear = 0.585333   
7   non linear = 1.591440       linear = 0.604465   
8   non linear = 1.444600       linear = 0.587350   
9   non linear = 1.562186       linear = 0.607937   
10  non linear = 1.559000       linear = 0.586294   
Done test

Сейчас нелинейная деталь работает примерно в 3 раза быстрее линейной.

0
11thdimension 3 Янв 2016 в 05:27