P10815 快速读入

cly312 / 2024-09-18 / 原文

C++标准库提供了强大的输入输出方法。但是,出于设计与安全上的原因,它们的性能不一定能满足算法竞赛的需求,因此在面对巨大的输入输出文件时可能需要考虑优化。

注意

  1. 通常来说,不必特别在意读写优化。
  2. 如果习惯 cin/cout 的话,在文件较大时记得关闭流同步,使用 '\n' 而不是 endl
  3. 输入输出文件大小十分极端时,才需要考虑手写读写优化。这通常意味着输入输出文件超过了 \({10}^{6}\)int ,即使在这个量级下,手写输入与标准库的差距仍通常不到0.1s。

当然,可以通过以下代码关闭流同步提升 cin/cout 性能。关闭流同步后,cin/cout 会拥有自己的缓冲区,与 scanf/printf 混用会带来异常。

ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
  1. cout<<endl; 会使用系统调用刷新输出缓冲区,输出大量换行时非常缓慢。

如果需要手写输入函数,使用getchar读取整数是一个有效减少输入时间的手段。

template<typename T1>
void read(T1 &x){
    char c;
    c=getchar();
    bool flag=1;//是否为正数
    while(c<'0'||c>'9'){//在整数前通常有一些其它字符
        if(c=='-'){
            flag=0;
        }
        c=getchar();
    }
    x=0;
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+c-'0';//使用位运算优化x*10
        c=getchar();
    }
    x=flag?x:(-x);
}

注意:
在输入到达文件结束时继续读取,getchar()会返回-1,导致死循环。如果遇到输入可能不完整的情况,需要在第一个while里判断EOF。

上面的程序中 getchar() 仍可以优化。stdio库里维护了一个缓冲区,以减少读取硬盘的次教:我们可以自己调用 fread ,来进一步减少读取硬盘的次数和标准库边界检查的时间。标准库些里通常缓冲区的大小为8KiB,事实上这是一个合理的大小:更大的缓冲区性能提升非常小。我们可以选择稍大一些的2的次幂作为缓冲区的大小。

const int SIZE=1<<14;//16Kib
char getc(){
    static char buf[SIZE],*begin=buf,*end=buf;
    if(begin==end){
        begin=buf;
        end=buf+fread(buf,1,SIZE,stdin);
    }
    return *begin++;
}

使用以上代码中的 getc() 函数替代 getchar() ,可以获得部分性能提升。

如果评测系统为Linux,可以考虑直接使用getchar_unlocked()替代getchar(),效率接近fread。这是一种放弃了线程安全的函数,但是对算法竞赛来说没有弊端。

知道了上述原理,我们就可以写出此题代码了:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0;
    bool b=false;
    char ch=getchar_unlocked();
    while(ch<'0'||ch>'9'){
        if(ch=='-') b=true;
        ch=getchar_unlocked();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar_unlocked();
    }
    if(b) return -x;
    else return x;
}
int main(){
    int n,sum=0;
    n=read();
    while(n--) sum+=read();
    printf("%d\n", sum);
    return 0;
}