在 C++ 中一次从 stdin 读取一个字节的快速而简单的方法

Bre*_*ent 4 c++ performance stdin

以下用于从 stdin 读取并计算每个字节出现次数的简单代码非常慢,在我的计算机上处​​理 1 GiB 数据大约需要 1 分 40 秒。

int counts[256] {0};

uint8_t byte;
while (std::cin >> std::noskipws >> byte) {
  ++counts[byte];
}
Run Code Online (Sandbox Code Playgroud)

当然,进行缓冲读取要快得多,不到一秒即可处理 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]];
  }
}
Run Code Online (Sandbox Code Playgroud)

然而,它的缺点是比较复杂并且需要手动缓冲区管理。

有没有一种方法可以在标准 C++ 中逐字节读取流,既像第一个片段一样简单、明显和惯用,又像第二个片段一样高性能?

pra*_*hal 5

这似乎是一个有趣的问题。我的结果在这里:

without cin sync      : 34.178s
with cin sync         : 14.347s
with getchar          : 03.911s
with getchar_unlocked : 00.700s
Run Code Online (Sandbox Code Playgroud)

源文件是使用以下命令生成的:

$ dd if=/dev/urandom of=file.txt count=1024 bs=1048576
Run Code Online (Sandbox Code Playgroud)

第一个是我的参考,没有改变:34.178s

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

getchar3.911s

#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;
}
Run Code Online (Sandbox Code Playgroud)

getchar_unlocked0.700s

#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;
}
Run Code Online (Sandbox Code Playgroud)

我的机器配置:

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
Run Code Online (Sandbox Code Playgroud)

对我来说, getchar_unlocked 是在不维护缓冲区的情况下读取字节的最快方法。