成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

天下武功,唯快不破:提升字符串格式化效率的小技巧

開發
在嵌入式項目開發中,字符串格式化是很常見的操作,我們一般都會使用 C 庫中的 sprintf 系列函數來完成格式化。這篇文章就專門來聊一聊把數字格式化成字符串,可以有什么更好的方法。也許技術含量不高,但是很實用!

[[384892]]

一、前言

在嵌入式項目開發中,字符串格式化是很常見的操作,我們一般都會使用 C 庫中的 sprintf 系列函數來完成格式化。

從功能上來說,這是沒有問題的,但是在一些時間關鍵場合,字符串的格式化效率會對整個系統產生顯著的影響。

例如:在一個日志系統中,吞吐率是一個重要的性能指標。每個功能模塊都產生了大量的日志信息,日志系統需要把時間戳添加到每條日志的頭部,此時字符串的格式化效率就比較關鍵了。

天下武功,唯快不破!

這篇文章就專門來聊一聊把數字格式化成字符串,可以有什么更好的方法。也許技術含量不高,但是很實用!

二、最簡單的格式化

  1. #include <stdio.h> 
  2. #include <string.h> 
  3. #include <limits.h> 
  4. #include <sys/time.h> 
  5.  
  6. int main() 
  7.     char buff[32] = { 0 }; 
  8.     sprintf(buff, "%ld", LONG_MAX); 
  9.     printf("buff = %s \n", buff); 

其中,LONG_MAX 表示 long 型數值的最大值。代碼在眨眼功夫之間就執行結束了,但是如果是一百萬、一千萬次呢?

三、測試1:手動格式化數字

1. 獲取系統時間戳函數

我的測試環境是:在 Win10 中通過 VirtualBox,安裝了 Ubuntu16.04 虛擬機,使用系統自帶的 gcc 編譯器。

為了測試代碼執行的耗時,我們寫一個簡單的函數:獲取系統的時間戳,通過計算時間差值來看一下代碼的執行速度。

  1. // 獲取系統時間戳 
  2. long long getSysTimestamp() 
  3.     struct timeval tv;   
  4.     gettimeofday(&tv, 0); 
  5.     long long ts = (long long)tv.tv_sec * 1000000 + tv.tv_usec; 
  6.     return ts;  

2. 實現格式化數字的函數

  1. // buff: 格式化之后字符串存儲地址; 
  2. // value: 待格式化的數字 
  3. void Long2String(char *buff, long value) 
  4.     long tmp; 
  5.     char tmpBuf[32] = { 0 }; 
  6.     // p 指向臨時數組的最后一個位置 
  7.     char *p = &tmpBuf[sizeof(tmpBuf) - 1]; 
  8.      
  9.     while (value != 0) 
  10.     { 
  11.         tmp  = value / 10; 
  12.         // 把一個數字轉成 ASCII 碼,放到 p 指向的位置。 
  13.         // 然后 p 往前移動一個位置。 
  14.         *--p = (char)('0' + (value - tmp * 10)); 
  15.         value = tmp; 
  16.     } 
  17.  
  18.     // 把臨時數組中的每個字符,復制到 buff 中。 
  19.     while (*p) *buff++ = *p++; 
  20. }     

這個函數的過程很簡單,從數字的后面開始,把每一個數字轉成 ASCII 碼,放到一個臨時數組中(也是從后往前放),最后統一復制到形參指針 buff 指向的空間。

3. 測試代碼

  1. int main() 
  2.     printf("long size = %d, LONG_MAX = %ld\n", sizeof(long), LONG_MAX); 
  3.      
  4.     // 測試 1000 萬次 
  5.     int  total = 1000 * 10000; 
  6.     char buff1[32] = { 0 }; 
  7.     char buff2[32] = { 0 }; 
  8.  
  9.     // 測試 sprintf 
  10.     long long start1 = getSysTimestamp(); 
  11.     for (int i = 0; i < total; ++i) 
  12.         sprintf(buff1, "%ld", LONG_MAX); 
  13.     printf("sprintf    ellapse:  %lld us \n", getSysTimestamp() - start1); 
  14.  
  15.     // 測試 Long2String 
  16.     long long start2 = getSysTimestamp(); 
  17.     for (int i = 0; i < total; ++i) 
  18.         Long2String(buff2, LONG_MAX); 
  19.     printf("Long2String ellapse: %lld us \n", getSysTimestamp() - start2); 
  20.      
  21.     return 0; 

4. 執行結果對比

  1. long size = 4, LONG_MAX = 2147483647 
  2. sprintf    ellapse:  1675761 us  
  3. Long2String ellapse: 527728 us 

也就是說:把一個 long 型數字格式化成字符串:

  1. 使用 sprintf 庫函數,耗時 1675761 us;
  2. 使用自己寫的 Long2String 函數,耗時 527728 us;

大概是 3 倍左右的差距。當然,在你的電腦上可能會得到不同的結果,這與系統的負載等有關系,可以多測試幾次。

四、測試2:混合格式化字符串和數字

看起來使用自己寫的 Long2String 函數執行速度更快一些,但是它有一個弊端,就是只能格式化數字。

如果我們需要把字符串和數字一起格式化成一個字符串,應該如何處理?

如果使用 sprintf 庫函數,那非常方便:

  1. sprintf(buff, "%s%d""hello", 123456); 

如果繼續使用 Long2String 函數,那么就要分步來格式化,例如:

  1. // 拆成 2 個步驟 
  2. sprintf(buff, "%s""hello"); 
  3. Long2String(buff + strlen(buff), 123456); 

以上兩種方式都能達到目的,那執行效率如何呢?繼續測試:

  1. int main() 
  2.     printf("long size = %d, LONG_MAX = %ld\n", sizeof(long), LONG_MAX); 
  3.      
  4.     // 測試 1000 萬 次 
  5.     const char *prefix = "ZhangSan has money: "
  6.     int  total = 1000 * 10000; 
  7.     char buff1[32] = { 0 }; 
  8.     char buff2[32] = { 0 }; 
  9.  
  10.     // 測試 sprintf 
  11.     long long start1 = getSysTimestamp(); 
  12.     for (int i = 0; i < total; ++i) 
  13.         sprintf(buff1, "%s%ld", prefix, LONG_MAX); 
  14.     printf("sprintf     ellapse: %lld us \n", getSysTimestamp() - start1); 
  15.  
  16.     // 測試 Long2String 
  17.     long long start2 = getSysTimestamp(); 
  18.     for (int i = 0; i < total; ++i) 
  19.     { 
  20.         sprintf(buff2, "%s", prefix); 
  21.         Long2String(buff2 + strlen(prefix), LONG_MAX); 
  22.     } 
  23.     printf("Long2String ellapse: %lld us \n", getSysTimestamp() - start2); 
  24.      
  25.     return 0; 

執行結果對比:

  1. long size = 4, LONG_MAX = 2147483647 
  2. sprintf     ellapse: 2477686 us  
  3. Long2String ellapse: 816119 us 

執行速度仍然是 3 倍左右的差距。就是說,即使拆分成多個步驟來執行,使用 Long2String 函數也會更快一些!

五、sprintf 的實現機制

sprintf 函數家族中,存在著一系列的函數,其底層是通過可變參數來實現的。之前寫過一篇文章一個printf(結構體指針)引發的血案,其中的第四部分,使用圖片詳細描述了可變參數的實現原理,摘抄如下。

1. 可變參數的幾個宏定義

  1. typedef char *    va_list; 
  2.  
  3. #define va_start  _crt_va_start 
  4. #define va_arg    _crt_va_arg   
  5. #define va_end    _crt_va_end   
  6.  
  7. #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )   
  8. #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )   
  9. #define _crt_va_end(ap)      ( ap = (va_list)0 ) 

注意:va_list 就是一個 char* 型指針。

2. 可變參數的處理過程

我們以剛才的示例 my_printf_int 函數為例,重新貼一下:

  1. void my_printf_int(int num, ...) // step1 
  2.     int i, val; 
  3.     va_list arg; 
  4.     va_start(arg, num);         // step2 
  5.     for(i = 0; i < num; i++) 
  6.     { 
  7.         val = va_arg(arg, int); // step3 
  8.         printf("%d ", val); 
  9.     } 
  10.     va_end(arg);                // step4 
  11.     printf("\n"); 
  12.  
  13. int main() 
  14.     int a = 1, b = 2, c = 3; 
  15.     my_printf_int(3, a, b, c); 

Step1: 函數調用時

C語言中函數調用時,參數是從右到左、逐個壓入到棧中的,因此在進入 my_printf_int的函數體中時,棧中的布局如下:


Step2: 執行 va_start

  1. va_start(arg, num); 

把上面這語句,帶入下面這宏定義:

  1. #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) 

宏擴展之后得到:

  1. arg = (char *)num + sizeof(num); 

結合下面的圖來分析一下:首先通過 _ADDRESSOF 得到 num 的地址 0x01020300,然后強轉成 char* 類型,再然后加上 num 占據的字節數(4個字節),得到地址 0x01020304,最后把這個地址賦值給 arg,因此 arg 這個指針就指向了棧中數字 1 的那個地址,也就是第一個參數,如下圖所示: 

Step3: 執行 va_arg

  1. val = va_arg(arg, int); 

把上面這語句,帶入下面這宏定義:

  1. #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 

宏擴展之后得到:

  1. val = ( *(int *)((arg += _INTSIZEOF(int)) - _INTSIZEOF(int)) ) 

結合下面的圖來分析一下:先把 arg 自增 int 型數據的大小(4個字節),使得 arg = 0x01020308;然后再把這個地址(0x01020308)減去4個字節,得到的地址(0x01020304)里的這個值,強轉成 int 型,賦值給 val,如下圖所示:


簡單理解,其實也就是:得到當前 arg 指向的 int 數據,然后把 arg 指向位于高地址處的下一個參數位置。

va_arg 可以反復調用,直到獲取棧中所有的函數傳入的參數。

Step4: 執行 va_end

  1. va_end(arg); 

把上面這語句,帶入下面這宏定義:

  1. #define _crt_va_end(ap)      ( ap = (va_list)0 ) 

宏擴展之后得到:

  1. arg = (char *)0; 

這就好理解了,直接把指針 arg 設置為空。因為棧中的所有動態參數被提取后,arg 的值為 0x01020310(最后一個參數的上一個地址),如果不設置為 NULL 的話,下面使用的話就得到未知的結果,為了防止誤操作,需要設置為NULL。

六、總結

這篇文章描述的格式化方法靈活性不太好,也許存在一定的局限性。但是在一些關鍵場景下,能明顯提高執行效率。

 

責任編輯:姜華 來源: IOT物聯網小鎮
相關推薦

2018-06-19 16:48:42

華為

2020-06-22 13:43:46

代碼編碼語言

2021-02-23 10:15:31

軟件開發IT領導者首席信息官

2019-09-09 08:40:44

2024-12-09 08:10:00

Python字符串格式化

2021-01-26 09:19:58

Redis框架架構

2020-01-16 16:20:55

網絡安全數據技術

2016-08-01 10:38:14

華為

2018-04-13 10:36:44

Web應用優化

2021-06-09 07:55:18

Python格式化字符串

2014-03-20 16:18:30

碼農工作效率

2022-02-21 09:35:36

機器學習自然語言模型

2013-06-18 10:52:12

大數據

2020-02-21 16:20:37

系統驅動項目管理

2022-05-09 14:04:27

Python字符串格式化輸出

2023-11-06 09:32:52

Java實踐

2009-09-02 15:56:49

C#格式化字符串

2014-12-04 15:19:51

程序員

2014-12-04 17:30:08

編程

2012-12-24 09:57:58

ERPDynamics AX
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 在线中文字幕亚洲 | 欧美性网站 | 国产精品欧美一区喷水 | 日韩欧美在线一区 | 人人艹人人爽 | 久久精品青青大伊人av | 国产精品久久久久aaaa樱花 | 久久久久国产一区二区三区四区 | 91n成人| 好好的日在线视频 | 中文字幕在线三区 | 国产一区二区三区四区三区四 | 国产精品美女久久久久久久久久久 | 日韩av成人在线 | 亚洲精品久久久久久久久久久 | 日韩欧美三级电影在线观看 | 一区二区三区在线播放视频 | 毛片区| 超碰在线免费公开 | 一级毛片高清 | 紧缚调教一区二区三区视频 | 午夜免费在线电影 | 日韩欧美一二三区 | 久久久久国产一区二区三区四区 | 国产性生活一级片 | 亚洲精品乱码久久久久久久久 | 国产精品69毛片高清亚洲 | 秋霞国产 | a免费观看| 成人免费区一区二区三区 | 亚洲一av | 亚洲精品国产a久久久久久 午夜影院网站 | 国产精品大全 | 中文在线一区 | 亚洲成人自拍 | 性一交一乱一透一a级 | 日韩毛片| 国产精品久久777777 | 欧美福利一区 | 91在线网 | 欧美日韩在线一区二区三区 |