奇技淫巧:NOIP的读入优化
最近看到洛谷上面有一个读入优化的代码:
inline char get_char(){//劲者快读 static char buf[1000001],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++; } inline short read(){ short num=0; char c; while(isspace(c=get_char())); while(num=num*10+c-48,isdigit(c=get_char())); return num; }
说实话第一个函数get_char的第二行,这么长一六三目运算符真心看不懂
(下面的read函数里面那个isspace()和isdigit()就是判断这个字符是不是空格,是不是数字,是的就返回true,不是返回false。你看多没用的函数= =)
然后我就把代码百度了一下,发现还真有类似的东西:
inline char NC(void) { static char buf[100000], *p1 = buf, *p2 = buf; if (p1 == p2) { p2 = (p1 = buf) + fread(buf, 1, 100000, stdin); if (p1 == p2) return EOF; } return *p1++; }
于是研究了一会,发现这是一个极其神奇的读入优化
一般来说我们读入都用的scanf和cin,实在必要的时候可以用getchar读入优化。
然后众所周知,cin比scanf慢,getchar最快。
但是到底差距有多大很多人都不知道。
于是上个星期我做了一个贼有意思的测试,把scanf、cin、getchar(分为宏定义函数和内联函数两个)分别读入1000000个数,然后输出运行时间。
getchar的两个函数贴在这里:
#define gi(a) do { \ register char ch; \ while((ch = getchar()) > '9' || ch < '0'); \ for(a = ch-'0'; (ch = getchar()) >= '0' && ch <= '9'; a = a*10+ch-'0'); \ }while(0) inline void gi2(int &a) { register char ch; while((ch = getchar()) > '9' ||ch < '0'); for(a = ch-'0'; (ch = getchar()) >= '0' && ch <= '9'; a = a*10+ch-'0'; } //(仅限正整数)
发现scanf大概比cin快2倍,getchar比cin快5倍。
其中宏定义的getchar稍稍比内联的getchar快那么一点点点点。
然后就是今天我看到的玄学优化了。
https://www.byvoid.com/zhs/blog/fast-readfile
这里有所有读入的速度评估(看来我还不是第一个干这种贼有意思的事情的wwww)
经过实测,这种优化比scanf快了10倍!
这个读入用的是fread,我百度了一下,这是一种直接把文件所有字符全部读入的一个函数。
函数原型是这样的:
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
可以看到有四个参数,分别是读入的数组,读入每个数据项的字节数,读入的个数,以及读入的文件stream。
返回的是读到的数据项的个数。
此外还有个static关键字,就把它看成是函数里面的全局变量就行了,也就是说再用一次这个函数,里面的值不会变的。
此外static只有在第一次定义的时候才能赋值,之后调用的时候赋值是无效的,也就是说之后的调用函数这个赋值那一行是没用的。
这里还有个点,看这一行:
p2 = (p1 = buf) + fread(buf, 1, 100000, stdin);
这里的=号的返回值就是赋值号右边的那个值,也就是buf的值。
那么就等于写成这样:
p1 = buf; p2 = buf+ fread(buf, 1, 100000, stdin);
在我们一开始调用函数的时候,p1 p2都被赋值为buf的数组开始的位置,之后进入if语句,p2变成数据最后的位置(开始的位置+数据的长度 = 最后的位置)。
然后随着每次返回,p1都会往前面移动一个,直到p1也遍历到了最后的位置,p1 == p2
这时候再次进入if语句,然后同样p2变成数据最后的位置,因为p1这时也是数据最后的位置了,所以p1照样==p2,于是数据遍历结束了,返回文件结束符EOF.
就这样,完美模拟一次文件读写~
然后把if语句简化就变成了那个很长一六的三目运算符:
inline char get_char(){//劲者快读 static char buf[1000001],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++; }
可以把这个模板背一下,然后就直接套自己的getchar读入优化模板了。
当然也可以把这里面的static索性全部拿到外面去,变成全局变量,然后做一个宏定义:
char buf[1000001],*p1=buf,*p2=buf; #define get_char() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
虽说这样肯定比内联函数快,但是因为宏定义毕竟是机械式替换的……所以不保证不出玄学bug,慎用。(不过一般来说用我自己写的模板不会有问题)
(其实我不会把只有一行语句的宏用do while(0)封装起来,所以只能打个括号完事)
这个优化虽然快是快,但是因为奇技淫巧……不保证不出bug,所以还是谨慎使用。
就当做普及一个玄学优化吧。
这里有一个大神写的整合版,大致原理是一样的,不过代码更简单,而且读入比我的又要快一倍(用的指针+一次性读入所有的整数),代码如下
const int MAXS = 60*1024*1024; const int MAXN = 10000000; char buf[MAXS]; int numbers[MAXN]; void analyse(char *buf,int len = MAXS) { int i; numbers[i=0]=0; for (char *p=buf;*p && p-buf<len;p++) if (*p == ' ') numbers[++i]=0; else numbers[i] = numbers[i] * 10 + *p - '0'; } void fread_analyse() { freopen("data.txt","rb",stdin); int len = fread(buf,1,MAXS,stdin); buf[len] = '\0'; analyse(buf,len); }
最后贴上我把这个玄学优化写进我的玄学getchar里面弄出的超级巨型玄学贼有意思奇技淫巧读入优化
char buf[1000001],*p1=buf,*p2=buf; #define get_char() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++) #define gi(a) do { \ register char ch; \ while((ch = get_char()) > '9' || ch < '0'); \ for(a = ch-'0'; (ch = get_char()) >= '0' && ch <= '9'; a = a*10+ch-'0'); \ }while(0)