最近看到洛谷上面有一個讀入優化的代碼:
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)