2012-07-13 14 views
1

假設我有一個char buf[12];,我知道它總是有一個右對齊的無符號數字填充在左邊的空格。因此,例如:_________329(其中_代表空間)。我能想到解析它的最快的方法是這樣的:如果我有一個固定大小的填充ascii數字的字符數組,我知道它指的是一個無符號的整數,那麼將其轉換爲這樣的最快方法是什麼?

while (*buf == ' ') buf++; 
atoi(buf); 

,但我不知道是否有一個更快的方法,特別是與atoi因爲我們知道它是無符號的這atoi不承擔..

+0

你不能使用'string'? – 2012-07-13 20:21:35

+5

'atoi'跳過領先的空間。爲什麼你的循環會比它更快呢? – 2012-07-13 20:49:55

+0

@JamesKanze - 也許他知道他的數字通常少於6個字符,所以從字符串的末尾開始更快?另外,'前導空格'可能是對'isspace()'的調用,這肯定比對''''的測試慢。 – 2012-07-13 21:37:23

回答

6

我假設第一個字符是爲「潛在符號」保留的,並且總是「空格」?否則,您只需要char[11]而不是char[12]。無論如何,固定尺寸允許手動循環展開:

unsigned parse(const char(&b)[12]) 
{ 
    return ((((((((((b[1] & 15)) 
      * 10 + (b[2] & 15)) 
      * 10 + (b[3] & 15)) 
      * 10 + (b[4] & 15)) 
      * 10 + (b[5] & 15)) 
      * 10 + (b[6] & 15)) 
      * 10 + (b[7] & 15)) 
      * 10 + (b[8] & 15)) 
      * 10 + (b[9] & 15)) 
      * 10 + (b[10]& 15); 
} 

注意,& 15特技對待空間和零點相同,並且將與兩個ASCII(空間= 32,零= 48)和EBCDIC(空間工作= 48,零= 240)。我還沒有檢查出其他字符編碼:)

這實際上會比atoi快嗎?找出答案的唯一方法是衡量。但是在任何情況下,我都可能會使用atoi,因爲使用標準函數會提高可讀性。

+0

我最終做了一些非常類似的事情! – 2012-07-13 22:41:01

+1

你真的獲得了任何速度? – fredoverflow 2012-07-13 22:42:56

+0

是的,基本上是2x2記憶矩陣類方法。 – 2012-07-14 20:53:02

0

可能是這樣的代碼會更快:

char *begin = strrchr(buf, ' '); 
atoi(begin ? begin : buf); 

假設快捷的平臺優化的標準函數strrchr。

1

首先,問問自己爲什麼要這樣做。如果'buf'是一個長度很大的文件,那麼你可能會受到Schmiel the Painter algorithm的影響,如果你有很多十進制數字,你可能會遇到問題時,使用例如。 GMP(請參閱有符號整數算術的mpz部分)

其次,考慮您知道您的標準庫不知道什麼。 Dmitry suggests使用'快速平臺優化strrchr,但沒有什麼strrchr可以做到解決遍歷字符串的問題,並且strrchr實際上有其他限制,如搜索終止空字符。

你可能知道一些事情,如:

  • 您的號碼絕不會是負的;即atoi不需要拿起領先的+/-符號。但是,您已經正確地注意到了這一點,但這可能不是時機的主要因素。
  • 你的號碼大部分會很短或者很長,這將決定你是否應該開始在字符串的開始或結尾尋找空格。值得注意的是,strrchr不知道字符串的長度,因此總是從頭開始讀取,就像我在查看的atoi的實現(在Newlib中)。您的示例代碼也意味着從字符串的開始處進行搜索。
  • 你的號碼總是以10爲底。這會消除一些數學問題。
  • 你的號碼總是符合無符號長整數。是的,這是保證,因爲他們是12個字符,但atoi不知道這一點,並會做一些嘗試來處理錯誤。另外,atoi()會返回一個有符號的整數,所以如果您需要像1,000,000,000這樣的13位數字,那麼這是需要解決的問題。
  • 其他我還沒有想過的東西;但你可能會。

您應該從開始閱讀源。從這個簡單的練習中可以獲得很多!我最近一直在使用Newlib並下載並打開,所以這就是我會參考的內容,但是GNU的glibc以及Windows使用的任何內容都可能會有所不同。乍一看,我看到一個簡單的優化:atoi只是一個調用strtol,或'字符串到長'(int和long都是我的平臺上的32位,'長'是必要得到更大的東西)。編譯器可能會將其優化爲直接調用,但它可能會爲我們節省一個週期。對於表面上速度敏感的應用程序,請直接調用strtol()。或者說,撥打strtoul,'string to unsigned long',因爲這就是你正在做的事情。現在我們已經有了一個函數,不會調用其他任何顯着的東西,讓我們來看看它。現在忽略重入的東西。小心括號,一些ifs有他們和相關的elses沒有他們(這是國際海事組織的不良風格,我更喜歡到處都是括號)。

unsigned long _strtoul_r 
    (struct _reent *rptr, _CONST char *nptr, char **endptr, int base) 
{ 
    register const unsigned char *s = (const unsigned char *)nptr; 
    register unsigned long acc; 
    register int c; 
    register unsigned long cutoff; 
    register int neg = 0, any, cutlim; 

    /* 
    * See strtol for comments as to the logic used. 
    */ 
    do { 
    c = *s++; 
    } while (isspace(c)); 
    if (c == '-') { 
    neg = 1; 
    c = *s++; 
    } else if (c == '+') 
    c = *s++; 
    if ((base == 0 || base == 16) && 
    c == '0' && (*s == 'x' || *s == 'X')) { 
    c = s[1]; 
    s += 2; 
    base = 16; 
    } 
    if (base == 0) 
    base = c == '0' ? 8 : 10; 
    cutoff = (unsigned long)ULONG_MAX/(unsigned long)base; 
    cutlim = (unsigned long)ULONG_MAX % (unsigned long)base; 
    for (acc = 0, any = 0;; c = *s++) { 
    if (isdigit(c)) 
     c -= '0'; 
    else if (isalpha(c)) 
     c -= isupper(c) ? 'A' - 10 : 'a' - 10; 
    else 
     break; 
    if (c >= base) 
     break; 
    if (any < 0 || acc > cutoff || (acc == cutoff && c > cutlim)) 
    any = -1; 
    else { 
     any = 1; 
     acc *= base; 
     acc += c; 
    } 
    } 
    if (any < 0) { 
    acc = ULONG_MAX; 
    rptr->_errno = ERANGE; 
    } else if (neg) 
    acc = -acc; 
    if (endptr != 0) 
    *endptr = (char *) (any ? (char *)s - 1 : nptr); 
    return (acc); 
} 

從函數定義出發,我們注意到,有一些重入克魯夫特可以剝離出來,如果我們的應用程序是單線程的。還有一個char ** ptr參數,它存儲一個指向經過解析數字的字符串的指針,這是我們不需要的。也沒有長度定義,所以它將不得不搜索空字符來查找字符串的長度。

在這個應用程序中,* s被定義爲一個寄存器,這在我的平臺上有意義,但可能不在您的平臺上。還有一些我們不需要定義的其他整數。

在do/while循環中,調用isspace(),它檢查空間,水平製表符,換行符,垂直製表符,提要和回車符。你只需要空間。此外,它從字符串的前面開始並返回。改變,如果你主要是小數字。

然後,我們做一些基礎測試的東西。基地可以是0,允許自動檢測基地(需要週期),如果它是8或16,它允許領先的0或領先'0x',我們不需要知道或測試。

接下來,我們創建'cutoff'和'cutlim'變量;你不需要這些,因爲表面上你不需要範圍檢查。

最後,我們得出最後的循環。有一個if \ else if \ else塊,用isdigit,isalphaisupper函數確定字符類型和數值。這些包含了一些與語言環境相關的代碼;看起來我們可以假設十進制值,用單個c -= 0語句替換整個if/else if/else塊。

接下來,在if (c >= base)中還有一些錯誤檢查哪些缺血並可能很好保留。回想一下,C是無符號的,所以如果* s例如是一個空格(0x20)(小於'0',0x30),則這個值將被計算爲(無符號)(0x30 - 0x20)= 255 - 10,這就是大於基數(10)。這不是完美的,但它非常好,非常便宜。

接下來,在if (any...塊中有一些邊界檢查,然後我們得到該函數的實際內容:acc *= base; acc += c;。我們很難做到這一點,但如果我們有一個二進制基數,我們可以將其轉換爲換檔。希望你的處理器擁有一個快速的硬件倍增器,如果這是一個Arduino ISR,你就會遇到麻煩。您可能想要查看DSP彙編指令,如乘法累加以加快速度,如果有的話。

在for循環之後,還有一些錯誤處理和負數處理,我們也可以忽略。

因此,要總結,我會寫一個新的函數來處理你的特殊情況,如果你做了很多:

unsigned long TwelveCharDecimalStringWithLeadingSpacestoul(char *nptr) 
{ 
    register const unsigned char *s = (const unsigned char *)nptr; 
    register unsigned long acc; 
    register int c, base = 10; 

    do { 
    c = *s++; 
    } while (c == ' '); 

    for (acc = 0;; c = *s++) { 
    c -= '0'; 
    if (c >= base) { 
     _errno = ERANGE; 
     acc = -1; 
     break; 
    } 
    acc *= base; 
    acc += c; 
    } 
    return (acc); 
} 

其取出的atoi的通用性,並使用假設你已經做得更快了一點。但是,除非這個操作發生非常多,或有發生的非常快,你可能用更簡單,更清晰,更安全,更靈活,一般較好的更好:

unsigned long result = 0; 
char *begin = strrchr(buf, ' '); 
result = strtoul(buf, NULL, 10); 

if (result == 0 && errno == ERANGE) 
    // Handle error 

編輯 :我寫完了,我注意到FredOverflow has posted a better answer。展開循環(我沒有這樣做,似乎沒有必要,但是如果有必要,任何已知持續時間的循環都可以展開),並且與& 15做一個整潔的詭計,我不得不承認這很酷。但是,上述功能仍然是在一般情況下如何解決加速某些標準庫調用問題的一個很好的例子。

+0

這是一個令人難以置信的答案......謝謝 – 2012-07-13 22:40:37

相關問題