Почему многопоточность неэффективна? - PullRequest
10 голосов
/ 03 февраля 2011

Я разработал класс, который заполняет массив целыми числами, используя различное количество потоков, чтобы увидеть силу многопоточности. Но по моему результату его нет ...

Идея : Идея состояла в том, чтобы заполнить массив из 100000000 целых чисел значением "1". Начиная с 1 потока (один поток заполняет весь массив) и увеличивая его до 100 потоков (каждый поток заполняет подмассив размером 100000000 / nbThreads)

Пример : из 10 потоков я создаю 10 потоков, каждый из которых заполняет массив из 10000000 целых чисел.

Вот мой код:

public class ThreadedArrayFilling extends Thread{
    private int start;
    private int partitionSize;
    public static int[] data;
    public static final int SIZE = 100000000;
    public static final int NB_THREADS_MAX = 100;


    public static void main(String[] args){
        data = new int[SIZE];
        long startTime, endTime;
        int partition, startIndex, j;
        ThreadedArrayLookup[] threads;

        for(int i = 1; i <= NB_THREADS_MAX; i++){       
            startTime = System.currentTimeMillis();
            partition = SIZE / i;
            startIndex = 0;
                threads = new ThreadedArrayLookup[i];
            for(j = 0; j < i; j++){         
                threads[j] = new ThreadedArrayLookup(startIndex, partition);
                startIndex += partition;
            }
            for(j = 0; j < i; j++){
                try {
                    threads[j].join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            endTime = System.currentTimeMillis();       
            System.out.println(i + " THREADS: " + (endTime - startTime) + "ms");
        }
    }

    public ThreadedArrayFilling(int start, int size){
        this.start = start;
        this.partitionSize = size;
        this.start();
    }

    public void run(){
        for(int i = 0; i < this.partitionSize; i++){
            data[this.start + i] = 1;
        }
    }

    public static String display(int[] d){
        String s = "[";

        for(int i = 0; i < d.length; i++){
            s += d[i] + ", ";
        }

        s += "]";
        return s;
    }

}

А вот мои результаты:

1 THREADS: 196ms
2 THREADS: 208ms
3 THREADS: 222ms
4 THREADS: 213ms
5 THREADS: 198ms
6 THREADS: 198ms
7 THREADS: 198ms
8 THREADS: 198ms
9 THREADS: 198ms
10 THREADS: 206ms
11 THREADS: 201ms
12 THREADS: 197ms
13 THREADS: 198ms
14 THREADS: 204ms
15 THREADS: 199ms
16 THREADS: 203ms
17 THREADS: 234ms
18 THREADS: 225ms
19 THREADS: 235ms
20 THREADS: 235ms
21 THREADS: 234ms
22 THREADS: 221ms
23 THREADS: 211ms
24 THREADS: 203ms
25 THREADS: 206ms
26 THREADS: 200ms
27 THREADS: 202ms
28 THREADS: 204ms
29 THREADS: 202ms
30 THREADS: 200ms
31 THREADS: 206ms
32 THREADS: 200ms
33 THREADS: 205ms
34 THREADS: 203ms
35 THREADS: 200ms
36 THREADS: 206ms
37 THREADS: 200ms
38 THREADS: 204ms
39 THREADS: 205ms
40 THREADS: 201ms
41 THREADS: 206ms
42 THREADS: 200ms
43 THREADS: 204ms
44 THREADS: 204ms
45 THREADS: 206ms
46 THREADS: 203ms
47 THREADS: 204ms
48 THREADS: 204ms
49 THREADS: 201ms
50 THREADS: 205ms
51 THREADS: 204ms
52 THREADS: 207ms
53 THREADS: 202ms
54 THREADS: 207ms
55 THREADS: 207ms
56 THREADS: 203ms
57 THREADS: 203ms
58 THREADS: 201ms
59 THREADS: 206ms
60 THREADS: 206ms
61 THREADS: 204ms
62 THREADS: 201ms
63 THREADS: 206ms
64 THREADS: 202ms
65 THREADS: 206ms
66 THREADS: 205ms
67 THREADS: 207ms
68 THREADS: 210ms
69 THREADS: 207ms
70 THREADS: 203ms
71 THREADS: 207ms
72 THREADS: 205ms
73 THREADS: 203ms
74 THREADS: 211ms
75 THREADS: 202ms
76 THREADS: 207ms
77 THREADS: 204ms
78 THREADS: 212ms
79 THREADS: 203ms
80 THREADS: 210ms
81 THREADS: 206ms
82 THREADS: 205ms
83 THREADS: 203ms
84 THREADS: 203ms
85 THREADS: 209ms
86 THREADS: 204ms
87 THREADS: 206ms
88 THREADS: 208ms
89 THREADS: 263ms
90 THREADS: 216ms
91 THREADS: 230ms
92 THREADS: 216ms
93 THREADS: 230ms
94 THREADS: 234ms
95 THREADS: 234ms
96 THREADS: 217ms
97 THREADS: 229ms
98 THREADS: 228ms
99 THREADS: 215ms
100 THREADS: 232ms

Что я пропустил?

РЕДАКТИРОВАТЬ: Дополнительная информация:

На моей машине установлено двухъядерное ядро.

Ожидания :

  • Я ожидал увидеть огромное увеличение производительности между 1 и 2 потоками (чтобы использовать двухъядерный)
  • Я также ожидал увидеть замедление после этого для большого количества потоков.

Но это не подтверждает мои ожидания. Мои ожидания неверны, или это проблема с моим алгоритмом?

Ответы [ 4 ]

19 голосов
/ 03 февраля 2011

С двумя ядрами наилучшая производительность, которую вы могли бы ожидать - это 2 потока, занимающих половину времени как один поток.Любые дополнительные потоки только после этого создают бесполезные издержки - при условии, что вы полностью ограничены процессором, но на самом деле это не так.потоки.И причина, вероятно, в том, что ваша программа не связана с процессором, а связана с памятью.Ваше узкое место - доступ к основной памяти, а 2 потока просто по очереди записывают в основную память.Реальные ядра процессора ничего не делают большую часть времени.Вы увидите ожидаемую разницу, если вместо небольшой фактической работы с большой областью памяти вы выполняете большую нагрузку на процессор при небольшом объеме памяти.Потому что тогда каждое ядро ​​процессора может работать полностью внутри своего кеша.

9 голосов
/ 03 февраля 2011

Многопоточность очень эффективна, когда ваше программное обеспечение связано с центральным процессором: есть много приложений, которые являются однопоточными, и вы можете видеть их болезненно недооценивающими современные процессоры, максимизируя использование только одного ядра (это очень четко видно на мониторах процессора).

Однако нет смысла запускать гораздо больше потоков, чем количество доступных (виртуальных) процессоров.

Правильно многопоточные приложения, выполняющие, например, перехват чисел, создают числорабочих потоков, связанных с количеством (виртуальных) процессоров, доступных для JVM.

4 голосов
/ 03 февраля 2011

Задача, которую вы выполняете внутри потока, настолько крошечная, что время, затрачиваемое на это, перевешивается накладными расходами вашей установки.

Выполните некоторые тяжелые вычисления (например, запустите приближение PI для помещения в массив).) вы увидите преимущество многопоточности, но только приблизительно до количества ядер, которые есть на вашей машине.

Или сделать что-то, что ожидает чего-то внешнего (чтение из базы данных, очистка данных с веб-сайта), это может быть более производительным, если другие потоки делают что-то полезное, в то время как другие ждут.

0 голосов
/ 05 февраля 2011

Возможно, что два потока - каждый с собственным процессором или ядром - работают в унисон, чтобы выполнить задачу медленнее, чем если бы только один поток выполнял всю работу.Оба ядра хотят, чтобы их кэши L1 + L2 записывали данные в память, и это нормально.Однако вскоре они насыщают общий кэш L3 таким образом, что он останавливает дополнительные записи до тех пор, пока ему не удастся записать обновленную строку кэша в ОЗУ, тем самым освободив его для приема новых записей.

Другими словами,Цель ваших потоков не в том, чтобы выполнять какую-либо обработку, а в заполнении системной памяти.Оперативная память системы медленная, и, как вы можете видеть, сравнивая результат с одним потоком с результатами для двух потоков, емкость записи в ОЗУ используется одним потоком и поэтому не может быть быстрее с двумя потоками.

Ваши потоки настолько малы, что, по всей вероятности, они будут находиться в кеше L1 и, следовательно, не требуют выборок из системной памяти, что затруднит вашу способность выполнять запись в память.Ваша способность писать в ОЗУ одинакова, независимо от того, пытаетесь ли вы сделать это с 1 или 100 потоками.Однако чем больше у вас потоков, тем больше накладных расходов на администрирование потоков.Это незначительно для нескольких потоков, но увеличивается для каждого дополнительного потока и в конечном итоге станет заметным.

...