【问题标题】:C++ map performance - Linux (30 sec) vs Windows (30 mins) !C++ 地图性能 - Linux(30 秒)与 Windows(30 分钟)!
【发布时间】:2010-05-14 05:33:54
【问题描述】:

我需要处理文件列表。不应对同一文件重复处理操作。我为此使用的代码是 -

using namespace std;

vector<File*> gInputFileList; //Can contain duplicates, File has member sFilename
map<string, File*> gProcessedFileList; //Using map to avoid linear search costs

void processFile(File* pFile)
{
    File* pProcessedFile = gProcessedFileList[pFile->sFilename];
    if(pProcessedFile != NULL)
        return; //Already processed

    foo(pFile); //foo() is the action to do for each file
    gProcessedFileList[pFile->sFilename] = pFile;
}

void main()
{
    size_t n= gInputFileList.size(); //Using array syntax (iterator syntax also gives identical performance)
    for(size_t i=0; i<n; i++){
        processFile(gInputFileList[i]);
    }
}

代码可以正常工作,但是...

我的问题是,当输入大小为 1000 时,需要 30 分钟 - 半小时 - 在 Windows/Visual Studio 2008 Express 上。同样的输入,在Linux/gcc上运行只需40秒!

可能是什么问题?单独使用时,操作 foo() 只需很短的时间即可执行。我应该为地图使用 vector::reserve 之类的东西吗?

编辑,额外信息

foo() 的作用是: 1.它打开文件 2.读入内存 3.关闭文件 4.解析内存中文件的内容 5. 建立一个代币列表;我为此使用了一个向量。

每当我中断程序时(在运行程序时输入集超过 1000 个文件):调用堆栈显示程序处于 std::vector 添加的中间。

【问题讨论】:

  • 我怀疑问题不在于 std::map,而在于 File * 类。您是否尝试过定位性能下降的阈值? Windows 和 linux 对打开文件的数量有不同的限制。
  • 如果注释掉foo(),需要多长时间?
  • 要获取更多信息,请删除 foo 调用。这可能是 Linux/Windows 中 STL、编译器或文件处理方面的差异。
  • 问题肯定不在地图性能上!
  • 上例中没有std::vector add。

标签: c++ performance stl


【解决方案1】:

在 Microsoft Visual Studio 中,访问标准 C++ 库时有一个全局锁,以防止在调试版本中出现多线程问题。这可能会导致很大的性能损失。例如,我们的完整测试代码在 Linux/gcc 上运行需要 50 分钟,而在 Windows VC++2008 上则需要 5 小时。请注意,在发布模式下使用非调试 Visual C++ 运行时编译时不存在这种性能损失。

【讨论】:

  • 我想就是这样。我生成了一个新的 Release 版本并进行了尝试。性能提高到 240 秒...感谢分享经验。这个 Debug STL 锁定有解决方法吗?
  • 我还要补充一点,即使在发布模式下,VC 中的迭代器也会额外检查 GCC 没有执行。谷歌 _SECURE_SCL 了解更多信息。
【解决方案2】:

我会像处理任何性能问题一样处理它。这意味着:分析。顺便说一下,MSVC 有一个内置的分析器,因此可能是熟悉它的好机会。

【讨论】:

  • Express 版没有内置分析器,我不认为。你能推荐替代品吗?
  • @sonofdelphi:尝试在 Windows 上使用 mingw 编译相同的程序并使用 gprof
【解决方案3】:

在随机时间使用调试器闯入程序,堆栈跟踪很可能会告诉您它在哪里花费时间。

【讨论】:

  • 已在问题中更新了此内容。它位于向量添加的中间。
【解决方案4】:

我非常非常怀疑您的性能问题来自 STL 容器。

尝试消除(注释掉)对foo(pFile) 的调用或任何其他涉及文件系统的方法。虽然运行一次foo(pFile) 可能看起来很快,但在 1000 个不同的文件上运行它(根据我的经验,尤其是在 Windows 文件系统上)可能会慢得多(例如,因为文件系统缓存行为。)

编辑

您最初的帖子声称调试和发布版本都受到了影响。现在您要撤回该声明。

请注意,在 DEBUG 构建中:

  1. STL 实现执行 额外的检查和断言
  2. 堆 操作(内存分配等) 执行额外的检查和断言; 此外,在调试下构建 low-fragmentation heap 是 禁用(整体高达 10 倍 内存分配速度减慢)
  3. 不执行任何代码优化, 这可能会导致进一步的 STL 性能下降(STL 很多时候严重依赖内联、循环展开等)

在 1000 次迭代中,您可能不会受到上述影响(至少不会在外循环级别),除非您在 INSIDE foo() 中大量使用 STL/堆。

【讨论】:

  • 已经处理过的文件存入map中,所以这一步是正确的。
  • 不,gProcessedFileList[] 是已处理文件的缓存,以避免重复处理同一个文件。
  • 我会检查文件是否被关闭。
  • 1.在最初发布的时候,我在不知不觉中检查了调试版本的行为,认为它是发布版本。我对此感到抱歉。 2. foo() 也大量使用 STL...正如我在问题的更新版本中指出的那样。
【解决方案5】:

如果您看到的性能问题与 map 类有任何关系,我会感到震惊。进行 1000 次查找和 1000 次插入应该花费大约微秒的组合时间。 foo() 在做什么?

【讨论】:

    【解决方案6】:

    在不知道其余代码如何适应的情况下,我认为缓存已处理文件的整体想法有点不稳定。

    尝试从您的向量中删除重复项首先,然后将它们全部处理。

    【讨论】:

      【解决方案7】:

      尝试注释每个块或主要操作以确定哪个部分实际上导致了 Linux 和 Windows 中的执行时间差异。我也不认为这是因为 STL 地图。问题可能在 foo() 内部。它可能在某些文件操作中,因为这是我唯一能想到的,在这种情况下会很昂贵。

      您可以在操作之间插入 clock() 调用以了解执行时间。

      【讨论】:

        【解决方案8】:

        你说当你中断时,你会发现自己在 vector::add 中。您向我们展示的代码中没有 vector::add ,所以我怀疑它在 foo 函数中。如果没有看到该代码,将很难说出发生了什么。

        您可能无意中创建了Shlemiel the Painter 算法。

        【讨论】:

        • 我没有把 foo() 代码放在帖子里;只解释了它的作用。
        • “Shlemiel the Painter”只是(对我而言)已知为 O(n2) 的新名称。但实际上,n 平方的 big-oh 本身应该是一个复杂性类,就像 off-by-one 本身就是 bug 类一样,因为它很常见。
        【解决方案9】:

        如果你放弃你的地图并分割你的向量,你可以稍微改进一下。这意味着重新排序输入文件列表。这也意味着您必须找到一种方法来快速确定文件是否已经被处理,可能通过在File 类中持有一个标志。如果可以重新排序文件列表并且可以将该脏标志存储在File 对象中,那么对于 n 个总文件和 m 个已处理文件,您可以将性能从 O(n log m) 提高到 O(n)。

        #include <algorithm>
        #include <functional>
        // ...
        vector<File*>::iterator end(partition(inputfiles.begin(), inputfiles.end(),
                                              not1(mem_fun(&File::is_processed))));
        for_each(inputfiles.begin(), end, processFile);
        

        如果您无法重新排序文件列表或无法更改 File 对象,则可以使用 vector 切换 map 并在输入文件列表中使用标记隐藏每个文件同一索引处的第二个向量。这将花费你 O(n) 的空间,但会给你 O(1) 的脏状态检查。

        vector<File*> processed(inputfiles.size(), 0);
        
        for( vector<File*>::size_type i(0); i != inputfiles.size(); ++i ) {
            if( processed[i] != 0 ) return;  // O(1)
            // ...
            processed[i] = inputfiles[i];    // O(1)
        }
        

        但要小心:您正在处理两个指向同一地址的不同指针,这就是两个容器中的每对指针的情况。确保只有一个指针拥有指针对象。

        我不希望这两种方法都能为性能下降提供解决方案,但尽管如此。

        【讨论】:

          【解决方案10】:

          如果您在 linux 中完成大部分工作,那么我强烈建议您只在 windows 中编译为发布模式。这让生活变得更加轻松,尤其是考虑到所有 Windows 不灵活的库处理令人头疼的问题。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-05-15
            • 2017-10-26
            • 1970-01-01
            • 2018-07-26
            • 2014-05-25
            • 1970-01-01
            相关资源
            最近更新 更多