Многопоточный мекс-код медленнее однопоточного

Я пишу мекс-код в MATLAB, чтобы сделать и операции (потому что операция использует библиотеку в C ++). В мекс-коде есть раздел, в котором есть функция, которая многократно вызывается в цикле с другим значением аргумента, и каждый вызов функции является независимым (то есть вычисление 1 вызова не зависит от предыдущих вызовов). Итак, чтобы ускорить это, я написал многопоточный код, который создает несколько потоков — точное число потоков равно числу итераций цикла, в моем примере это значение равно 10. Каждый поток вычисляет функцию в цикле для отдельного значения аргумент, потоки возвращаются и соединяются, выполняется еще несколько вычислений и возвращается результат.
Все это в теории должно дать мне хорошее ускорение, но я вижу, что многопоточный код намного медленнее, чем обычный однопоточный! У меня есть доступ к очень мощным 24-ядерным машинам, так что это совершенно сбивает с толку, потому что я ожидал, что каждый поток будет запланирован на отдельном ядре.
Есть идеи, что ведет к этому? Какие-нибудь общие проблемы / ошибки в коде, которые приводят к этому?

Любая помощь будет оценена.

РЕДАКТИРОВАТЬ:
Чтобы ответить на многие сомнения, возникшие в решениях, предложенных здесь людьми, я хочу поделиться информацией о моем коде:
1. Каждый вызов функции занимает несколько минут, поэтому синхронизация и порождение потоков здесь не должны быть чрезмерными (хотя, если в этом случае есть какие-либо смягчающие обстоятельства, любая информация об этом будет очень полезной!)

  1. Каждый поток имеет доступ к общим структурам данных, массивам, матрицам, но значения в них вообще не перезаписываются. Все записи в переменные выполняются для переменных, указателей, массивов и т. Д., Которые являются локальными для потока. Итак, я полагаю, здесь не должно быть много пропусков кэша?

  2. Также в моем коде нет разделов мьютекса, так как нет потока, записывающего в какую-либо общую область памяти. Все записи находятся в местах памяти, локальных для потока.

Я все еще пытаюсь выяснить причину, по которой моя многопоточная реализация не работает 🙁 Таким образом, любые указатели / информация будут действительно полезны!

Спасибо!!

1

Решение

Учитывая, насколько общий ваш вопрос, общий ответ таков: в игре, вероятно, два эффекта:

  • При запуске и остановке потоков (и их синхронизации) возникают большие накладные расходы, и масштабирование вычислений недостаточно для преодоления накладных расходов. Общее время на вызов функции позволит пролить свет на эту проблему.
  • Потоки могут конкурировать друг с другом и замедлять совокупную производительность. Распространенным механизмом является «кеширование». Поскольку несколько ядер совместно используют один и тот же контроллер памяти и части иерархии кеша, один поток может заполнить кеш необходимой информацией только для того, чтобы часть этих данных была удалена из-за потребностей другого потока, что привело к увеличению числа обращений к основной памяти. Поскольку доступ к основной памяти очень дорогой, конечным результатом является замедление.

Я бы протестировал работу с переменным количеством потоков. Например, может оказаться, что использование двух потоков выгодно, а четыре или более — нет. Чтобы получить более подробные ответы, добавьте больше деталей к вопросу, таких как тип вычисления, размер набора данных и т. Д.

1

Другие решения

Вы не описали, что делает ваш код, так что это всего лишь догадки.

Многопоточность — это не чудодейственное лекарство. Есть много способов, которыми многопоточность того, что было однопоточным фрагментом кода, может быть медленнее, чем оригинал. Существует много накладных расходов, связанных с порождением, синхронизацией, объединением и уничтожением потоков.

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

Еще один способ замедлить ситуацию — это установить барьеры для предотвращения параллельных операций. Мьютекс, например, для защиты от нескольких писателей, одновременно обращающихся к одному и тому же объекту. Этот защищенный код должен быть маленьким. Сделайте так, чтобы все тела вашего потока работали под видом мьютекса, и у вас есть эквивалент однопоточного приложения, в которое добавлена ​​целая куча потоков.

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

1

Я не думаю, что ваши проблемы вообще специфичны для меня — это звучит как обычные проблемы с производительностью при программировании многопоточного кода для SMP.

Чтобы добавить немного к уже упомянутым потенциальным проблемам:

  • Ложный обмен строк кэша: вы можете подумать, что ваши потоки работают независимо, хотя на самом деле они обращаются к разным данным в одной и той же строке кэша. Тривиальный пример:

    /* global variable accessible by all threads */
    int thread_data[nthreads];
    
    /* inside thread function */
    thread_data[thrid] = some_value;
    
  • неэффективное использование полосы пропускания памяти. В системах NUMA вы хотите, чтобы процессоры обращались к своим собственным банкам данных. Если вы неправильно распределяете данные, процессоры запрашивают память у других процессоров. Это подразумевает общение, о котором вы не подозреваете.

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

1
По вопросам рекламы [email protected]