申明
首先,这是一个纯粹的技术讨论贴,来自群里的清华在读大神lkmcfj的测试数据,本人只是整理和简单总结。再次感谢大神熬夜肝。
目的
我们知道在OI中,常有海量数据读入需求,如何降低数据读入的时间,是一个重要的环节。因此C++党和C党对cin和scanf的争论持续不断,本人只是中立的说明一个测试。也许在不同的环境、不同的机器下,可以得到不同的测试结果。不喜可以喷。
测试环境
UBUNTU18.04 + gcc7.4.0
程序目的
从test.in读出数据,并求和。看不同的数据读入方法消耗的时间。读取的方法将包括
- scanf
- fread
- fread+sscanf
- cin快速读取
储备知识
在测试过程中,使用到了time命令。下面简单介绍一下time命令。
作用
输出的信息分别显示了该命令所花费的 real 时间、user 时间和 sys 时间。
语法
time (参数) (指令)
参数,time提供的参数。具体请参考 help。
指令,就是需要运行的命令。比如我们输入time ls,测试一下 ls 这个命令消耗的时间。
[root@localhost ~]# time ls
anaconda-ks.cfg install.log install.log.syslog satools text
real 0m0.009s
user 0m0.002s
sys 0m0.007s
输出解析
看到没有,执行时间一下子就统计出来了。但输出内容中有三个统计时间,real、user 和 sys,它们都代表什么含义呢?哪个才是 ls 命令的执行时间呢?下面我们就一起来看看这三个统计时间。
(1) real:从进程 ls 开始执行到完成所耗费的 CPU 总时间。该时间包括 ls 进程执行时实际使用的 CPU 时间,ls 进程耗费在阻塞上的时间(如等待完成 I/O 操作)和其他进程所耗费的时间(Linux 是多进程系统,ls 在执行过程中,可能会有别的进程抢占 CPU)。
(2) user:进程 ls 执行用户态代码所耗费的 CPU 时间。该时间仅指 ls 进程执行时实际使用的 CPU 时间,而不包括其他进程所使用的时间和本进程阻塞的时间。
(3) sys:进程 ls 在内核态运行所耗费的 CPU 时间,即执行内核系统调用所耗费的 CPU 时间。
现在,我们应该对这三个时间非常清楚了吧。ls 命令的真正执行时间是多少?答案就是 user+sys 的时间,但一般情况下,real=user+sys,因而我们就使用 real 的时间作为 ls 的执行时间了。
当然,这个时间结果有坑,具体我们这里不说明,不是本文讨论的范围。
测试结果
test.in文件结构
文件里第一行是 n,第二行是 n 个[ 0, 1e9]范围的整数。
运行结果
二话不说,我们先上图,再解释。
运行结果说明
第一个时间是使用scanf()函数读取test.in,并求和。消耗的时间。
第二个时间是使用fread()函数读取test.in,然后手动分析数据,求和。消耗的时间。
第三个时间是使用fread()函数读取test.in,使用sscanf()分析数据,求和。消耗的时间。
第四个时间是使用cin快读,并求和。消耗的时间。
对应的程序
使用pastebin的链接,https://paste.ubuntu.com/p/w3wKCRfTpj/。再次感谢lkmcfj做出的贡献。或者直接对应的代码如下。
数据生成程序
// generator
#include <cstdio>
#include <random>
constexpr int N = 10000000, MAXV = 1000000000;
int main()
{
std::random_device rd;
std::mt19937 engine(rd());
std::uniform_int_distribution<int> genint(0, MAXV);
printf("%d\n", N);
for(int i = 0; i < N; ++i) printf("%d ", genint(engine));
return 0;
}
naive scanf
// naive scanf
#include <cstdio>
int main()
{
int n;
scanf("%d", &n);
long long sum = 0;
while(n--)
{
int t;
scanf("%d", &t);
sum += t;
}
printf("%lld\n", sum);
return 0;
}
fread(manual parsing)
// fread(manual parsing)
#include <cstdio>
#include <cctype>
#include <cstddef>
struct input_parser
{
static constexpr size_t BUFFER_SIZE = 10000000;
FILE *stream;
char *buffer, *end_buffer, *cur;
input_parser(FILE *s):
stream(s), buffer(new char[BUFFER_SIZE]), cur(buffer)
{
size_t size = fread(buffer, 1, BUFFER_SIZE, stream);
end_buffer = buffer + size;
}
~input_parser() {delete[] buffer;}
void go()
{
++cur;
if(cur == end_buffer)
{
size_t size = fread(buffer, 1, BUFFER_SIZE, stream);
end_buffer = buffer + size;
cur = buffer;
}
}
template <class IntType>
IntType get()
{
IntType ret = 0;
while(!isdigit(*cur)) go();
while(isdigit(*cur))
{
ret = ret * 10 + *cur - '0';
go();
}
return ret;
}
};
int main()
{
input_parser parser(stdin);
int n = parser.get<int>();
long long sum = 0;
while(n--) sum += parser.get<int>();
printf("%lld\n", sum);
return 0;
}
fread + sscanf
// fread + sscanf
#include <cstdio>
#include <cctype>
#include <cstddef>
struct input_parser
{
static constexpr size_t BUFFER_SIZE = 10000000;
FILE *stream;
char *buffer, *end_buffer, *cur;
input_parser(FILE *s):
stream(s), buffer(new char[BUFFER_SIZE]), cur(buffer)
{
size_t size = fread(buffer, 1, BUFFER_SIZE, stream);
end_buffer = buffer + size;
}
~input_parser() {delete[] buffer;}
char *next_space()
{
char *finder = cur;
while(finder != end_buffer && isspace(*finder)) ++finder;
while(finder != end_buffer && !isspace(*finder)) ++finder;
return finder;
}
template <class IntType>
IntType get() // assuming cur != end_buffer
{
char *nexts = next_space();
if(nexts == end_buffer && end_buffer == buffer + BUFFER_SIZE)
{
size_t used_head = 0;
while(cur != end_buffer) buffer[used_head++] = *cur++;
size_t size = fread(buffer + used_head, 1, BUFFER_SIZE - used_head, stream);
end_buffer = buffer + used_head + size;
cur = buffer;
nexts = next_space();
}
*nexts = 0;
IntType t;
sscanf(cur, "%d", &t);
cur = nexts + 1;
if(cur == end_buffer && end_buffer == buffer + BUFFER_SIZE)
{
size_t size = fread(buffer, 1, BUFFER_SIZE, stream);
end_buffer = buffer + size;
cur = buffer;
}
return t;
}
};
int main()
{
input_parser parser(stdin);
int n = parser.get<int>();
long long sum = 0;
while(n--) sum += parser.get<int>();
printf("%lld\n", sum);
return 0;
}
cin快读
最后:鄙视一下大神的代码风格。开玩笑的。