簡體   English   中英

為什么矢量和地圖搜索比靜態比較慢得多

[英]Why are vector and map searches so much slower than static comparisons

我正在解析大小約1MB的文件,讀取前300KB並搜索一些特定的簽名。 我的策略是,對於每個字節,查看字節是否在map / vector /我知道可能在簽名開頭的任何字節中,如果是,則查找完整簽名 - 對於此示例,假設那些領先字節是x37,x50和x52。 處理總共90個文件(實際上9個文件10次),以下代碼在2.122秒內執行:

        byte * bp = &buffer[1];
        const byte * endp = buffer + bytesRead - 30; // a little buffer for optimization - no signature is that long
        //multimap<byte, vector<FileSignature> >::iterator lb, ub;
        map<byte, vector<FileSignature> >::iterator findItr;
        vector<FileSignature>::iterator intItr;
        while (++bp != endp)
        {
            if (*bp == 0x50 || *bp == 0x52 || *bp == 0x37)  // Comparison line
            {
                findItr = mapSigs.find(*bp);
                for (intItr = findItr->second.begin(); intItr != findItr->second.begin(); intItr++)
                {
                    bool bMatch = true;
                    for (UINT i = 1; i < intItr->mSignature.size(); ++i)
                    {
                        if (intItr->mSignature[i] != bp[i])
                        {
                            bMatch = false;
                            break;
                        }
                    }

                    if (bMatch)
                    {
                        CloseHandle(fileHandle);
                        return true; 
                    }
                }
            }
        }

但是,我的初始實現在一個緩慢的84秒內結束。 唯一的區別與上面標有“//比較行”的行有關:

findItr = mapSigs.find(*bp);
if (findItr != mapSigs.end())
...

使用包含3個值的向量的非常類似的實現也導致極慢的處理(190秒):

if (find(vecFirstChars.begin(), vecFirstChars.end(), *bp) != vecFirstChars.end())
{
    findItr = mapSigs.find(*bp);
    ...

但訪問向量元素的實現直接執行得很好(8.1秒)。 不如靜態比較好,但仍遠遠優於其他選項:

if (vecFirstChars[0] == *bp || vecFirstChars[1] == *bp || vecFirstChars[2] == *bp)
{
    findItr = mapSigs.find(*bp);
    ...

迄今為止最快的實現(受以下組件10的啟發)如下所示,大約在2.0秒內完成:

bool validSigs[256] = {0};
validSigs[0x37] = true;
validSigs[0x50] = true;
validSigs[0x52] = true;

while (++bp != endp)
{
    if (validSigs[*bp])
    {
    ...

擴展它以使用2個validSigs來查看第二個字符是否有效也將總運行時間降低到0.4秒。

我覺得其他實現應該會更好。 特別是地圖,它應該按照更多的簽名前綴進行擴展,並且搜索是O(log(n))vs O(n)。 我錯過了什么? 我唯一的黑暗猜測是靜態比較和(對較小的現存)矢量索引,我得到用於比較的值緩存在寄存器或其他位置,這使得它明顯快於讀取從記憶里。 如果這是真的,我是否能夠明確地告訴編譯器將經常使用特定值? 是否有任何其他優化我可以利用以下代碼不明顯?

我正在使用Visual Studio 2008進行編譯。

這很簡單,可以歸結為執行的指令數量。 向量,映射或查找表將完全駐留在CPU級別1數據高速緩存中,因此內存訪問不占用時間。 對於查找表,只要大多數字節與簽名前綴不匹配,分支預測器就會停止流控制占用時間。 (但其他結構確實會產生流量控制開銷。)

非常簡單,與矢量中的每個值進行比較依次需要3次比較。 該映射是O(log N),但由於導航鏈接的數據結構,系數(由big-O表示法忽略)很大。 查找表是O(1),系數很小,因為可以通過單個機器指令完成對結構的訪問,然后剩下的全部是與零的一次比較。

分析性能的最佳方法是使用分析器工具,例如valgrind / kcachegrind。

“與常數比較”將3個存儲器地址與3個常數進行比較。 如果編譯器感覺如此,這種情況將非常容易地執行諸如展開或執行位優化之類的操作。 書面ASM將在這里擁有的唯一分支將是高度可預測的。

對於文字3元素向量查找,需要解除引用向量值的地址的額外成本。

對於矢量循環,編譯器不知道此時矢量有多大。 所以它必須編寫一個通用循環。 這個循環中有一個分支,一個分支單向2次,然后是另一個方向。 如果計算機使用啟發式“分支按照上次的方式進行”,則會導致許多分支預測失敗。

要驗證該理論,請嘗試使分支更可預測 - 一次搜索最多100個不同輸入字節的每個元素,然后搜索下一個。 這將使天真的分支預測工作在98%的時間,而不是代碼中的33%。 即,為簽名0掃描100(或其他)字符,然后掃描簽名1的100(或其他)字符,直到簽名用完為止。 然后繼續下一個100個字符的塊來掃描簽名。 我之所以選擇100,是因為我試圖避免分支預測失敗,而且我認為百分之幾的分支預測失敗並不是那么糟糕。 :)

對於map解決方案,井map具有高的恆定開銷,因此它很慢是可預測的。 map的主要用途是處理大量的n次查找,以及它們非常容易編碼的事實。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM