Как уже упоминалось, этот код может быть ограничен вводом / выводом. Однако в наши дни многие компьютеры могут иметь 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
. Но я думаю, что этот подход более естественный.
Простые экспериментальные результаты
Я протестировал этот код для чтения файла объемом 10 ГБ на жестком диске (WD Green 2 ТБ) и на SSD (Intel 120 ГБ).
С жестким диском да, ускорений не было. Даже замедления не наблюдалось. Это ясно показывает, что этот код ограничен вводом / выводом. Этот код практически не имеет вычислений. Просто я / O.
Однако с SSD у меня было ускорение на 1,2 с 4 ядрами. Да, ускорение мало. Но вы все равно можете получить его с SSD. И если вычисления станут немного больше (я просто поставил очень короткий цикл ожидания занятости), ускорения будут значительными. Я смог получить ускорение 2,5.
В целом, я хотел бы рекомендовать вам попробовать распараллелить этот код.
Кроме того, если вычисления не тривиальны, я бы порекомендовал конвейеризация . Приведенный выше код просто делится на несколько больших блоков, что приводит к низкой эффективности кэширования. Однако параллелизация конвейера может привести к лучшему использованию кэша. Попробуйте использовать TBB для параллелизации конвейера. Они обеспечивают простую конвейерную конструкцию.