在C++中从标准输入流(stdin)按字节一次读取的快速简便方法

3
以下这段朴素的代码用于从标准输入中读取数据并计算每个字节的出现次数,但它非常缓慢,在我的机器上处理1GB的数据需要约1分40秒。
int counts[256] {0};

uint8_t byte;
while (std::cin >> std::noskipws >> byte) {
  ++counts[byte];
}

进行缓冲读取当然要快得多,处理1个GiB的数据不到一秒钟。

uint8_t buf[4096];

uint8_t byte;
int n;
while (n = read(0, (void *)buf, 4096), n > 0) {
  for (int i = 0; i < n; ++i) {
    ++counts[buf[i]];
  }
}

然而,它的缺点是更加复杂,并需要手动进行缓冲管理。
在标准C++中有没有一种读取流时逐字节进行操作的方法既像第一个片段一样简单、明显和惯用,同时又像第二个片段一样高效?

可能是 cin.get() 吗? - user10957435
@Chipster 尝试过了,每次读取一个字符时速度和 operator>> 一样慢。 - Brennan Vincent
唯一获得速度的方法是进行批量读取。我不确定你在抱怨什么“手动缓冲管理”,因为你展示的代码中没有任何这样的内容。 - NathanOliver
@NathanOliver 嗯?我的意思是定义一个4096个字符的“buf”,将其传递给“read”,然后处理到其长度。 - Brennan Vincent
2个回答

4
这似乎是一个有趣的问题。我的结果在这里:
without cin sync      : 34.178s
with cin sync         : 14.347s
with getchar          : 03.911s
with getchar_unlocked : 00.700s

此源文件是使用以下工具生成的:

$ dd if=/dev/urandom of=file.txt count=1024 bs=1048576

第一个是我参考,没有更改:34.178秒
#include <bits/stdc++.h>
int main(int argc, char **argv) {
    FILE *f = freopen(argv[1], "rb", stdin);
    int counts[256] {0};

    uint8_t byte;
    while (std::cin >> std::noskipws >> byte) {
      ++counts[byte];
    }
    return 0;
}

使用 std::ios::sync_with_stdio(false);14.347秒

#include <bits/stdc++.h>
int main(int argc, char **argv) {
    std::ios::sync_with_stdio(false);
    FILE *f = freopen(argv[1], "rb", stdin);
    int counts[256] {0};

    uint8_t byte;
    while (std::cin >> std::noskipws >> byte) {
      ++counts[byte];
    }
    return 0;
}

使用 getchar3.911秒

#include <bits/stdc++.h>
int main(int argc, char **argv) {
    FILE *f = freopen(argv[1], "rb", stdin);
    int v[256] {0};
    unsigned int b;
    while ((b = getchar()) != EOF) {
        ++v[b];
    }
    return 0;
}

使用 getchar_unlocked0.700秒

#include <bits/stdc++.h>
int main(int argc, char **argv) {
    FILE *f = freopen(argv[1], "rb", stdin);
    int v[256] {0};
    unsigned int b;
    while ((b = getchar_unlocked()) != EOF) {
        ++v[b];
    }
    return 0;
}

我的机器配置:

CPU  : Intel(R) Core(TM) i5-3210M CPU @ 2.50GHz
MEM  : 12GB
Build: g++ speed.cc -O3 -o speed
g++ v: g++ (Ubuntu 7.4.0-1ubuntu1~18.04) 7.4.0
exec : time ./speed file.txt

对我而言,getchar_unlocked 是最快的无需维护缓冲区即可读取字节的方法。


0
我会尝试这样做:
std::ios::sync_with_stdio(false);

这将大大加快cin的速度。


不,这并不会改变我的基准测试所需的时间。运行在 macOS 上。 - Brennan Vincent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接