Многопоточность не быстрее, чем в одном потоке (простой цикл цикла) - PullRequest
6 голосов
/ 29 сентября 2010

Я экспериментирую с некоторыми многопоточными конструкциями, но почему-то кажется, что многопоточность не быстрее, чем один поток. Я сузил его до очень простого теста с вложенным циклом (1000x1000), в котором система только считает.
Ниже я опубликовал код как для однопоточной, так и для многопоточности, и как они выполняются.
В результате один поток завершает цикл примерно за 110 мс , тогда как два потока также занимают около 112 мс .
Я не думаю, что проблема заключается в многопоточности. Если я отправляю только один из обоих Runnables в ThreadPoolExecutor, он выполняется за половину времени одного потока, что имеет смысл. Но добавление второго Runnable делает его в 10 раз медленнее. Оба ядра 3,00 ГГц работают на 100%.
Я думаю, что это может быть характерно для ПК, поскольку чей-то компьютер показал результаты с двойной скоростью на многопоточности. Но что я могу с этим сделать? У меня Intel Pentium 4 3,00 ГГц (2 процессора) и Java JRE6.

Тестовый код:

// Single thread:
long start = System.nanoTime(); // Start timer
final int[] i = new int[1];     // This is to keep the test fair (see below)
int i = 0;
for(int x=0; x<10000; x++)
{
    for(int y=0; y<10000; y++)
    {
        i++; // Just counting...
    }
}
int i0[0] = i;
long end = System.nanoTime();   // Stop timer

Этот код выполняется примерно за 110 мс .

// Two threads:

start = System.nanoTime(); // Start timer

// Two of the same kind of variables to count with as in the single thread.
final int[] i1 = new int [1];
final int[] i2 = new int [1];

// First partial task (0-5000)
Thread t1 = new Thread() {
    @Override
    public void run() 
    {
        int i = 0;
        for(int x=0; x<5000; x++)
            for(int y=0; y<10000; y++)
                i++;
        i1[0] = i;
    }
};

// Second partial task (5000-10000)  
Thread t2 = new Thread() {
    @Override
    public void run() 
    {
        int i = 0;
        for(int x=5000; x<10000; x++)
            for(int y=0; y<10000; y++)
                i++;
        int i2[0] = i;
    }
};

// Start threads
t1.start();
t2.start();

// Wait for completion
try{
    t1.join();
    t2.join();
}catch(Exception e){
    e.printStackTrace();
}

end = System.nanoTime(); // Stop timer

Этот код выполняется примерно за 112 мс .

Редактировать: я изменил Runnables на Threads и избавился от ExecutorService (для простоты проблемы).

Редактировать: пробовал некоторые предложения

Ответы [ 6 ]

11 голосов
/ 29 сентября 2010

Вы определенно не хотите продолжать опрос Thread.isAlive() - это сжигает много циклов ЦП без веской причины. Вместо этого используйте Thread.join().

Кроме того, вероятно, не очень хорошая идея, чтобы потоки напрямую увеличивали массивы результатов, строки кеша и все такое. Обновите локальные переменные и сделайте одно хранилище, когда вычисления будут выполнены.

EDIT:

Полностью упущено, что вы используете Pentium 4. Насколько мне известно, многоядерных версий P4 нет - чтобы создать иллюзию многоядерности, он имеет Hyper-Threading : два логические ядра совместно используют исполнительных блоков одного физического ядра . Если ваши потоки зависят от одних и тех же исполнительных блоков, ваша производительность будет такой же (или хуже, чем!) Однопоточной производительности. Например, вам потребуются вычисления с плавающей точкой в ​​одном потоке и целочисленные вычисления в другом, чтобы повысить производительность.

Реализация P4 HT много критиковалась, более новые реализации (последние core2) должны быть лучше.

4 голосов
/ 29 сентября 2010

Попробуйте немного увеличить размер массива. Нет, правда.

Небольшие объекты, размещенные последовательно в одном и том же потоке, будут иметь тенденцию первоначально выделяться последовательно. Это, вероятно, в той же строке кэша. Если у вас два ядра имеют доступ к одной и той же строке кэша (а затем micro-benhcmark, по сути, просто выполняет последовательность записей на один и тот же адрес), им придется бороться за доступ.

В java.util.concurrent есть класс, в котором есть куча неиспользуемых long полей. Их целью является разделение объектов, которые часто используются разными потоками, в разные строки кэша.

2 голосов
/ 29 сентября 2010

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

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

1 голос
/ 29 сентября 2010

Проверено ли количество доступных ядер на вашем компьютере с помощью Runtime.getRuntime (). AvailableProcessors ()?

1 голос
/ 29 сентября 2010

Вы ничего не делаете с i, поэтому ваш цикл, вероятно, просто оптимизирован.

0 голосов
/ 02 августа 2017

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

...