快读
以P10815为例。
IO 是这道大数据求和题真正的瓶颈。
数据规模最高到 \(10^8\),直接用 cin 或者 scanf 读数据,很容易卡在输入环节超时。
手写快读是比较直接的解决办法。
IO 性能模型
单次系统调用开销远高于普通算术运算。
读取总耗时
\T_{io}=T_{syscall}+T_{copy}+T_{parse} \\
\(T_{syscall}\) 代表系统调用开销,\(T_{copy}\) 是内核向用户空间拷贝数据的耗时,\(T_{parse}\) 负责字符转数字。
快读本质就是降低这三项开销。
思路
利用 fread 一次性把一大段输入拉进用户侧缓冲区,后续所有字符解析都在用户内存完成。
减少频繁进入内核的切换成本。缓冲区大小需要权衡,过小会多次读取,过大占用更多内存。
解析逻辑
遍历缓冲区内字符,跳过空格、换行这类空白分隔符。
遇到负号记录符号,依次读取数字字符并计算数值。
写解析代码时尽量精简分支,减少CPU流水线停顿。解析完成后移动指针,继续处理下一组数字。
code
#include<cstdio>
char buf[1<<25];
int ptr;
inline int read(){
int x=0,f=1;
while(buf[ptr]<'0'||buf[ptr]>'9'){
if(buf[ptr]=='-')f=-1;
ptr++;
}
while(buf[ptr]>='0'&&buf[ptr]<='9'){
x=x*10+buf[ptr]-'0';
ptr++;
}
return x*f;
}
int main(){
fread(buf,1,sizeof(buf),stdin);
ptr=0;
int n=read();
long long sum=0;
for(int i=1;i<=n;i++)sum+=read();
printf("%lld\n",sum);
return 4352;//🤪
}
编译优化
编译时开启 O2。
inline 函数会被内联,省去函数调用开销。
编译器会简化循环与分支,减少指令周期。
O2不会改动程序逻辑,不会破坏读入正确性。
其他
关闭同步的 cin 速度会提升,但上限不如整块缓冲快读。
scanf自带缓冲,但封装逻辑重,亿级数据下速度不足。
getchar 逐字符读取,依赖库缓冲,调用次数更多,性能弱于 fread 整块读取。
内存与缓冲区
大容量缓冲区不要放在栈里,栈空间有限,容易栈溢出。
全局变量存放在静态区,适合做大缓冲。
缓冲区尺寸选择2的幂次,方便内存对齐,提升访问速度。
边界
这种一次性预读写法,输入总量超过缓冲区容量时,单次 fread 无法读完,需要改成循环填充缓冲区。
输入格式错乱会导致指针偏移,解析结果出错。
流水线与分支优化
解析代码里的条件分支会引发分支预测失败,增加耗时。
调整判断顺序可以提升预测成功率。
无分支写法能够消除分支开销,但可读性变差,调试麻烦。