在 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: 我读取的文件只有82MB 大。我主要是说它可以达到4000,因为我认为可能有必要知道,以便进行缓冲。

编辑3: 谢谢你们所有人的回答,但是考虑到我的问题,似乎没有多少改进的余地。我必须使用 readline,因为我想计算行数。将 ifstream 实例化为二进制文件也不会提高读取速度。我会尽可能地将它并行化,至少这样应该可以。

编辑4: 所以显然我可以做一些事情。非常感谢您为此投入了这么多时间,我非常感激!=)

95759 次浏览

更新: 一定要检查初始答案下面的(令人惊讶的)更新


内存映射文件为我提供了很好的帮助:

#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 的 href = “ http://linux.die.net/man/2/mmap”rel = “ noReferrer”> ,而不是使用 Boost: 在科利鲁上直播

#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;
}

如何在 C + + 中快速解析空格分隔的浮点数?

4000 * 400,000 = 1.6 GB 如果你的硬盘不是固态硬盘,你可能会得到 ~ 100 MB/s 的连续读取。输入输出只需要16秒。

因为你没有详细说明你使用的具体代码,或者你需要如何解析这些文件(你需要一行一行地读它吗,系统是否有很多内存,你可以把整个文件读入一个大的内存缓冲区,然后解析它吗你几乎不能做什么来加快这个过程。

在顺序读取文件时,内存映射文件不会提供任何性能改进。也许手动解析新行的大块内容而不是使用“ 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;
}

使用 Random file access或者使用 binary mode.for 连续,这是很大的,但仍然取决于你正在阅读的内容。

您必须同时读取所有文件吗? (例如,在应用程序的开始)

如果需要,请考虑将操作并行化。

无论哪种方式,都可以考虑使用二进制流,或者对于数据块使用未提供的

作为一个在竞争性编程方面有一点背景的人,我可以告诉你: 至少对于整数解析这样的简单事情,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;
}

(注意: 这种方法只适用于任意两个整数之间只有一个非数字字符的情况)。

当然,如果可能的话,还要避免内存分配... ..。

Neil Kirk,很遗憾我不能回复你的评论(声誉不够好) ,但是我做了一个关于 ifstream 和 string stream 的性能测试,一行一行地阅读文本文件,结果是完全一样的。

std::stringstream stream;
std::string line;
while(std::getline(stream, line)) {
}

这对于106MB 的文件需要1426ms。

std::ifstream stream;
std::string line;
while(ifstream.good()) {
getline(stream, line);
}

这在同一个文件上需要1433ms。

下面的代码反而更快:

const int MAX_LENGTH = 524288;
char* line = new char[MAX_LENGTH];
while (iStream.getline(line, MAX_LENGTH) && strlen(line) > 0) {
}

同一个文件需要884ms。 这有点棘手,因为您必须设置缓冲区的最大大小(即输入文件中每行的最大长度)。