Распараллелить цикл while с OpenMP

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

int main()
{
  char buffer[BUFFER_SIZE];
  while(fgets(buffer, BUFFER_SIZE, stdin))
  {
    fgets(buffer, BUFFER_SIZE, stdin);
    do_some_simple_processing_on_the_second_line_of_the_record(buffer);
    fgets(buffer, BUFFER_SIZE, stdin);
    fgets(buffer, BUFFER_SIZE, stdin);
  }
  print_out_result();
}

Это конечно оставляет некоторые детали (вменяемость/проверка ошибок и т. д.), Но это не относится к вопросу.

программа работает нормально, но файлы данных, с которыми я работаю огромный. Я решил попробовать ускорить программа путем распараллеливания цикла с OpenMP. После небольшого поиска, однако, кажется, что OpenMP может обрабатывать только for циклы, где количество итераций известно заранее. Так как я не знаю размер файлов заранее, и даже простые команды типа wc -l занять много времени для запуска, как я могу распараллелить эту программу?

3 ответов


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


как thiton уже упоминалось, этот код может быть ввода/вывода ограничена. Однако в эти дни многие компьютеры могут иметь SSD и RAID-диски с высокой пропускной способностью. В таком случае можно получить ускорение от распараллеливания. Более того, если вычисление не является тривиальным, то распараллеливайте выигрыши. Даже если ввод-вывод эффективно сериализован из-за насыщенной полосы пропускания, вы все равно можете получить ускорение, распределив вычисления на многоядерные.


вернемся к самому вопросу, вы можете распараллелить этот цикл стандарт OpenMP. С stdin, Я понятия не имею, чтобы распараллелить, потому что он должен читать последовательно и без предварительной информации о конце. Однако, если вы работаете с типичным файлом, вы можете это сделать.

вот мой код omp parallel. Я использовал некоторые Win32 API и MSVC CRT:

void test_io2()
{
  const static int BUFFER_SIZE = 1024;
  const static int CONCURRENCY = 4;

  uint64_t local_checksums[CONCURRENCY];
  uint64_t local_reads[CONCURRENCY];

  DWORD start = GetTickCount();

  omp_set_num_threads(CONCURRENCY);

  #pragma omp parallel
  {
    int tid = omp_get_thread_num();

    FILE* file = fopen("huge_file.dat", "rb");
    _fseeki64(file, 0, SEEK_END);
    uint64_t total_size = _ftelli64(file);

    uint64_t my_start_pos = total_size/CONCURRENCY * tid;
    uint64_t my_end_pos   = min((total_size/CONCURRENCY * (tid + 1)), total_size);
    uint64_t my_read_size = my_end_pos - my_start_pos;
    _fseeki64(file, my_start_pos, SEEK_SET);

    char* buffer = new char[BUFFER_SIZE];

    uint64_t local_checksum = 0;
    uint64_t local_read = 0;
    size_t read_bytes;
    while ((read_bytes = fread(buffer, 1, min(my_read_size, BUFFER_SIZE), file)) != 0 &&
      my_read_size != 0)
    {
      local_read += read_bytes;
      my_read_size -= read_bytes;
      for (int i = 0; i < read_bytes; ++i)
        local_checksum += (buffer[i]);
    }

    local_checksums[tid] = local_checksum;
    local_reads[tid]     = local_read;

    fclose(file);
  }

  uint64_t checksum = 0;
  uint64_t total_read = 0;
  for (int i = 0; i < CONCURRENCY; ++i)
    checksum += local_checksums[i], total_read += local_reads[i];

  std::cout << checksum << std::endl
    << total_read << std::endl
    << double(GetTickCount() - start)/1000. << std::endl;
}

этот код выглядит немного грязным, потому что мне нужно было точно распределить объем файла для чтения. Однако код достаточно прост. Одна вещь, имейте в виду, что вам нужно иметь указатель файла для каждого потока. Вы не можете просто поделиться указателем файла, потому что внутренняя структура данных может быть потокобезопасной. Кроме того, этот код можно распараллелить с помощью parallel for. Но я думаю, что такой подход более естественен.


простые экспериментальные результаты

я протестировал этот код для чтения файла 10GB на HDD (WD Green 2TB) и SSD (Intel 120GB).

С HDD, да, никаких ускорений не было получено. Наблюдалось даже замедление. Этот ясно показывает, что этот код ограничен вводом-выводом. Этот код практически не имеет вычислений. Просто I / O.

однако, с SSD, у меня был ускорение 1.2 С 4-мя ядрами. Да, ускорение небольшое. Но вы все равно можете получить его с SSD. И, если вычисление станет немного больше (я просто поставил очень короткий цикл ожидания занятости), ускорения будут значительными. Я смог получить ускорение 2.5.


В общем, я хотел бы рекомендовать вам попытаться распараллелить это код.

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


в ответ на" minding " я не думаю, что ваш код действительно оптимизирует что-либо здесь. Существует много общего непонимания относительно этого оператора "#pragma omp parallel", это на самом деле просто породит потоки, без ключевого слова" for", все потоки будут просто выполнять любые коды, которые следуют. Таким образом, ваш код фактически будет дублировать вычисления в каждом потоке. В ответ на Daniel, вы были правы, OpenMP не может оптимизировать цикл while, единственный способ оптимизации это путем реструктуризации кода так, чтобы итерация была известна заранее (например, цикл while один раз со счетчиком). Извините за публикацию другого ответа, поскольку я пока не могу комментировать, но, надеюсь, это очищает общие недоразумения.