Быстрое чтение текстовых файлов в C++
В настоящее время я пишу программу на C++, которая включает чтение большого количества текстовых файлов. У каждой ~400.000 строк, в крайнем случае 4000 или более символов в строке. Просто для тестирования я прочитал один из файлов, используя ifstream и реализацию, предлагаемую cplusplus.com. Это заняло около 60 секунд, что слишком долго. Теперь мне было интересно, есть ли простой способ улучшить скорость чтения?
редактировать: код, который я использую, более или менее это:
string tmpString;
ifstream txtFile(path);
if(txtFile.is_open())
{
while(txtFile.good())
{
m_numLines++;
getline(txtFile, tmpString);
}
txtFile.close();
}
редактировать 2: файл, который я читаю, имеет размер всего 82 МБ. Я в основном говорил, что она может достигать 4000, потому что я думал, что это может быть необходимо для буферизации.
редактировать 3: Спасибо всем за ваши ответы, но, похоже, не так много места для улучшения, учитывая мою проблему. Я должен использовать readline, так как я хочу посчитать количество строк. Реализация ifstream как двоичного также не делала чтения быстрее. Я постараюсь распараллелить это как можно больше, чтобы это работало по крайней мере.
редактировать 4: Так что, очевидно, я могу кое-что сделать. Большое спасибо sehe за то, что вы уделили мне столько времени, я это очень ценю! знак равно
6 ответов
Обновления: не забудьте проверить (удивительно) обновления ниже первоначального ответа
Файлы с отображенной памятью хорошо послужили мне1:
#include <boost/iostreams/device/mapped_file.hpp> // for mmap
#include <algorithm> // for std::find
#include <iostream> // for std::cout
#include <cstring>
int main()
{
boost::iostreams::mapped_file mmap("input.txt", boost::iostreams::mapped_file::readonly);
auto f = mmap.const_data();
auto l = f + mmap.size();
uintmax_t m_numLines = 0;
while (f && f!=l)
if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
m_numLines++, f++;
std::cout << "m_numLines = " << m_numLines << "\n";
}
Это должно быть довольно быстро.
Обновить
В случае, если это поможет вам проверить этот подход, вот версия, использующаяmmap
напрямую вместо Boost: посмотрите вживую на Coliru
#include <algorithm>
#include <iostream>
#include <cstring>
// for mmap:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
const char* map_file(const char* fname, size_t& length);
int main()
{
size_t length;
auto f = map_file("test.cpp", length);
auto l = f + length;
uintmax_t m_numLines = 0;
while (f && f!=l)
if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
m_numLines++, f++;
std::cout << "m_numLines = " << m_numLines << "\n";
}
void handle_error(const char* msg) {
perror(msg);
exit(255);
}
const char* map_file(const char* fname, size_t& length)
{
int fd = open(fname, O_RDONLY);
if (fd == -1)
handle_error("open");
// obtain file size
struct stat sb;
if (fstat(fd, &sb) == -1)
handle_error("fstat");
length = sb.st_size;
const char* addr = static_cast<const char*>(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0u));
if (addr == MAP_FAILED)
handle_error("mmap");
// TODO close fd at some point in time, call munmap(...)
return addr;
}
Обновить
Последняя часть производительности, которую я смог вытащить из этого, я нашел, посмотрев на источник GNU coreutils wc
, К моему удивлению, используя следующий (очень упрощенный) код, адаптированный из wc
работает примерно в 84% времени, которое занимает файл с отображенной памятью выше:
static uintmax_t wc(char const *fname)
{
static const auto BUFFER_SIZE = 16*1024;
int fd = open(fname, O_RDONLY);
if(fd == -1)
handle_error("open");
/* Advise the kernel of our access pattern. */
posix_fadvise(fd, 0, 0, 1); // FDADVICE_SEQUENTIAL
char buf[BUFFER_SIZE + 1];
uintmax_t lines = 0;
while(size_t bytes_read = read(fd, buf, BUFFER_SIZE))
{
if(bytes_read == (size_t)-1)
handle_error("read failed");
if (!bytes_read)
break;
for(char *p = buf; (p = (char*) memchr(p, '\n', (buf + bytes_read) - p)); ++p)
++lines;
}
return lines;
}
1 см., Например, тест здесь: Как быстро проанализировать разделенные пробелами числа в C++?
4000 * 400 000 = 1,6 ГБ, если ваш жесткий диск не является твердотельным накопителем, вы, вероятно, получаете последовательное чтение ~100 МБ / с. Это 16 секунд только в I/O.
Поскольку вы не уточняете конкретный код, который вы используете, или как вам нужно анализировать эти файлы (нужно ли читать его построчно, есть ли у системы много оперативной памяти, если бы вы могли прочитать весь файл в большой буфер оперативной памяти? а потом разбирать?) Мало что можно сделать, чтобы ускорить процесс.
Файлы с отображением в памяти не обеспечат никакого улучшения производительности при последовательном чтении файла. Возможно, ручной синтаксический анализ больших кусков для новых строк вместо использования "getline" даст улучшение.
РЕДАКТИРОВАТЬ После некоторого обучения (спасибо @sehe). Вот решение с отображением памяти, которое я бы, вероятно, использовал.
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
int main() {
char* fName = "big.txt";
//
struct stat sb;
long cntr = 0;
int fd, lineLen;
char *data;
char *line;
// map the file
fd = open(fName, O_RDONLY);
fstat(fd, &sb);
//// int pageSize;
//// pageSize = getpagesize();
//// data = mmap((caddr_t)0, pageSize, PROT_READ, MAP_PRIVATE, fd, pageSize);
data = mmap((caddr_t)0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
line = data;
// get lines
while(cntr < sb.st_size) {
lineLen = 0;
line = data;
// find the next line
while(*data != '\n' && cntr < sb.st_size) {
data++;
cntr++;
lineLen++;
}
/***** PROCESS LINE *****/
// ... processLine(line, lineLen);
}
return 0;
}
Нил Кирк, к сожалению, я не могу ответить на ваш комментарий (недостаточно репутации), но я провел тест производительности для ifstream в потоке строки, и производительность, читая текстовый файл построчно, точно такая же.
std::stringstream stream;
std::string line;
while(std::getline(stream, line)) {
}
Это занимает 1426мс в файле размером 106 МБ.
std::ifstream stream;
std::string line;
while(ifstream.good()) {
getline(stream, line);
}
Это занимает 1433 мс на том же файле.
Следующий код быстрее вместо этого:
const int MAX_LENGTH = 524288;
char* line = new char[MAX_LENGTH];
while (iStream.getline(line, MAX_LENGTH) && strlen(line) > 0) {
}
Это занимает 884 мсек в том же файле. Это немного сложно, так как вы должны установить максимальный размер буфера (то есть максимальную длину для каждой строки во входном файле).
Как человек с небольшим опытом работы в конкурентном программировании, я могу вам сказать: по крайней мере для простых вещей, таких как целочисленный анализ, основной ценой в C является блокировка потоков файлов (что по умолчанию выполняется для многопоточности). Использовать unlocked_stdio
версии вместо (fgetc_unlocked()
, fread_unlocked()
). Для C++ распространенным знанием является использование std::ios::sync_with_stdio(false)
но я не знаю, так ли это быстро, как unlocked_stdio
,
Для справки вот мой стандартный код целочисленного анализа. Это намного быстрее, чем scanf, как я сказал, в основном из-за отсутствия блокировки потока. Для меня это было так же быстро, как лучшая ручная кодировка mmap или пользовательских версий с буферизацией, которые я использовал ранее, без безумного долга за обслуживание.
int readint(void)
{
int n, c;
n = getchar_unlocked() - '0';
while ((c = getchar_unlocked()) > ' ')
n = 10*n + c-'0';
return n;
}
(Примечание: этот работает только в том случае, если между любыми двумя целыми числами находится ровно один нецифровый символ).
И конечно избегайте выделения памяти, если это возможно...
Вы должны читать все файлы одновременно? (в начале вашего приложения, например)
Если вы это сделаете, подумайте о распараллеливании операции.
В любом случае, рассмотрите возможность использования двоичных потоков или небуферизованного чтения для блоков данных.
Использование Random file access
или использовать binary mode
, для последовательного, это большой, но все же это зависит от того, что вы читаете.