Снижение производительности при увеличении количества ядер

Мой mac оснащен 16 ядрами.

System.out.println(Runtime.getRuntime().availableProcessors());  //16

Я запускаю код ниже, чтобы увидеть эффективность использования моих ядер. Поток "CountFileLineThread" просто подсчитывает количество строк в файле (в папке 133 файла)

Я делаю заметки в этой строке:

ExecutorService es = Executors.newFixedThreadPool(NUM_CORES);

Где NUM_CORES находится между 1 и 16.

Вы можете отметить из приведенного ниже результата, что выше 5 ядер, производительность начинает ухудшаться. Я бы не ожидал "продукта уменьшающегося возврата" для 6 ядер и выше (кстати, для 7 ядер он занимает более 22 минут, привет!!!!), Почему мой вопрос?

enter image description here

public class TestCores
{   
  public static void main(String args[]) throws Exception
  {
    long start = System.currentTimeMillis();
    System.out.println("START");

    int NUM_CORES = 1;

    List<File> files = Util.getFiles("/Users/adhg/Desktop/DEST/");
    System.out.println("total files: "+files.size());
    ExecutorService es = Executors.newFixedThreadPool(NUM_CORES);
    List<Future<Integer>> futures = new ArrayList<Future<Integer>>();
    for (File file : files)
    {
        Future<Integer> future = es.submit(new CountFileLineThread(file));
        futures.add(future);
    }

    Integer total = 0;

    for (Future<Integer> future : futures)
    {
        Integer result = future.get();
        total+=result;
        System.out.println("result :"+result);

    }

    System.out.println("----->"+total);

    long end = System.currentTimeMillis();
    System.out.println("END. "+(end-start)/1000.0);
}
}

Ответ 1

Я добавил это как комментарий, но я собираюсь бросить его туда и ответить. Поскольку ваш тест выполняет ввод/вывод файлов, вы, вероятно, попали в точку с этим 6-м потоком, где теперь вы делаете слишком много операций ввода-вывода и тем самым замедляете все. Если вы действительно хотите получить преимущество от 16 ядер, вы должны перезаписать поток чтения файла, чтобы использовать неблокирующий ввод-вывод.

Ответ 2

Моя догадка заключается в том, что вы можете наложить столько нагрузки на дисковый ввод-вывод, что вы все замедлили! См. Производительность ввода/вывода в "Мониторе активности" (если вы находитесь в OSX). В Linux используйте команду vmstat, чтобы получить представление о том, что происходит. [Если вы видите много изменений или высокую скорость чтения/с и записываете /s, тогда вы идете]


Несколько вещей, которые я заметил:

CountFileLineThread не содержится в коде. Пожалуйста, поместите его, чтобы мы могли точно видеть, что происходит.

Далее

for (Future<Integer> future : futures)
{
    Integer result = future.get();
    total+=result;
    System.out.println("result :"+result);

}

Здесь обратите внимание, что вы заблокированы по результату первого Task (future.get()). Между тем другие результаты могут быть уже доступны, но вы не можете их увидеть до тех пор, пока не завершится первое. Используйте CompletionService вместо этого, чтобы получить результаты в порядке их завершения для лучшего измерения. Это не имеет значения, так как вы хотите, чтобы все потоки были выполнены до окончания таймера.

Другой момент: блокировка ввода-вывода - это ключ. Неважно, как много ядер, если задачи заблокированы для ввода-вывода, сети и т.д. Современные процессоры имеют то, что Hyper Threading, и они могут запускать поток, ожидающий запуска, если в настоящее время выполняются блоки потока.

Так, например, если у меня есть 16 ядер, и я создаю 16 потоков, прося их прочитать файлы объемом 1 ГБ, я не получу никаких улучшений производительности, просто имея больше ядер. Узким местом является диск и память.

Ответ 3

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

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