身爲一隻以卡常爲生的蒟蒻,總想着經過一些奇技淫巧來掩飾優化常數。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的原理及分析。緩存
本次測試在如下環境進行:數據結構
硬件:函數
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;
軟件: 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的編譯命令也沒有此條)
文件: 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 中國大陸許可協議進行許可。