Откройте MPI и Boost MPI, используя слишком много файловых дескрипторов

Я запускаю проект с использованием Boost MPI (1.55) поверх Open MPI (1.6.1) в вычислительном кластере.

В нашем кластере есть узлы с 64 процессорами, и мы запускаем по одному процессу MPI на каждом. Большая часть нашего взаимодействия осуществляется между отдельными процессами, каждый из которых имеет серию открытых запросов irecv () (для разных тегов), а отправка выполняется с блокировкой с помощью send ().

Проблема, которую мы получаем, заключается в том, что после короткого времени обработки (обычно менее 10 минут) мы получаем эту ошибку, которая приводит к завершению программы:

[btl_tcp_component.c:1114:mca_btl_tcp_component_accept_handler] accept() failed: Too many open files in system (23).

Более тщательная отладка показывает, что эти дескрипторы файлов берут на себя сетевые сокеты, и мы достигли предела нашей ОС в 65536 открытых дескрипторов. Большинство из них находятся в состоянии «TIME_WAIT», что, по-видимому, является тем, что TCP делает в течение (обычно) 60 секунд после закрытия сокета (чтобы перехватить любые поздние пакеты). У меня создалось впечатление, что Open MPI не закрывает сокеты (http://www.open-mpi.org/faq/?category=tcp#tcp-socket-closing) и просто оставил открытыми до N ^ 2 сокетов, чтобы все процессы могли взаимодействовать друг с другом. Очевидно, 65536 выходит за рамки 64 ^ 2 (наиболее частая причина этой ошибки, связанной с MPI, заключается просто в том, что ограничение файла меньше N ^ 2), и большинство из них были сокетами, которые недавно были в закрытом состоянии.

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

namespace mpi = boost::mpi;
mpi::communicator world;

bool poll(ourDataType data, mpi::request & dataReq, ourDataType2 work, mpi::request workReq) {
  if(dataReq.test()) {
    processData(data); // do a bunch of work
    dataReq = world.irecv(mpi::any_source, DATATAG, data);
    return true;
  }
  if(workReq.test()) {
    int target = assess(work);
    world.send(target, DATATAG, dowork);
    world.irecv(mpi::any_source, WORKTAG, data);
    return true;
  }
  return false;
}

bool receiveFinish(mpi::request finishReq) {
  if (finishReq.test()) {
    world.send(0, RESULTS, results);
    resetSelf();
    finishReq = world.irecv(0, FINISH);
    return true;
  }
  return false;
}

void run() {
  ourDataType data;
  mpi::request dataReq = world.irecv(mpi::any_source, DATATAG, data);
  ourDataType2 work;
  mpi::request workReq = world.irecv(mpi::any_source, WORKTAG, work);
  mpi::request finishReq = world.irecv(0, FINISH); // the root process can call a halt

  while(!receiveFinish(finishReq)) {
    bool doWeContinue = poll(data, dataReq);
    if(doWeContinue) {
      continue;
    }
    // otherwise we do other work
    results = otherwork();
    world.send(0, RESULTS, results);
  }
}

person Marc    schedule 25.07.2014    source источник
comment
О, дорогой C ++, поэтому, пожалуйста, сделайте следующее с огромной оговоркой, что я не понимаю этот язык ... В любом случае я не вижу никаких mpi_wait или mpi_test - всякий раз, когда у вас есть неблокирующая связь mpi, ДОЛЖНО быть соответствующее ожидание . Я подозреваю, что эти сокеты не закрываются, но я должен подчеркнуть, что это предположение, поскольку ожидание / тест может быть где-то еще, или C ++ может быть еще более странным, чем я думаю.   -  person Ian Bush    schedule 25.07.2014
comment
Да, @IanBush, C ++ не слишком дружит с нами. Если наверху находится уровень Boost MPI, такие операторы, как dataReq.test (), аналогичны mpi_test для этого конкретного irecv. Я не уверен, нужно ли подождать, если будет регулярный опрос с тестом?   -  person Marc    schedule 25.07.2014


Ответы (1)


Возможно, это не настоящая причина того, что Open MPI открывает так много сокетов, но похоже, что в следующей части функции poll() происходит утечка запросов:

if(workReq.test()) {
  int target = assess(work);
  world.send(target, DATATAG, dowork);
  world.irecv(mpi::any_source, WORKTAG, data); // <-------
  return true;
}

Дескриптор запроса, возвращенный world.irecv(), никогда не сохраняется и, следовательно, теряется. При периодическом вызове для одного и того же workReq объекта эта ветвь будет выполняться каждый раз после завершения запроса, поскольку при тестировании уже выполненного запроса всегда возвращается true. Таким образом, вы начнете получать множество неблокирующих сообщений, которые никогда не будут ожидаться или проверяться. Не говоря уже об отправленных сообщениях.

Аналогичная проблема существует в receiveFinish - finishReq передается по значению, и присвоение не повлияет на значение в run().

Примечание: действительно ли вы используете этот код? Похоже, что функция poll(), которую вы вызываете в run(), принимает два аргумента, в то время как показанная здесь функция принимает четыре аргумента, и нет аргументов со значениями по умолчанию.

person Hristo Iliev    schedule 28.07.2014
comment
Привет, Христо, и спасибо за ваш вклад. Это не тот код, который мы запускаем, это, по сути, его перефразированная версия, потому что он довольно большой и сложный. Строка irecv, на которую вы указали, была ошибкой и должна была читать: workReq = world.irecv(mpi::any_source, WORKTAG, data); Однако я взглянул на наш код, и мы действительно передаем некоторые запросы по значению (а в других местах по ссылке). Это действительно могло быть источником проблемы - person Marc; 29.07.2014
comment
Похоже, моей проблемой были запросы, передаваемые по значению, а не по ссылке. Большое спасибо за то, что заметили это. - person Marc; 29.07.2014