Подтвердить что ты не робот

Параллелизация цикла for

У меня есть цикл for, где вычисление на итерации i не зависит от вычислений, выполненных в предыдущих итерациях.

Я хочу распараллелить цикл for (мой код в java), так что вычисление нескольких итераций может выполняться одновременно на нескольких процессорах. Должен ли я создать поток для вычисления каждой итерации, то есть число создаваемых потоков равно числу итераций (число итераций велико в цикле for)? Как это сделать?

4b9b3361

Ответ 1

Вот небольшой пример, который может показаться полезным начать с распараллеливания. Предполагается, что:

  • Вы создаете объект Input, который содержит вход для каждой итерации ваших вычислений.
  • Вы создаете объект Output, который содержит результат вычисления ввода каждой итерации.
  • Вы хотите передать список входов и сразу вернуть список выходов.
  • Ваш ввод - разумный кусок работы, поэтому накладные расходы не слишком высоки.

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

public List<Output> processInputs(List<Input> inputs)
        throws InterruptedException, ExecutionException {

    int threads = Runtime.getRuntime().availableProcessors();
    ExecutorService service = Executors.newFixedThreadPool(threads);

    List<Future<Output>> futures = new ArrayList<Future<Output>>();
    for (final Input input : inputs) {
        Callable<Output> callable = new Callable<Output>() {
            public Output call() throws Exception {
                Output output = new Output();
                // process your input here and compute the output
                return output;
            }
        };
        futures.add(service.submit(callable));
    }

    service.shutdown();

    List<Output> outputs = new ArrayList<Output>();
    for (Future<Output> future : futures) {
        outputs.add(future.get());
    }
    return outputs;
}

Ответ 2

Вы не должны обрабатывать поток вручную. Вместо этого:

  • создайте разумную размерную службу исполнителей пула потоков (если ваши вычисления не делают IO, используйте столько потоков, сколько у вас есть).
  • Запустить цикл, который отправляет каждое отдельное вычисление службе-исполнителю и сохраняет результирующие объекты Future. Обратите внимание: если каждое вычисление состоит только из небольшого количества работы, это создаст много накладных расходов и, возможно, даже будет медленнее, чем однопоточная программа. В этом случае отправьте задания, которые выполняют пакеты вычислений, как предлагает mdma.
  • Запустите второй цикл, который собирает результаты из всех Future (он будет неявно ждать завершения всех вычислений)
  • отключить службу исполнителя

Ответ 3

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

Если вы не полностью привязаны к Java, вы можете попробовать параллельную высокопроизводительную систему C, такую ​​как OpenMPI. OpenMPI подходит для такого рода проблем.

Ответ 4

Не создавайте темы самостоятельно. Я рекомендую вам использовать инфраструктуру fork/join (jsr166y) и создавать задачи, которые повторяются по заданному диапазону элементов. Он позаботится об управлении потоками для вас, используя столько потоков, сколько поддерживает оборудование.

Задача детализации является основной проблемой здесь. Если каждая итерация является относительно небольшим вычислением (например, менее 100 операций), то каждая итерация, выполняемая как отдельная задача, приведет к множеству накладных расходов на планирование задач. Лучше, чтобы каждая задача принимала список аргументов для вычисления и возвращала результат в виде списка. Таким образом, каждая задача может вычислить 1, 10 или тысячи элементов, чтобы сохранить гранулярность задачи на разумном уровне, который уравновешивает доступность работы и уменьшает издержки на управление задачами.

Существует также класс ParallelArray в jsr166z, который позволяет повторять вычисления по массиву. Это может сработать для вас, если значения, которые вы вычисляете, являются примитивными типами.