奇技淫巧: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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM