2008-11-07 32 views
6

通過分析我發現這裏的sprintf需要很長時間。是否有一個更好的執行選擇,仍然處理y/m/d h/m/s字段中的前導零?我該如何改進/取代我認爲是性能熱點的sprintf?

SYSTEMTIME sysTime; 
GetLocalTime(&sysTime); 
char buf[80]; 
for (int i = 0; i < 100000; i++) 
{ 

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d", 
     sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
     sysTime.wHour, sysTime.wMinute, sysTime.wSecond); 

} 

注:OP解釋的意見,這是一個精簡的例子。 「真實」循環包含使用來自數據庫的不同時間值的附加代碼。剖析指出sprintf()是罪犯。

+0

「很長時間「?我期望它是微秒而不是毫秒(取決於CPU) – Roddy 2008-11-07 13:19:44

+0

你最好找一種方式來調用sprintf,而不是頻繁地調用。 – Brian 2008-11-07 13:36:26

回答

18

如果您正在編寫自己的函數來完成這項工作,則字符串值爲0.61的查找表將避免必須對除年份以外的所有內容進行任何算術運算。

編輯:請注意,以應付閏秒(和匹配strftime()),你應該能夠打印60秒值和61

char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" }; 

另外,怎麼樣strftime()?我不知道性能如何比較(它可能只是調用sprintf()),但值得一看(它可能是在執行上面的查找本身)。

+3

+1來自一位老嵌入式工程師。當時間比尺寸更重要時,很難打敗查找表! – 2008-11-07 13:14:54

6

您可以嘗試填充輸出中的每個字符。

buf[0] = (sysTime.wYear/1000) % 10 + '0' ; 
buf[1] = (sysTime.wYear/100) % 10 + '0'; 
buf[2] = (sysTime.wYear/10) % 10 + '0'; 
buf[3] = sysTime.wYear % 10 + '0'; 
buf[4] = '-'; 

...等...

不漂亮,但你得到的圖片。如果沒有別的,它可能有助於解釋爲什麼sprintf不會那麼快。

OTOH,也許你可以緩存最後的結果。這樣你只需要每秒產生一次。

+0

在現實生活中,循環的每次迭代(從數據庫)的時間值是不同的。我簡化了示例的代碼, – 2008-11-07 13:07:17

6

Printf需要處理很多不同的格式。 您當然可以抓住source for printf並將其作爲基礎來推出自己的版本,專門處理sysTime結構。這樣,你通過一個參數,它只是完成需要完成的工作,僅此而已。

1

您可能會通過手動滾動一個在返回buf中設置數字的例程來增加perf,因爲您可以避免重複解析格式字符串,而不必處理大量更復雜的案例sprintf處理。我很討厭實際上建議這樣做。

我會建議試圖找出如果你能以某種方式減少需要產生這些字符串量,是他們選配somegtimes,它們可以被緩存等

0

很難想象,你會以格式化整數擊敗sprintf。你確定sprintf是你的問題嗎?

+0

它不是格式化整數,sprintf需要時間,它解析格式字符串。由於它在循環中不會改變,因此每次解析它都是不必要的。 – 2008-11-07 13:42:51

2

你是什麼意思「長」的時間 - 因爲sprintf()是在循環的唯一語句和循環(增量,比較)是可以忽略不計的「管道」,該sprintf()消耗大多數時候。

還記得那天晚上在第三街上失去了他的結婚戒指的人的一個老笑話,但是在5號找到了,因爲那裏的燈光更亮了?你已經構建了一個旨在「證明」你的假設的例子,即sprintf()是無效的。

除了所用的所有其他功能和算法之外,如果您配置的「實際」代碼包含sprintf(),則結果將會更加準確。或者,請嘗試編寫自己的版本,以解決您需要的特定零填充數字轉換。

您可能會對結果感到驚訝。

+0

代碼片段是一個簡化的例子。在實際的例子中,循環中有很多東西。 (char *)_bstr_t,itoa ....這裏的衝刺是不好的。 – 2008-11-07 13:11:02

+0

愛因斯坦是否說過一切都應該儘可能簡單,但並不簡單? :-)編輯你的問題來反映這一點。 – 2008-11-07 13:17:31

1

如何緩存結果?這不是一種可能嗎?考慮到這個特殊的sprintf()調用在你的代碼中過於頻繁,我假設在大多數連續調用之間,年,月和日不會改變。

因此,我們可以實現類似下面的內容。聲明一老一電流SYSTEMTIME結構:

SYSTEMTIME sysTime, oldSysTime; 

此外,申報獨立的部分來保存日期和時間:

char datePart[80]; 
char timePart[80]; 

爲,第一時間,你必須填寫這兩個sysTime,oldSysTime以及datePart和timePart。但後續sprintf()可以做得相當快,如下所示:

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond); 
if (oldSysTime.wYear == sysTime.wYear && 
    oldSysTime.wMonth == sysTime.wMonth && 
    oldSysTime.wDay == sysTime.wDay) 
    { 
    // we can reuse the date part 
    strcpy (buff, datePart); 
    strcat (buff, timePart); 
    } 
else { 
    // we need to regenerate the date part as well 
    sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay); 
    strcpy (buff, datePart); 
    strcat (buff, timePart); 
} 

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME)); 

上面的代碼有一些冗餘,使代碼更容易理解。你可以輕鬆分解。如果你知道即使是小時和分鐘也不會比你對例程的調用更快,你可以進一步加速。

+0

沒有。我的代碼片段是從真實的東西簡化。 – 2008-11-07 13:11:39

1

我會做一些事情......

  • 緩存當前的時間,這樣你就不必每次都
  • 再生時間戳手動操作時間轉換。 printf家族函數中最慢的部分是格式字符串解析,並且將循環用於每次循環執行時的解析都很愚蠢。
  • 嘗試使用2字節查找表進行所有轉換({ "00", "01", "02", ..., "99" })。這是因爲你想避免模運算,而一個2字節的表格意味着你只需要使用一個模。
2

看起來像Jaywalker提出了一個非常相似的方法(打我不到一個小時)。

除了已經建議的查找表方法(下面的n2s []數組),如何生成格式緩衝區,以便通常的sprintf不那麼密集?除非年/月/日/小時發生變化,否則下面的代碼每次只能在循環中填寫分鐘和秒鐘。很明顯,如果其中任何一個發生了變化,你確實會採用另一個sprintf命中,但總體上它可能不會超過你目前所見(當與數組查找結合在一起時)。


static char fbuf[80]; 
static SYSTEMTIME lastSysTime = {0, ..., 0}; // initialize to all zeros. 

for (int i = 0; i < 100000; i++) 
{ 
    if ((lastSysTime.wHour != sysTime.wHour) 
    || (lastSysTime.wDay != sysTime.wDay) 
    || (lastSysTime.wMonth != sysTime.wMonth) 
    || (lastSysTime.wYear != sysTime.wYear)) 
    { 
     sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s", 
       sysTime.wYear, n2s[sysTime.wMonth], 
       n2s[sysTime.wDay], n2s[sysTime.wHour]); 

     lastSysTime.wHour = sysTime.wHour; 
     lastSysTime.wDay = sysTime.wDay; 
     lastSysTime.wMonth = sysTime.wMonth; 
     lastSysTime.wYear = sysTime.wYear; 
    } 

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]); 

} 
1

我正在處理類似的問題。

我需要在嵌入式系統上記錄具有時間戳,文件名,行號等的調試語句。我們已經有了一個記錄器,但是當我把旋鈕轉到「完全記錄」時,它會吃掉我們所有的過程週期,並使我們的系統處於嚴重狀態,表明沒有計算設備應該經歷過。

有人說:「你不能測量/觀察某物,而不改變你正在測量/觀察的東西。」

所以我正在改變事情來提高性能。目前的狀況是,Im比原來的功能調用快(這個日誌系統中的瓶頸不在函數調用中,而是在日誌讀取器中是一個單獨的可執行文件,如果我寫我自己的日誌棧)。

我需要提供的接口類似於 - void log(int channel, char *filename, int lineno, format, ...)。我需要附加通道名稱(目前在列表中有一個線性搜索!對於每個調試語句!)和時間戳(包括毫秒計數器)。這裏有一些事情Im做,使這個faster-

  • 字符串化的頻道名稱,所以我可以strcpy,而不是搜索列表。將宏LOG(channel, ...etc)定義爲log(#channel, ...etc)。您可以使用memcpy如果通過定義LOG(channel, ...)log("...."#channel - sizeof("...."#channel) + *11*)得到固定字節通道固定字符串的長度長度
  • 生成時刻字符串幾次第二。你可以使用asctime或其他東西。然後將memcpy固定長度的字符串寫入每個調試語句。
  • 如果你想要實時生成時間戳字符串,那麼帶有賦值的查找表(不是memcpy!)是完美的。但這隻適用於2位數字,也許是一年。
  • 三位數(毫秒)和五位數(lineno)怎麼樣?我不喜歡itoa和我不喜歡自定義itoa(digit = ((value /= value) % 10))要麼因爲divs和mods是。我編寫了下面的函數,後來發現在AMD優化手冊(彙編)中有類似的東西,這讓我相信這些是關於最快的C實現。

    void itoa03(char *string, unsigned int value) 
    { 
        *string++ = '0' + ((value = value * 2684355) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = ' ';/* null terminate here if thats what you need */ 
    } 
    

    同樣,對於行號,

    void itoa05(char *string, unsigned int value) 
    { 
        *string++ = ' '; 
        *string++ = '0' + ((value = value * 26844 + 12) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = ' ';/* null terminate here if thats what you need */ 
    } 
    

總體來說,我的代碼是相當快了。我需要使用的vsnprintf()需要大約91%的時間,而我的代碼的其餘部分只需要9%(而代碼的其餘部分,即除了以前使用的vsprintf()以前需要54%)