Я пишу мекс-код в MATLAB, чтобы сделать и операции (потому что операция использует библиотеку в C ++). В мекс-коде есть раздел, в котором есть функция, которая многократно вызывается в цикле с другим значением аргумента, и каждый вызов функции является независимым (то есть вычисление 1 вызова не зависит от предыдущих вызовов). Итак, чтобы ускорить это, я написал многопоточный код, который создает несколько потоков — точное число потоков равно числу итераций цикла, в моем примере это значение равно 10. Каждый поток вычисляет функцию в цикле для отдельного значения аргумент, потоки возвращаются и соединяются, выполняется еще несколько вычислений и возвращается результат.
Все это в теории должно дать мне хорошее ускорение, но я вижу, что многопоточный код намного медленнее, чем обычный однопоточный! У меня есть доступ к очень мощным 24-ядерным машинам, так что это совершенно сбивает с толку, потому что я ожидал, что каждый поток будет запланирован на отдельном ядре.
Есть идеи, что ведет к этому? Какие-нибудь общие проблемы / ошибки в коде, которые приводят к этому?
Любая помощь будет оценена.
РЕДАКТИРОВАТЬ:
Чтобы ответить на многие сомнения, возникшие в решениях, предложенных здесь людьми, я хочу поделиться информацией о моем коде:
1. Каждый вызов функции занимает несколько минут, поэтому синхронизация и порождение потоков здесь не должны быть чрезмерными (хотя, если в этом случае есть какие-либо смягчающие обстоятельства, любая информация об этом будет очень полезной!)
Каждый поток имеет доступ к общим структурам данных, массивам, матрицам, но значения в них вообще не перезаписываются. Все записи в переменные выполняются для переменных, указателей, массивов и т. Д., Которые являются локальными для потока. Итак, я полагаю, здесь не должно быть много пропусков кэша?
Также в моем коде нет разделов мьютекса, так как нет потока, записывающего в какую-либо общую область памяти. Все записи находятся в местах памяти, локальных для потока.
Я все еще пытаюсь выяснить причину, по которой моя многопоточная реализация не работает 🙁 Таким образом, любые указатели / информация будут действительно полезны!
Спасибо!!
Учитывая, насколько общий ваш вопрос, общий ответ таков: в игре, вероятно, два эффекта:
Я бы протестировал работу с переменным количеством потоков. Например, может оказаться, что использование двух потоков выгодно, а четыре или более — нет. Чтобы получить более подробные ответы, добавьте больше деталей к вопросу, таких как тип вычисления, размер набора данных и т. Д.
Вы не описали, что делает ваш код, так что это всего лишь догадки.
Многопоточность — это не чудодейственное лекарство. Есть много способов, которыми многопоточность того, что было однопоточным фрагментом кода, может быть медленнее, чем оригинал. Существует много накладных расходов, связанных с порождением, синхронизацией, объединением и уничтожением потоков.
Предположим, под рукой стояла задача добавить десять пар чисел. Если вы сделаете это многопоточным, создавая поток для каждого добавления, а затем присоединяясь и уничтожая по окончании вычисления, ваша многопоточная версия будет намного, намного медленнее, чем оригинал. Потоки не предназначены для очень коротких вычислений. Затраты на порождение, присоединение и уничтожение сократят любое ускорение, которое вы получите, выполняя эти простые задачи параллельно.
Еще один способ замедлить ситуацию — это установить барьеры для предотвращения параллельных операций. Мьютекс, например, для защиты от нескольких писателей, одновременно обращающихся к одному и тому же объекту. Этот защищенный код должен быть маленьким. Сделайте так, чтобы все тела вашего потока работали под видом мьютекса, и у вас есть эквивалент однопоточного приложения, в которое добавлена целая куча потоков.
Те барьеры, которые препятствуют параллельному выполнению, могут присутствовать, даже если вы не поставили их на место. Некоторые из этих барьеров находятся в стандартной библиотеке C. POSIX требует, чтобы большинство функций библиотеки было поточно-ориентированным. Стандарт только перечисляет функции, которые не должны быть потокобезопасными. Если вы используете библиотечные функции в этих вычислениях, вам лучше остаться однопоточным, потому что ваш код по сути однопоточный.
Я не думаю, что ваши проблемы вообще специфичны для меня — это звучит как обычные проблемы с производительностью при программировании многопоточного кода для SMP.
Чтобы добавить немного к уже упомянутым потенциальным проблемам:
Ложный обмен строк кэша: вы можете подумать, что ваши потоки работают независимо, хотя на самом деле они обращаются к разным данным в одной и той же строке кэша. Тривиальный пример:
/* global variable accessible by all threads */
int thread_data[nthreads];
/* inside thread function */
thread_data[thrid] = some_value;
неэффективное использование полосы пропускания памяти. В системах NUMA вы хотите, чтобы процессоры обращались к своим собственным банкам данных. Если вы неправильно распределяете данные, процессоры запрашивают память у других процессоров. Это подразумевает общение, о котором вы не подозреваете.
нить сродства. Несколько связано с пунктом выше. Вы хотите, чтобы ваши потоки были связаны с их собственными процессорами в течение всей продолжительности вычислений. В противном случае они могут быть перенесены ОС, что приводит к накладным расходам, и они могут быть перемещены дальше от банка памяти, к которому они получат доступ.