拯救你的字串

來源:互聯網
上載者:User

 

你還在為string s = str1 + "123" + str2 + str3;這樣的鏈式運算代碼產生的臨時對象,和二次級複雜度(最少的複雜度為str1 長度乘以"+"的個數)而苦惱嗎?

 

從今天開始起,徹底遠離這些問題吧。

 

這招我原本以為是自己發明的,還給他取了個名字叫做"操作符代理"(英文翻譯出來居然有關鍵字,歉),結果世間達人早有類似的思想, 叫做運算式模板(expression-template,我叫他et),(字型的區別其實是正房太太和偏房太太的區別,外資和民企的區別),嘿。但是我覺得我遠遠的擴充了它已有的一些概念,從而覺得我自己完全有資格給這項技術取個新名字。

 

大家可以先看看My Code,代碼中2次測試了字串8次連加,單個字串ascii長度在10-30(這樣的長度很常見吧,url, sql, xml操作中最最常見的長度區間)。連續執行10000000次。對於2次測試的區別,請注意115行和123行的區別(看到115行那個seed"種子"了吧,呵呵)。*strb.begin() = '0'是用來防止某些stl實現使用引用計數和copy on write減少了拷貝次數(這樣的技術天生就是為了讓你在雙核多線程程式中困惑的)。

 

#include <string><br />#include <time.h><br />#include <stdio.h><br />using namespace std;<br />#ifdef _MSC_VER<br />#if _MSC_VER <= 1200<br />#ifndef __INTEL_COMPILER<br />#define _MSVC_6<br />#pragma warning( disable : 4786)<br />#endif<br />#endif<br />#endif<br />template <class D ><br />struct et_seed<br />{<br /> typedef D dest_type;<br />};<br />namespace _et_private<br />{<br />template <bool N><br />struct mpl_bool<br />{<br /> size_t sizer[size_t(N)+10];<br />};<br />template <typename T><br />mpl_bool<true> get_result(T*, typename T::dest_type*);<br />mpl_bool<false> get_result(...);<br />template <typename T><br />struct check<br />{<br /> enum {result = (sizeof(get_result((T*)(NULL),NULL)) == sizeof(mpl_bool<true>)) };<br />};<br />template <class D, class L, class R ><br />struct binary_op<br />{<br /> typedef D dest_type;<br /> typedef L left_type;<br /> typedef R right_type;<br /> inline binary_op (const left_type& lhs, const right_type& rhs):<br /> ldata(lhs), rdata(rhs){<br /> }<br /> const left_type& ldata;<br /> const right_type& rdata;<br />#ifndef _MSVC_6<br /> template <typename T><br /> inline static void op (const T& arg, dest_type& result){<br /> result += arg;<br /> }</p><p> template <typename T, typename M><br /> inline static void op (const binary_op<dest_type, T, M>& arg, dest_type& result ){<br /> binary_op<dest_type, T, M>::op(arg.ldata, result);<br /> binary_op<dest_type, T, M>::op(arg.rdata, result);<br /> }<br /> inline static void op (const et_seed<dest_type>& /*arg*/, dest_type& /*result*/ ){}<br /> inline operator dest_type() const{<br /> dest_type result;<br /> binary_op<dest_type, left_type, right_type>::op(ldata, result );<br /> binary_op<dest_type, left_type, right_type>::op(rdata, result );<br /> return result;<br /> }<br />#else //vc6 cannot choose the correct template function, so the 3rd argument is used to help it!<br /> template <typename T><br /> inline static void op (const T& arg, dest_type& result, mpl_bool<false>* /*dummy*/ ){<br /> result += arg;<br /> }</p><p> template <typename T, typename M><br /> inline static void op (const binary_op<dest_type, T, M>& arg, dest_type& result, mpl_bool<true>* /*dummy*/ ){<br /> binary_op<dest_type, T, M>::op(arg.ldata, result, (mpl_bool<check<T>::result>*)NULL);<br /> binary_op<dest_type, T, M>::op(arg.rdata, result, (mpl_bool<check<M>::result>*)NULL);<br /> }<br /> inline static void op (const et_seed<dest_type>& /*arg*/, dest_type& /*result*/, void* /*dummy*/){}<br /> inline operator dest_type () const{<br /> dest_type result;<br /> binary_op<dest_type, left_type, right_type>::op(ldata, result, (mpl_bool<check<left_type>::result>*)NULL );<br /> binary_op<dest_type, left_type, right_type>::op(rdata, result, (mpl_bool<check<right_type>::result>*)NULL );<br /> return result;<br /> }<br />#endif<br />};<br />}<br />template <typename D, typename C><br />inline const _et_private::binary_op<D, et_seed<D>, C ><br /> operator + (const et_seed<D>& lhs, const C& rhs){<br /> return _et_private::binary_op<D, et_seed<D>, C > (lhs, rhs);<br />}<br />template <typename D, typename C><br />inline const _et_private::binary_op<D, C, et_seed<D> ><br /> operator + (const C& lhs, const et_seed<D>& rhs){<br /> return _et_private::binary_op<D, C, et_seed<D> > (lhs, rhs);<br />}<br />template <typename D, typename L, typename R, typename C><br />inline const _et_private::binary_op<D, _et_private::binary_op<D, L, R>, C ><br /> operator + (const _et_private::binary_op<D, L, R>& lhs, const C& rhs){<br /> return _et_private::binary_op<D, _et_private::binary_op<D, L, R>, C > (lhs, rhs);<br />}<br />template <typename D, typename L, typename R, typename C><br />inline const _et_private::binary_op<D, C, _et_private::binary_op<D, L, R> ><br /> operator + (const C& lhs, const _et_private::binary_op<D, L, R>& rhs){<br /> return _et_private::binary_op<D, C, _et_private::binary_op<D, L, R> > (lhs, rhs);<br />}<br />typedef et_seed<std::string> string_helper;<br />void test()<br />{<br /> et_seed<std::string> seed ;<br /> string str1 = "1234567890";<br /> string str2 = "12345678901234567890";<br /> string str3 = "123456789012345678901234567890";<br /> string strb;<br /> int current = time(NULL);<br /> int i = 0;<br /> for(i = 0 ; i < 10000000; ++i)<br /> {<br /> strb = seed + str1 + str2 + string("3 ") + str1 + "12345678901234567890" + str3 + ((seed + str1 + str2) + ' ') ;<br /> *strb.begin() = '0';<br /> }<br /> printf("%d/n", time(NULL) - current);<br /> string().swap(strb);<br /> current = time(NULL);<br /> for(i = 0 ; i < 10000000; ++i)<br /> {<br /> strb = str1 + str2 + string("3 ") + str1 + "12345678901234567890" + str3 + ((str1 + str2) + ' ' ) ;<br /> *strb.begin() = '0';<br /> }<br /> printf("%d/n", time(NULL) - current);<br />}<br />int main(int , char** )<br />{<br /> test();<br /> return 0;<br />}

 

release編譯後運行測試。具體的時間顯然和各自機器效能有關,直接比較沒有意義,有的伺服器太快我甚至把測試次數加了2個0。就說說比例吧:

 

個人電腦,amd雙核xp系統下關閉一切其他軟體,

1)vc6編譯器 + vc6stl,“有種”版本拷貝用的時間大約為“沒種”版本用時的56%,

2)intel編譯器(v10.1.034)+ vc6stl結果大致與1)相同,不到1%的波動範圍;

3)vc6編譯器 + stlport4.6.2,“有種”版本拷貝用的時間大約為“沒種”版本用時的75%,這個版本用iostream來輸出編譯器會崩潰,呵呵

4)intel編譯器(v10.1.034)+ stlport4.6.2,“有種”版本拷貝用的時間大約為“沒種”版本用時的60%。

5)vc2008編譯器 + vc2008stl,“有種”版本拷貝用的時間大約為“沒種”版本用時的70%

 

公用伺服器,由於有別人在使用,資料偶爾有波動,不過我看top幾乎也就我的程式一直佔著99%的cpu,呵呵

5)unix-center的 ubuntu伺服器雙核單線程(ubuntu.unix-center.net), gcc 4.2.4,“有種”版本拷貝用的時間大約為“沒種”版本用時的89-92%。

 

6)unix-center的 solaris伺服器八核四線程(t1000.unix-center.net), gcc 4.0.3,“有種”版本拷貝用的時間大約為“沒種”版本用時的80-85%。

7)unix-center的 aix伺服器2芯雙核單線程(aix.unix-center.net), gcc 4.2.0,居然連結說找不到string的建構函式,go,google說這個版本aix的gcc確實有這問題,我也沒心思去給它再編譯一個來玩了。

 

結論:

大家也看到了,“有種”和"沒種"之間效能提升還是不小的,windows平台尤其明顯。gcc似乎比較生猛,效能提升不像win那麼bt,我估計是匿名傳回值最佳化作的比較好吧。另外有一點打擊我的是intel編譯器居然並不比vc6快多少,虧我逢人就推薦它呢。總之如果你存在效能最佳化的需求並且c++代碼中有如上所述的自訂2元運算子鏈式操作,上面的技術還是很值得你考慮的,幾乎不費一槍一彈.(不!花了一"彈",呵呵)

 

用法:

首先確定了操作結果類型後,先定義一個種子.如106行 et_seed<std::string> seed;建立一個string類型的種子.

 

然後把種子放到運算鏈的左端, 可以放在第一位,如本代碼115行;也可以放第二位. (你熟悉std::string和const char*的混合連加的約束話,就明白這裡為什麼不能放在第三位了). 用操作符把它和已有的運算鏈合并.這裡是"+". 一般放在第一位比較好看也比較好改, 推薦.

 

最後如果有改變運算順序的括弧,並且該括弧內沒有子括弧,把括弧內部的運算式看作一個獨立的運算鏈重複上一步.

如本代碼115行,string("3 ")和改變運算順序無關,不理睬,而下一個括弧顯然改變了運算順序,但是裡面有子括弧,我們只需要在子括弧那裡再放一次種子.

總之找最""的括弧放種子.注意種子放多了也沒事,0損失,只要你喜歡種菜.挖最深的土去種菜和廣撒種幾乎沒有多大區別,不是嗎?

 

好了,大功告成!10%-40%的效率提升等著你.

 

事實上,絕大多數操作僅需要建立一個種子然後放在最左端就可以了,只要滿足結合律的自訂運算子我們其實都可以把括弧去掉(即滿足a+b+c == a+(b+c)),上面的代碼是為了說明.另外種子是可以重用的,有興趣你甚至可以讓他singleton.  臨時(匿名,右值,叫法很多)種子也是可以用的, 115行可以把seed換成et_seed<std::string>(),看你興趣了.說了這麼久了你也應該注意到了et_seed是一個空類,建立幾乎是無開銷的(取決於你的編譯器) .所以種子種多了也沒有什麼損失.

 

 

不管怎麼說,這一招還是很有原創性和實用價值的:

1。非侵入性,只需要在使用的時候"+"一個“種子”。幾乎不可能和已有的代碼產生衝突。

2。極佳的易用性,頂多隻需要修改代碼10來個字母左右。

3。極佳的可移植性,筆者在不影響效率的前提下儘可能的少的使用一些c++的進階特性,小心的讓代碼可以在vc6這些古董編譯器中編譯通過。

4。極廣的適用範圍,和原有的操作符相比,僅滿足了下列要求的類型就可以使用,這裡用"+"操作和std::string類型來描繪一下而已,大家可以把+批量替換成其他2元運算子來使用,如*,/,-等(c++的泛型不能泛操作符,囧)。

要求分為文法要求語義要求2類:

<1>有無參建構函式(自訂或預設皆可)。只要不是引用類(如std::auto_ptr_ref),都應該有吧。文法要求。

 

<2>支援"+"操作。廢話還是要說的。文法要求

 

<3>"+"操作傳回型別固定為原類型。見過string + string == string  && string + const char* == const char* 這種情況嗎?如果你見到這樣的類,還是趕緊把它扔掉吧,別讓它汙染你的代碼。語義要求。


<4>支援和"+"操作相同類型的"+="操作(非必須)。如string的 "+"操作支援string,const char*,char等操作類型,string的"+="操作也通過重載支援了這些類型。如果你的"+="少支援了某些類型,不到用時不抱錯語義要求。

 

<5>"+="操作和"+"操作的參數都是傳常量參考型別或者傳值的如string operator+ (const string& lhs, char rhs);第一個傳常量引用,第2個傳值。string& operator+= ( char c );同上理。如果你用某個類時遇到a=b+c之後,b或c的值發生了變化的情況,推薦你還是珍愛生命,遠離不良類庫吧。文法要求。


<6>a = a + b和a += b等價, a 等價於 string() + a, a + b + c等價於a + (b + c) 。語義要求。非常重要!!

 

注1:

如果違反上面這些要求中的某一條,不滿足文法要求將會編譯時間抱錯,不滿足語義要求可能編譯時間報錯,也有可能編譯時間不報錯而可以運行,運行時又分高效運行和原效率運行2種情況,具體情況非常複雜,大家可以留言討論。違反了要求<6>將會導致錯誤的運行結果!模板的靜態檢查能力畢竟有限,我能設的文法樁有限,何況我還要相容古董編譯器,太複雜了編譯器直接罷工...

注2:

除非違反了要求<6>,不存在編譯時間不抱錯而執行階段錯誤的情況。運行時發生錯誤請首先檢查操作類型是否滿足<6>.

注3:

還有一些要求,滿足上述要求的同時也就自動滿足了,如可同類型拷貝構造等("+"操作傳回值所要求的)。不再贅述.反而想提醒大家同類型拷貝建構函式有explicit關鍵字修飾嗎?

 

經典的c++庫裡面的"+"操作符重載都滿足上述要求,如stl, ace, qt等等(boost太博大精深了,筆者不敢妄言,呵呵)。你寫的庫呢,夠"種"嗎?

 

簡單描述原理:種子會讓"+"運算子的結果為一個2叉樹,節點為"+"左右運算對象的引用;而2叉樹和2叉樹,2叉樹和原始的運算對象"+"的結果都是一棵新的二叉樹.從而最終二元鏈式"+"運算結果為一棵二叉樹, 其中葉子節點為原始運算對象和種子,種子本質是用來第一步作為葉子節點協助原始的運算物件建構2叉樹,避開原有的二元"+"運算. 樹最終將附值(或拷貝)給目標類型,此時樹利用無參建構函式先產生一個目標類型對象R,然後從頭後序遍曆整棵樹,遇到子樹繼續後序遍曆,遇到原始的運算對象時利用它對產生的目標類型對象R作+=操作,遇到種子節點不做任何操作.此過程中利用+=返回引用不需要傳回值的優點,一次性的構造出了結果對象R.最後利用產生的結果對象R,附值(或拷貝).

 

注意這裡的二叉樹包含2個引用,建立開銷很小,二叉樹建立的數量和+的數量相同.

 

關鍵字:二叉樹,+=操作.

(最新版本請閱讀):

http://blog.csdn.net/zy498420/archive/2010/11/10/6000525.aspx

可能有的朋友看My Code有些吃力,我在下一篇會和大家解釋其中的一些奧妙,我的一些心得,我走的一些彎路,我的一些建議.其實c++庫的設計就是這樣的,寫庫的人累死累活,用庫的人方便的欲仙欲死.筆者這裡至少寫了5000行代碼才有了現有這個種子的思路的.並不是每個人都需要看懂stl的,會用就行!!

 

鄙人對代碼著作權毫無任何要求,但是請求拷貝走代碼的朋友請留言,方便筆者統計.如果有更好建議,還請不吝賜教.

 

補充一句:這個東西取名叫"種子",是為了紀念一門幾乎不為人知的民族英烈

種世衡,種諤,种師道,种師中

世代英烈,先後對抗西夏,遼,金,論貢獻比楊家將大多了,很可惜現在的人除非讀水滸時注意到“老種經略相公”和“小種經略相公"並且願意深入探尋者方知一二.

水滸裡面的好漢人人幾乎都以在大小種經略相公手下做過事為榮耀,王進、魯智深、楊志等都在老種經略相公手下任過職,金錢豹子湯隆在老種經略相公手下打造過兵器. 大小種經略相公是誰,希望大家還是踴躍去搜搜,這樣的英雄值得記憶!

 

遠離教科書,"禮失求諸於野",獨立思考,立劍為脊!

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.