К сожалению, нет лучшего способа проследить внутреннюю работу коллективных операций MPI. Стандартный интерфейс трассировки в MPI основан на парадигме PMPI: все вызовы MPI_*
реализуются как слабые псевдонимы реальных функций MPI. Фактические функции доступны под именем PMPI_*
(при этом вызовы PMPI_*
также являются либо реальными реализациями, либо псевдонимами). Это позволяет библиотекам трассировщика объявлять свои собственные MPI_*
функции, которые вызывают PMPI_*
, генерируя события трассировки до и после вызова. Например:
int MPI_Reduce(void *sendbuf, void *recvbuf, int count,
MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm)
{
int result;
trace_event_start("MPI_Reduce");
result = PMPI_Reduce(sendbuf, recvbuf, count, datatype, op, root, comm);
trace_event_end("MPI_Reduce");
return result;
}
Когда этот код компонуется с остальной частью программы, все вызовы MPI_Reduce
заменяются вызовами трассируемой версии (поскольку MPI_Reduce
изначально был слабым псевдонимом, компоновщик не будет жаловаться на переопределение символа).
Теперь реальная проблема в вашем случае заключается в том, что MPI_Reduce
реализуется не с использованием вызовов MPI_Send
и MPI_Recv
, а с использованием вызовов низкоуровневых функций MPICH2, например. MPIC_Send_ft
и MPIC_Recv_ft
. Их нельзя перехватить с помощью механизма PMPI. В этом случае вы можете извлечь код из исходного кода MPICH2 и заменить внутренние вызовы вызовами MPI_Send
и MPI_Recv
, а затем проследить получившуюся реализацию.
Я выполнил процедуру, описанную выше, и она довольно хорошо работает с Open MPI, за исключением небольшого неудобства - как только вы предоставляете свою собственную реализацию функции MPI, например. MPI_Reduce
, это больше не слабый псевдоним, и связывание с библиотекой трассировки может привести к ошибке повторяющегося символа. В этом случае я бы просто назвал свою реализацию MyMPI_Reduce
и поставил #define MPI_Reduce MyMPI_Reduce
в начале тех исходных файлов, которые необходимо отследить. Я не очень хорошо знаком с MPICH2, но из исходного кода я могу сказать, что он позволяет подключать пользовательские реализации, и это упрощает его (например, нет необходимости обманывать препроцессор).
И еще: в MPICH2 есть несколько реализаций редукции, по крайней мере, в версии 3.0, и он выбирает одну из них во время выполнения с помощью простой эвристической логики:
if ((count*type_size > MPIR_PARAM_REDUCE_SHORT_MSG_SIZE) &&
(HANDLE_GET_KIND(op) == HANDLE_KIND_BUILTIN) && (count >= pof2)) {
/* do a reduce-scatter followed by gather to root. */
mpi_errno = MPIR_Reduce_redscat_gather(sendbuf, recvbuf, count, datatype, op, root, comm_ptr, errflag);
if (mpi_errno) {
/* for communication errors, just record the error but continue */
*errflag = TRUE;
MPIU_ERR_SET(mpi_errno, MPI_ERR_OTHER, "**fail");
MPIU_ERR_ADD(mpi_errno_ret, mpi_errno);
}
}
else {
/* use a binomial tree algorithm */
mpi_errno = MPIR_Reduce_binomial(sendbuf, recvbuf, count, datatype, op, root, comm_ptr, errflag);
if (mpi_errno) {
/* for communication errors, just record the error but continue */
*errflag = TRUE;
MPIU_ERR_SET(mpi_errno, MPI_ERR_OTHER, "**fail");
MPIU_ERR_ADD(mpi_errno_ret, mpi_errno);
}
}
person
Hristo Iliev
schedule
16.07.2013