我正在解析大约 1MB 大小的文件,读取前 300KB 并搜索一些特定的签名。我的策略是,对于每个字节,查看该字节是否在映射/向量/我知道可能位于签名开头的任何字节中,如果是,则查找完整签名-对于本示例,假设那些领先字节为 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 来查看第 2 个字符是否有效,从而使总运行时间降低到 0.4 秒。
我觉得其他实现应该表现更好。尤其是地图,它应该随着更多签名前缀的添加而缩放,并且搜索是 O(log(n)) 与 O(n)。我错过了什么?我唯一的猜测是,通过静态比较和(在较少的情况下)矢量索引,我将用于比较的值缓存在寄存器或其他位置,这使得它比读取快得多从记忆里。如果这是真的,我是否能够明确告诉编译器将经常使用特定值?对于以下不明显的代码,我是否可以利用其他优化?
我正在使用 Visual Studio 2008 进行编译。