• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            FireEmissary

              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
              14 隨筆 :: 0 文章 :: 20 評論 :: 0 Trackbacks

            KMP 算法并非優化

            算法書和數據結構書對KMP算法多有介紹,稱只需對字符串掃描一遍不需回溯云云.然而,它恐怕只應該作為一種思想存在;用于實際的字符串查找并不理想.要費勁心血實現和優化它,才能在特定的字符串上略微超過(也可能略微遜過)std::search.

             

             

            KMP算法的基本思想,是利用需要匹配字符串的自身信息來避免回溯.(這里討論的算法是以C/C++為編程語言,因此下標索引以0開始) 

            例如:字符串PAT=abcabcde,里面第二段的abcPAT開頭的字符是匹配的.

            假如我們有個要查找的字符串TEXT=abcabD...,在比較到TEXT'D'(TEXT[5])也即到了PAT的第二個'c'(PAT[5])時比較失敗,一般的字符串查找又得從TEXT[1]('b')開始查找PAT.KMP算法知道PAT的第二段abc匹配自身的開頭字符串,它會對PAT存儲如下next:0 0 0 1 2 3 0 0數字代表匹配失敗后應該從PAT的第多少個位置開始比較.我們這里在PAT[5]處失敗,而前面的PAT[0...4]都比較成功,因此先看看PAT[5]的上一個成功位置的next信息:next[4]=2,這代表應該從PAT[2]處重新開始比較(即說明有2個字符不需要比了),就不需要跳到TEXT[1]處重新啟動查找,這時的PAT[0...1]為”ab,TEXT'D'的前面兩個也是”ab,只需要開始比較TEXT[5]PAT[2],KMP說的無回溯意義正是如此:不需要回溯TEXT的比較位置.如果PAT[2]又失敗了,next[1]=0.那么TEXT[5]PAT[0]開始比較,不成功的話TEXT遞增到TEXT[6],一直遞增到成功為止,成功的話當然PAT也開始遞增.

            偽碼如下:

             

            template<class I>

            I find(I beg,I end)

            {

            int patPos=0;

            while (beg!=end)

            {

            if (*beg==PatChar[patPos])

            {//匹配時遞增文本和模式的指針.

            ++beg;

            if (++patPos!=sizePat)

            continue;

            //如果模式指針遞增了模式的長度那么多次,說明比較成功,返回指針

            return beg-=failFunc.size(); 

            }


            else if (patPos)//根據next表信息調整模式串要比較的位置

            patPos
            =nextTable(--patPos);

            else

            ++beg;

             

            }


            return end;

            }


             

            我用微軟的wingdi.h頭文件測試,方法是讀入后自加到1M以上,在里面找”IN HDC, IN UINT””PolyPolyline這樣的串若干次.

            實測性能非常低,既比不過std::string::find,也比不過std::search.

             

            于是我進行了第一次優化.像很多書本的作者正確指出的那樣,一般的程序員對該優化的地方常常估計錯誤:

            請看前面說的PAT[5]失敗的地方,它跳轉到PAT[2]開始比較,然而PAT[2]比較必定失敗,為什么?因為PAT[5]==PAT[2]='c',PAT[5]失敗當然PAT[2]也會失敗,我的優化移除此類情況,也即next:0 0 0 1 2 3 0 0優化后變為:0 0 0 0 0 3 0 0 .

            興沖沖的重新編譯運行換來的卻是些微的提升.顯然這不是影響性能的地方.

             

            分析良久后也沒找到應該改進的地方,因為std::search的代碼比較平凡.最后抱著試試看的心理,把代碼替換了:

             

            template<class I>

            I find(I beg,I end)

            { …

            beg
            =std::find(beg,end, PatChar[0]);

            int patPos=0;



            else if (patPos)

            patPos
            =nextTable(--patPos);

            else

            {

            beg
            =std::find(beg,end, PatChar[0]);

            patPos
            =0;

            }


            }


            return end;

            }

            帶來了巨大的速度提升,使得KMP查找的速度大大提升,gcc上比string.find,vs 2008std::search快了(,換句話說就是比gccstd::search,vs 2008string.find...)

             

            ,總算發現性能低下的真正原因了:在一大塊文本中找少量匹配的文本,最好是先濾掉不匹配的.對于不匹配的情況,std::find需要的指令更少;因此先用它來跳過不匹配的字符后在進入KMP比較算法的核心會帶來可觀的速度提升.

             

            那么接下來還有什么高級手段使得KMP算法大大改進嗎?我嘗試了很久.然而很遺憾,沒有什么再值得慶祝的優化了:它們增加了10倍的煩惱,換來一點點的性能提升.比方說:對于PAT=abcabcde,它的前置串”abcabnext值都為0,意味著可以先提前用簡單的代碼去比較”abcab,一旦比較失敗又繼續從失敗處開始找而不需要去查看next.因此我的實際代碼是用findFront_代替了std::find來干這事.這讓KMP算法提升了一點點性能,付出了n天思考和除錯的時間.

             

            最終的成型算法是這樣的結果:gcc自帶的string::find要快,偶爾(通過對比較文本串的大小和比較次數調整)快于gccstd::search;vs 2008std::search,vs 2008string::find秒殺....

             

            結論:

             1.簡單的代碼編譯器容易優化.也適合現代處理器的預測執行技術和cache.

            2.日常用的要查找的字符串不會有多少適合KMP查找:abcabcabcabcnext表是什么值?優化后應該全為0!我對著wingdi.h看了幾遍才找到了像”WINGDIAPI BOOL WINAPI,PATPAINT這些歪瓜劣棗,它們的next表優化后也只有一兩個地方有值.KMP算法應該適用在文本中大量字符串重復且要找的子串也自身重復的情況.

            3.去實現KMP查找不如用std::search.

             

            代碼下載

             

            花絮:

                gccstring::find很低效;因為它們就是用char_traiteq一個個去找,vs2008的是用memchr先找到一個再去比較整個串.也許unix世界習慣了用正則庫根本不怎么用標準庫自帶的.

              不過gccstring::find低效是相對自己的其它庫函數而言.它編譯的測試代碼普遍比vs2008的快上一倍以上.

               vs2008std::search比較低效;就是平凡地一個字符一個字符對比;gccstd::search先用std::find找到第一個相等的字符后再去比較整個串.(看這里和string::find情況反過來了)

               vs2008std::find很平凡,只是對char的情況特化成memchr;gccfind對隨機迭代器做了優化,循環展開成44個地比較.

             

            測試環境:xp sp3 

            Cpu:amd 5000超頻到2.7G 

            內存:ddr2 2G.

            posted on 2010-07-01 21:34 FireEmissary 閱讀(4011) 評論(2)  編輯 收藏 引用

            評論

            # re: KMP 算法并非字符串查找的優化[未登錄] 2010-07-03 08:52 Lucifer
            同意樓主的觀點。大體來說,

            1.少量字符串,標準庫的執行效率是最好的。
            2.中等規模字符串,KMP 算法比較有優勢。
            3.大量字符串,BM 及其變種算法比較有優勢。

            這個是我對字符串精確匹配算法應用的認識。不對之處,還請指教。

            PS: boost 庫中應該有這些算法的實現吧。  回復  更多評論
              

            # re: KMP 算法并非字符串查找的優化 2010-07-06 15:10 djx_zh
            glibc中用SSE4.2指令集優化的KMP算法,號稱最快可以比c的strstr函數加速10倍。  回復  更多評論
              

            国产成人久久777777| 日韩AV毛片精品久久久| 日韩精品久久久久久久电影蜜臀| 少妇高潮惨叫久久久久久| 久久99国产精品一区二区| 久久亚洲欧洲国产综合| 久久香综合精品久久伊人| 88久久精品无码一区二区毛片 | 亚洲国产精品婷婷久久| 一本综合久久国产二区| 久久综合久久久| 久久久久高潮综合影院| 精品久久综合1区2区3区激情| 午夜天堂精品久久久久| 久久久91人妻无码精品蜜桃HD| 精品久久久久久成人AV| 久久久午夜精品福利内容| 久久精品国产一区二区三区日韩| 久久人人爽人人爽人人片AV麻烦 | 国内精品久久久久久久久电影网 | 狠狠色丁香婷婷综合久久来| 亚洲性久久久影院| 国产精品久久久久aaaa| 久久人人爽人人爽人人片av高请| 中文精品久久久久人妻| 久久亚洲中文字幕精品一区| 色偷偷888欧美精品久久久| 精品久久久久久久无码| 久久精品中文无码资源站| 久久亚洲精品无码aⅴ大香 | 国产麻豆精品久久一二三| 亚洲中文久久精品无码| 狠狠色丁香婷婷久久综合五月 | 性欧美大战久久久久久久| 久久久久亚洲av成人无码电影| 99久久精品国产综合一区| 久久精品成人国产午夜| 日韩亚洲欧美久久久www综合网 | 国产精品综合久久第一页 | 亚洲AV日韩精品久久久久久| 久久久无码精品亚洲日韩京东传媒 |