关于读入优化的分析

关于读入优化的分析

摘要

身为一只以卡常为生的蒟蒻,总想着经过一些奇技淫巧来掩饰优化常数。ios

因而本文章就非正式地从最初的开始到最终的终止来谈谈在OI种各类主流、非主流读入的速度以及利弊。c++

序言

随着算法的发展各类数据结构等劲题出现,这些题除了思惟难度的提升还带来者输入数据的增多(特别的有:uoj.ac上的一道题须要选手本身生成数据,而数论题每每输入较少),对于有追求有理想的选手快速读入是必不可少的技能。算法

尽管市面上有不一样的主流读入优化,可是大多都是基于fread的,其他的只是一些小变更。ubuntu

而笔者就在不久以前发现更快可是非主流的mmap(在sys/mman.h中)函数,此函数比目前已知全部读入都快。数组

如今,咱们从入门的cin_with_sync(true)而后到进阶的cin_with_sync(false),再到标准的scanf而后到getchar,再到fread(old),再是fread(new),最后是mmap的原理及分析。缓存

标准

本次测试在如下环境进行:数据结构

  1. 硬件:函数

    a) VM WorkingStation Pro 14虚拟机测试

    b) 基于Ubuntu 14.04 LTS 32位 的NOI Linux 1.4.1优化

    c) 内存1003.1MiB,硬盘19.9GB,CPU Intel® Core™ i7-6498DU CPU @ 2.50GHz,GPU Gallium 0.4 on SVGA3D; build: RELEASE;

  2. 软件: a) 编译器G++ posix gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04)

    b) 测评器:Project Lemon v1.2 测试版

    c) 编译命令:g++ -o %s %s.*(不加入-std=c++11的缘由是由于c++11标准会忽略部分例如register的语句,同时NOI的编译命令也没有此条)

  3. 文件: a) 输入文件:两组,大小分别为127585438 Byte 和 127582201 Byte,前半部分为11111111个不超过INT_MAX(在climits内)的非负整数,用空格分隔,中间一个换行符,紧接着一行由11111111个id>=48的字符组成。

    b) 输出文件:为了不代码的部分被过度优化,最后程序将根据输入计算一个值,而后输出这个值。详见代码。

代码

如下代码尽可能按照最快的方式尽可能写成函数:

 

  1 //cin_with_sync(true)
  2 #include <cstdio>
  3 #include <iostream>
  4 
  5 using namespace std;
  6 
  7 #define MAXN 11111111
  8 
  9 inline int test(){
 10     int recieve_int, ret = 0;
 11     for(int i = 0; i < MAXN; i++){
 12         cin >> recieve_int;
 13         ret += recieve_int;
 14     }
 15     char recieve_char;
 16     for(int i = 0; i < MAXN; i++){
 17         cin >> recieve_char;
 18         ret -= recieve_char;
 19     }
 20     return ret + 1;
 21 }
 22 
 23 
 24 int main(){
 25     freopen("fr.in", "r", stdin);
 26     printf("%d", test());
 27     fclose(stdin);
 28     return 0;
 29 }
 30 //cin_with_sync(false)
 31 #include <cstdio>
 32 #include <iostream>
 33 
 34 using namespace std;
 35 
 36 #define MAXN 11111111
 37 
 38 inline int test(){
 39     ios::sync_with_stdio(false);
 40     cin.tie(0);
 41     int recieve_int, ret = 0;
 42     for(int i = 0; i < MAXN; i++){
 43         cin >> recieve_int;
 44         ret += recieve_int;
 45     }
 46     char recieve_char;
 47     for(int i = 0; i < MAXN; i++){
 48         cin >> recieve_char;
 49         ret -= recieve_char;
 50     }
 51     return ret + 1;
 52 }
 53 
 54 
 55 int main(){
 56     freopen("fr.in", "r", stdin);
 57     printf("%d", test());
 58     fclose(stdin);
 59     return 0;
 60 }
 61 //scanf
 62 #include <cstdio>
 63 
 64 using namespace std;
 65 
 66 #define MAXN 11111111
 67 
 68 inline int test(){
 69     int recieve_int, ret = 0;
 70     for(int i = 0; i < MAXN; i++){
 71         scanf("%d", &recieve_int);
 72         ret += recieve_int;
 73     }
 74     char recieve_char;
 75     scanf("%c", &recieve_char), scanf("%c", &recieve_char);
 76     for(int i = 0; i < MAXN; i++){
 77         scanf("%c", &recieve_char);
 78         ret -= recieve_char;
 79     }
 80     return ret + 1;
 81 }
 82 
 83 
 84 int main(){
 85     freopen("fr.in", "r", stdin);
 86     printf("%d", test());
 87     fclose(stdin);
 88     return 0;
 89 }
 90 //getchar
 91 #include <cstdio>
 92 
 93 using namespace std;
 94 
 95 #define MAXN 11111111
 96 
 97 inline int read(){
 98     int num = 0;
 99     char c;
100     while((c = getchar()) < 48);
101     while(num = num * 10 + c - 48, (c = getchar()) >= 48);
102     return num;
103 }
104 
105 inline int test(){
106     int recieve_int, ret = 0;
107     for(int i = 0; i < MAXN; i++){
108         recieve_int = read();
109         ret += recieve_int;
110     }
111     char recieve_char;
112     while((recieve_char = getchar()) < 60);
113     ret -= recieve_char;
114     for(int i = 0; i < MAXN; i++){
115         recieve_char = getchar();
116         ret -= recieve_char;
117     }
118     return ret;
119 }
120 
121 
122 int main(){
123     freopen("fr.in", "r", stdin);
124     printf("%d", test());
125     fclose(stdin);
126     return 0;
127 }
128 //fread(old)
129 #include <cstdio>
130 
131 using namespace std;
132 
133 #define MAXN 11111111
134 
135 #define Finline __inline__ __attribute__ ((always_inline))
136 
137 Finline char get_char(){
138     static char buf[200000001], *p1 = buf, *p2 = buf;
139     return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 200000000, stdin), p1 == p2) ? EOF : *p1 ++;
140 }
141 inline int read(){
142     int num = 0;
143     char c;
144     while((c = get_char()) < 48);
145     while(num = num * 10 + c - 48, (c = get_char()) >= 48);
146     return num;
147 }
148 
149 inline int test(){
150     int recieve_int, ret = 0;
151     for(int i = 0; i < MAXN; i++){
152         recieve_int = read();
153         ret += recieve_int;
154     }
155     char recieve_char;
156     while((recieve_char = get_char()) < 60);
157     ret -= recieve_char;
158     for(int i = 0; i < MAXN; i++){
159         recieve_char = get_char();
160         ret -= recieve_char;
161     }
162     return ret;
163 }
164 
165 
166 int main(){
167     freopen("fr.in", "r", stdin);
168     printf("%d", test());
169     fclose(stdin);
170     return 0;
171 }
172 //fread(new)
173 #include <cstdio>
174 
175 using namespace std;
176 
177 #define MAXN 11111111
178 
179 #define Finline __inline__ __attribute__ ((always_inline))
180 
181 Finline char get_char(){
182     static char buf[200000001], *p1 = buf, *p2 = buf + fread(buf, 1, 200000000, stdin);
183     return p1 == p2 ? EOF : *p1 ++;
184 }
185 inline int read(){
186     int num = 0;
187     char c;
188     while((c = get_char()) < 48);
189     while(num = num * 10 + c - 48, (c = get_char()) >= 48);
190     return num;
191 }
192 
193 inline int test(){
194     int recieve_int, ret = 0;
195     for(int i = 0; i < MAXN; i++){
196         recieve_int = read();
197         ret += recieve_int;
198     }
199     char recieve_char;
200     while((recieve_char = get_char()) < 60);
201     ret -= recieve_char;
202     for(int i = 0; i < MAXN; i++){
203         recieve_char = get_char();
204         ret -= recieve_char;
205     }
206     return ret;
207 }
208 
209 
210 int main(){
211     freopen("fr.in", "r", stdin);
212     printf("%d", test());
213     fclose(stdin);
214     return 0;
215 }
216 //mmap
217 #include <cstdio>
218 #include <fcntl.h>
219 #include <unistd.h>
220 #include <sys/mman.h>
221 
222 using namespace std;
223 
224 #define MAXN 11111111
225 
226 #define Finline __inline__ __attribute__ ((always_inline))
227 
228 char *pc;
229 
230 inline int read(){
231     int num = 0;
232     char c;
233     while ((c = *pc++) < 48);
234     while (num = num * 10 + c - 48, (c = *pc++) >= 48);
235     return num;
236 }
237 
238 inline int test(){
239     pc = (char *) mmap(NULL, lseek(0, 0, SEEK_END), PROT_READ, MAP_PRIVATE, 0, 0);
240     int recieve_int, ret = 0;
241     for(int i = 0; i < MAXN; i++){
242         recieve_int = read();
243         ret += recieve_int;
244     }
245     char recieve_char;
246     while((recieve_char = *pc++) < 60);
247     ret -= recieve_char;
248     for(int i = 0; i < MAXN; i++){
249         recieve_char = *pc++;
250         ret -= recieve_char;
251     }
252     return ret + 1;
253 }
254 
255 
256 int main(){
257     freopen("fr.in", "r", stdin);
258     printf("%d", test());
259     fclose(stdin);
260     return 0;
261 }
262 //数据生成器
263 #include <ctime>
264 #include <cstdio>
265 #include <climits>
266 #include <algorithm>
267 
268 #define MAXN 11111111
269 
270 int main(){
271     freopen("fr.in", "w", stdout);
272     srand(time(NULL));
273     for(int i = 0; i < MAXN; i++) printf("%d ", rand() % INT_MAX);
274     puts("");
275     for(int i = 0; i < MAXN; i++) putchar(rand() % 48 + 79);
276     fclose(stdout);
277     return 0;
278 }

 

结果

咱们在测评机下作了屡次试验,调整了代码的部分细节,取最后一次测试成绩以下:

 

 

 

并且据最新试验代表,在Luogu_OJ上对于正常输入的题平均每1.5MB的输入mmap和fread(new)具备时间差4ms

分析

cin_with_sync(true)当然慢,其缘由是由于为了其保持和scanf等函数的输出保持同步,因此一直会刷新流,因此当然慢。然而因为cin“比较智能”,因此用它也有理由,并且使用cout输出long long会比printf快很多。

因此cin_with_sync(false)会快很多的缘由同上。

然而咱们惊奇地发现scanf“居然”比cin_with_sync(false)慢!?其实在实际测试过程当中,二者的速度不相上下,都是差很少的。

getchar是笔者第一个学的快读,而后其实在实际使用中这种快读比scanf的优点更为明显,特别是在分散读入的时候。然而如今二者跑出了仅差0.2s的成绩,其实也不用惊讶,由于在之前的scanf的实现主要在loc_incl.h内的_doscan函数内,观察这个函数发现它就是你的快读的整合版。

fread(old)利用fread函数把全部输入一次性输入到程序内的缓存数组而后用getchar式快读调用。好处就是文件操做少,于是速度快。

fread(new)和fread(old)的惟一区别就是fread(new)只读了一次文件,而fread(old)容许读屡次。fread(old)其实是为了防止数据分几回输入,然而于是函数较长,不太有可能被inline优化。而fread(new)则能够避免这些。同时在实际使用中,fread(new)也具备更快的速度。

mmap基于Linux的黑科技,直接将文件映射到内存操做,中间不须要阻塞系统调用,不须要内核缓存,只须要一次lseek,于是有更优的速度,是极限卡常者不二选择。在fread(new)已经很是快的状况下再甩36ms,并且实际使用的时候速度更快。

总结

对于初学者来讲,cin和scanf足矣。

然而若是是在须要cin来读字符串或者某些奇怪的东西的话,建议不要流同步。固然若是你知道流的概念以后也能够灵活使用。

能够发现getchar是全部方法中空间最小的,由于它的实现不须要scanf那样把全部状况枚举出来,也不须要额外数组,适用于平常生活。

而fread是在各个平台(实测在某些平台上(例如Win32的部分机子)是会出现没法读入的状况,但通常测评系统都会支持,因此在提交时能够改掉)下均可以使用的一个比较快速的读入方式,同时在gdb中,fread须要使用EOF,而这就能够方便文本终端中一次性把数据输入gdb。同时你能够用缓存数组进行一些更高级的操做。

mmap只能在Linux下使用,并且不接受键盘读入 ,建议在确保程序无误后使用。

 

更新

鉴于在实际使用过程当中,有extern inline优化(更强的内联优化),可是这种优化须要慎用。

因而咱们顺便来说一下inline的做用。

inline是一种浪费空间而换取时间的玩意。

通常在没有复杂语句,没有循环,屡次调用的地方用。

对于通常的inline,编译器经常会忽略这种优化,除非像这么简单(通常来讲,带有循环、递归、switch、goto、static都不会inline)的:

1 __asm__ __volatile__ ("\tmovq %1, %%rax \n\t imulq %2 \n\t idivq %3\n" : "=d"(ret): "m"(a), "m"(b), "m"(MODS) : "%rax");

而后对于static inline,编译器会有所重视(固然详细的信息须要深刻研究),对于__函数声明前的调用__、递归调用、被经过地址应用的,编译器会像普通函数同样编译。

对于extern inline,编译器大部分都会展开,达到一个相似于宏函数的效果,可是比宏函数略微高级的是它采用的不是直接替换,例如#define pow(x) x * x,而后调用pow(10 + 10)就会出问题,可是extern inline不会。可是彷佛extern inline有时候使用会有问题,例如我就遇到过在extern inline过的函数对全局变量操做出错的问题。

而后目前来讲彷佛对于几种快速读入,笔者用起来仍是没有问题的。固然这还受到代码细节的影响。

 

协议

本做品采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行许可。

相关文章
相关标签/搜索