C++性能优化-字符串的优化
字符串的優(yōu)化
糟糕的字符串連接函數(shù)
在C++中字符串是按照值的形式實現(xiàn)的,又因為C++中字符串底層是使用動態(tài)內存實現(xiàn)的,因此、在項目中對字符串的優(yōu)化必不可少,也是性能優(yōu)化的重點。
假如代碼中有如下remove_ctrl函數(shù)的實現(xiàn):
std::string remove_ctrl(std::string s) {std::string result;for (int i=0; i<s.length(); ++i) {if(s[i] >= 0x20)result = result + s[i];}return result; }remove_ctrl() 在循環(huán)中對通過參數(shù)接收到的字符串 s 的每個字符進行處理。循環(huán)中的代碼就是導致這個函數(shù)成為熱點的原因。 if 條件語句從字符串中得到一個字符,然后與一個字面常量進行比較。這里沒有什么問題。但是第 5 行的語句就不一樣了。
正如之前所指出的,字符串連接運算符的開銷是很大的。它會調用內存管理器去構建一個新的臨時字符串對象來保存連接后的字符串。如果傳遞給 remove_ctrl() 的參數(shù)是一個由大于0x2(可打印)的字符組成的字符串,那么 remove_ctrl() 幾乎會為 s 中的每個字符都構建一個臨時字符串對象。對于一個由 100 個字符組成的字符串而言,這會調用 100 次內存管理器來為臨時字符串分配內存,調用 100 次內存管理器來釋放內存
對上述代碼進行基準測試,結果如下:
------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------- bench_remove_ctrl_operator 12433 ns 12432 ns 54195如果你的代碼編輯器帶自動檢查,就會發(fā)現(xiàn)這樣的代碼連代碼編輯器都受不了,給你顯示一大堆波浪號。
使用復合賦值操作避免臨時字符串
注意觀察上面實現(xiàn)的第5行代碼result = result + s[i];,因為字符串是按照值處理的,等號右邊的值想加后會在挨個賦值到等號左邊,這樣如果參數(shù)字符串有n個字符,那么remove_ctrl會復制O(n[^2])個自負,所有這些內存分配和復制都會導致性能變差。
為了減少字符串的復制,可以使用復合操作符+=,使用復合操作符之后的代碼如下:
std::string remove_ctrl(std::string s) {std::string result;for (int i=0; i<s.length(); ++i) {if(s[i] >= 0x20)result += s[i];}return result; }改進之后測試結果:
------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------- bench_remove_ctrl_operator 12433 ns 12432 ns 54195 bench_remove_ctrl_operator_opt 12400 ns 12400 ns 56281可以看出測試結果中改進之后的代碼明顯比沒有改進之前快很多,而且這個時間會隨著傳入的字符串的長度增長而增加,傳入的字符串越長,沒有改進的代碼需要復制的就越多。
通過預留存儲空間來減少內存的重新分配
經(jīng)過上述優(yōu)化之后,result仍然存在在執(zhí)行的過程中,由于緩存區(qū)不夠需要重新分配的情況,導致頻繁分配內存,可以通過提前分配內存優(yōu)化該問題,結果如下:
std::string remove_ctrl_reserve(std::string s) {std::string result;result.reserve(s.length());for (int i=0; i< (int)s.length(); ++i) {if(s[i] >= 0x20)result += s[i];}return result; }運行結果:
------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------- bench_remove_ctrl_operator 12170 ns 12168 ns 56990 bench_remove_ctrl_operator_opt 12390 ns 12388 ns 57518 bench_remove_ctrl_reserve 767 ns 767 ns 908214消除對參數(shù)字符串的復制
上述方法中還是會對參數(shù)字符串進行一次復制,由于內存分配是非常昂貴的,因此哪怕是一次內存分配也值得從程序中去除,優(yōu)化之后參數(shù)按照引用傳遞,結果如下:
std::string remove_ctrl_ref_args(std::string &s) {std::string result;result.reserve(s.length());for (int i=0; i< (int)s.length(); ++i) {if(s[i] >= 0x20)result += s[i];}return result; }更改之后運行結果:
------------------------------------------------------------------------- Benchmark Time CPU Iterations ------------------------------------------------------------------------- bench_remove_ctrl_operator 12158 ns 12159 ns 56439 bench_remove_ctrl_operator_opt 12044 ns 12046 ns 57409 bench_remove_ctrl_reserve 762 ns 762 ns 921342 bench_remove_ctrl_ref_args 737 ns 738 ns 938882移除對返回值的復制
經(jīng)過上述的優(yōu)化之后,性能已經(jīng)有了很大的改善。但是仔細觀察代碼還是能發(fā)現(xiàn)存在不必要的內存申請的地方,那就是返回值,每次在處理好字符串之后,返回時還需要復制一次,這時也會申請內存。我們完全可以將字符串通過引用傳出去,改進之后的代碼如下:
void remove_ctrl_ref_result_it(std::string &result, std::string const &s) {result.clear();result.reserve(s.length());for (auto &it:s) {if (it >= 0x20)result += it;} }改進之后的運行結果如下:
-------------------------------------------------------------------------- Benchmark Time CPU Iterations -------------------------------------------------------------------------- bench_remove_ctrl_operator 11948 ns 11949 ns 55770 bench_remove_ctrl_operator_opt 12088 ns 12089 ns 58264 bench_remove_ctrl_reserve 767 ns 767 ns 899659 bench_remove_ctrl_ref_args 742 ns 742 ns 948853 bench_remove_ctrl_ref_args_it 758 ns 758 ns 922450 bench_remove_ctrl_ref_result_it 493 ns 493 ns 1382138可以看到,當返回值也通過引用返回時,函數(shù)調用的耗時一下子降低到了493納秒。
用字符串代替數(shù)組
當程序有嚴格的性能要求的時候,不是使用C++風格的字符串,而應該使用C風格的字符數(shù)組,使用字符數(shù)組需要程序員自己控制內存的申請和釋放,能夠最大程度提升性能,但是帶來的后果就是維護的成本增加
void remove_ctrl_cstrings(char* destp, char const* srcp, size_t size) {for (size_t i=0; i<size; ++i) {if (srcp[i] >= 0x20)*destp++ = srcp[i];}*destp = 0; }使用C風格的字符數(shù)組測試結果:
-------------------------------------------------------------------------- Benchmark Time CPU Iterations -------------------------------------------------------------------------- bench_remove_ctrl_operator 12103 ns 12103 ns 55630 bench_remove_ctrl_operator_opt 12175 ns 12175 ns 57324 bench_remove_ctrl_reserve 765 ns 765 ns 918301 bench_remove_ctrl_ref_args 746 ns 746 ns 938322 bench_remove_ctrl_ref_args_it 751 ns 751 ns 912842 bench_remove_ctrl_ref_result_it 497 ns 497 ns 1394954 bench_remove_ctrl_cstrings 270 ns 270 ns 2575663使用更好的算法
有沒有即好維護又有高性能的的方法,答案是有的,那就是使用更好的算法。
經(jīng)過對以上的改進的思考,我們發(fā)現(xiàn)只要想辦法能夠減少內存的申請和釋放,字符串也能有很高的性能,這就是新算法需要下手的地方。
因為、老的實現(xiàn)方式,是每個值都要對比之后,挨個添加到result中,這就導致頻繁的操作內存,新算法中使用append一段一段的取符合條件的字符串添加到result中,實際測試結果141納秒左右,已經(jīng)優(yōu)于C風格的字符串,這就要感謝算法帶來的buff加成。
void remove_ctrl_block_append(std::string &result, const std::string &s) {result.clear();result.reserve(s.length());for (size_t b=0,i=b; b < s.length(); b = i+1) {for (i=b; i<s.length(); ++i) {if (s[i] < 0x20) break;}result.append(s, b, i-b);} }運行結果:
-------------------------------------------------------------------------- Benchmark Time CPU Iterations -------------------------------------------------------------------------- bench_remove_ctrl_operator 12258 ns 12257 ns 54639 bench_remove_ctrl_operator_opt 12347 ns 12347 ns 56488 bench_remove_ctrl_reserve 765 ns 765 ns 896059 bench_remove_ctrl_ref_args 736 ns 736 ns 947049 bench_remove_ctrl_ref_args_it 746 ns 746 ns 926601 bench_remove_ctrl_ref_result_it 494 ns 494 ns 1394869 bench_remove_ctrl_cstrings 270 ns 270 ns 2562077 bench_remove_block_append 141 ns 141 ns 4941262當然,還可以使用剔除不符合要求字符的方式實現(xiàn):
void remove_ctrl_erase(std::string &s) {for (size_t i = 0; i < s.length();)if (s[i] < 0x20)s.erase(i,1);else ++i; }測試結果:
-------------------------------------------------------------------------- Benchmark Time CPU Iterations -------------------------------------------------------------------------- bench_remove_ctrl_operator 12146 ns 12146 ns 56901 bench_remove_ctrl_operator_opt 12254 ns 12254 ns 56758 bench_remove_ctrl_reserve 756 ns 756 ns 909901 bench_remove_ctrl_ref_args 735 ns 735 ns 938884 bench_remove_ctrl_ref_args_it 748 ns 748 ns 922788 bench_remove_ctrl_ref_result_it 494 ns 494 ns 1422658 bench_remove_ctrl_cstrings 275 ns 275 ns 2534856 bench_remove_block_append 143 ns 143 ns 4871896 bench_remove_erase 169 ns 169 ns 4104307當然、還可以使用更優(yōu)秀的庫,使用優(yōu)化性能更好的編譯器,或者為自己的業(yè)務專門定制一個字符串的類,來達到最優(yōu)。
總結
以上是生活随笔為你收集整理的C++性能优化-字符串的优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: const函数和const对象
- 下一篇: C++优化热点语句