【问题标题】:C++ string memory managementC++ 字符串内存管理
【发布时间】:2010-10-11 03:18:15
【问题描述】:

上周,我用 C# 编写了几行代码,将一个大型文本文件(300,000 行)写入字典。写了十分钟,不到一秒就执行了。

现在我正在将该段代码转换为 C++(因为我需要在旧的 C++ COM 对象中使用它)。到目前为止,我已经花了两天时间。 :-( 虽然生产力差异本身就令人震惊,但我需要一些建议。

加载需要 7 秒,甚至更糟:之后释放所有 CStringW 需要的时间正好是这么多。这是不可接受的,我必须想办法提高性能。

有没有机会我可以分配这么多的字符串而不会看到这种可怕的性能下降?

我现在的猜测是,我必须将所有文本填充到一个大数组中,然后让我的哈希表指向该数组中每个字符串的开头并删除 CStringW 内容。

但在此之前,各位 C++ 专家有什么建议吗?

编辑:我对自己的回答如下。我意识到这对我来说是最快的路线,并且也踏上了认为正确的方向 - 朝着更多托管代码。

【问题讨论】:

  • 您确实需要向我们展示您现在正在使用的代码,您的描述太模糊,无法提供输入。
  • 您肯定需要提供有关您的数据结构的更多信息...
  • 在我熟悉 .Net 之前,从 C++ 过渡到 C#——不知道框架(在我的例子中是 VCL)是如何相似或不同的——与其他方式一样具有挑战性大约。没有必要责怪 C++ 来问你的问题。
  • 在c#中你说你使用字典,你在C++代码中使用的是什么集合?
  • 另外,您使用的是哪个编译器?如果是 VS 6.0 请考虑更改它,那个编译器很糟糕。

标签: c++ mfc memory-management


【解决方案1】:

你正在穿上 Raymond Chen 的鞋子。他做了同样的事情,用非托管 C++ 编写了一本中文字典。 Rico Mariani 也这样做了,它是用 C# 编写的。 Mariani 先生制作了一个版本。陈先生写了6个版本,试图匹配Mariani版本的性能。为了实现这一目标,他几乎重写了 C/C++ 运行时库的重要部分。

在那之后,托管代码得到了更多的尊重。 GC 分配器是不可能被击败的。检查this blog 帖子中的链接。这个blog post 你可能也会感兴趣,它有助于了解 STL 值语义是如何成为问题的一部分。

【讨论】:

  • 好吧,引用 Mariani 的话“所以,是的,你绝对可以击败 CLR。​​”但我同意 CLR 的性能令人印象深刻。
【解决方案2】:

难怪 CLR 的内存管理比 MFC 所基于的一堆陈旧而肮脏的技巧更好:它至少比 MFC 本身年轻两倍,而且它是基于池的。当我不得不使用字符串数组和 WinAPI/MFC 处理类似的项目时,我只是使用了用 WinAPI 的 TCHAR 实例化的 std::basic_string 和我自己的基于 Loki::SmallObjAllocator 的分配器。在这种情况下,您还可以查看 boost::pool(如果您希望它具有“标准感觉”或必须使用 7.1 之前的 VC++ 编译器版本)。

【讨论】:

    【解决方案3】:

    问题不在于 CString,而在于您分配了很多小对象 - 默认内存分配器并未针对此进行优化。

    编写您自己的分配器 - 分配一大块内存,然后在分配时只在其中推进一个指针。这实际上是 .NET 分配器所做的。准备好后删除整个缓冲区。

    我认为有在(更多)有效 C++ 中编写自定义新/删除运算符的示例

    【讨论】:

    • 虽然我从未尝试过使用它,但标准 C++ 库的容器接受分配器作为参数,以便调整/优化项目创建。如果需要。
    【解决方案4】:

    感谢你们所有人的深刻见解。为你点赞! :-)

    我必须承认我根本没有为此做好准备 - C# 会以这种方式击败优秀的旧 C++ 中的废话。请不要认为这是对 C++ 的冒犯,而是 .NET Framework 中的一个非常出色的内存管理器。

    我决定退后一步,转而在 InterOp 竞技场中进行这场战斗!也就是说,我将保留我的 C# 代码,让我的旧 C++ 代码通过 COM 接口与 C# 代码对话。

    有人问了很多关于我的代码的问题,我将尝试回答其中的一些问题:

    • 编译器是 Visual Studio 2008,不,我没有运行调试版本。

    • 该文件是使用 UTF8 文件阅读器读取的,我从一名 Microsoft 员工那里下载了该文件,该员工在他们的网站上发布了该文件。它返回了 CStringW,大约 30% 的时间实际上都花在了读取文件上。

    • 我存储字符串的容器只是一个固定大小的指向 CStringW 的指针向量,它从未调整大小。

    编辑:我确信给我的建议确实有效,而且如果我在 C# 代码上投入足够的时间,我可能会击败 C# 代码。另一方面,这样做根本不会为客户提供任何价值,而坚持下去的唯一理由就是证明它可以做到……

    【讨论】:

      【解决方案5】:

      将字符串加载到单个缓冲区,解析文本以用字符串终止符 ('\0') 替换换行符,然后使用指向该缓冲区的指针添加到集合中。

      或者 - 例如如果您必须在加载期间进行 ANSI/UNICODE 转换 - 使用块分配器,这会牺牲删除单个元素。

      class ChunkAlloc
      {
         std::vector<BYTE> m_data;
         size_t m_fill;
         public:
           ChunkAlloc(size_t chunkSize) : m_data(size), m_fill(0) {}
           void * Alloc(size_t size)
           {
             if (m_data.size() - m_fill < size)
             {
                // normally, you'd reserve a new chunk here
                return 0;
             }
             void * result = &(m_data[m_fill]);
             m_fill += size;
             return m_fill;
           }
      }
      // all allocations from chuunk are freed when chung is destroyed.
      

      不会在 10 分钟内破解,但 30 分钟和一些测试听起来不错:)

      【讨论】:

      • 是的,是的。这也是我半夜想出来的! :-)
      • :) 请参阅 Raymond vs Rico (blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx) 与您的项目非常相似,结果很明确:使用 C++,您可以比 C# 应用程序甚至加载更快地解决问题,但它会花费您。很多。
      【解决方案6】:

      如果它是只读字典,那么以下内容应该适合您。

      Use fseek/ftell functionality, to find the size of the text file.
      
      Allocate a chunk of memory of that size + 1 to hold it.
      
      fread the entire text file, into your memory chunk.
      
      Iterate though the chunk.
      
          push_back into a vector<const char *> the starting address of each line.
      
          search for the line terminator using strchr.
      
          when you find it, deposit a NUL, which turns it into a string.
          the next character is the start of the next line
      
      until you do not find a line terminator.
      
      Insert a final NUL character.
      

      您现在可以使用向量来获取指针,这将让您 访问对应的值。

      当你用完字典后,释放内存,让向量 超出范围时死亡。

      [编辑] 这在 dos 平台上可能会稍微复杂一些,因为行终止符是 CRLF。

      在这种情况下,使用 strstr 来查找它,并增加 2 以查找下一行的开头。

      【讨论】:

      • 这会很好用而且速度很快。不过我认为没有必要 - 他在代码中做了一些坏事,可能可以修复并且具有可读和简单的代码。
      • 扼杀它的是内存分配/释放的构造/销毁成本。
      • 或者(如果 Win32 支持等效)mmap 文件 MAP_PRIVATE(写入时复制),这样可以节省复制它。
      • 当然是一个选项。我不知道额外的间接费用是多少。
      • 我可能会做这样的事情!但我事先不知道大小——我想同时读取 ASCII、UTF8 和 Unicode 文件。所以我想我会像你说的那样做,但我会用 32Kb 的块来做。这样我最终会分配 100 个内存而不是 330000。
      【解决方案7】:

      这听起来很像 Raymond Chen 与 Rico Mariani 的 C++ 与 C# 中英词典的表现。 Raymond 经过多次迭代才击败 C#。

      也许那里有一些有用的想法。

      http://blogs.msdn.com/ricom/archive/2005/05/10/performance-quiz-6-chinese-english-dictionary-reader.aspx

      【讨论】:

      • 是的,你不知道你有多正确! :-) 但对我来说,另外一件棘手的事情是我想一次性读取 ASCII、UTF8 和 Unicode,所以我必须将文件数据转换为我存储的数据。
      • @danbystrom,ASCII 可视为 UTF-8 的子集。 UTF-8 是一种 Unicode 编码格式。如果您的文件采用 UTF-8 格式,则可以使用 Unicode 中定义的任何字符,并且现有的 ASCII 文本不需要重新编码。
      【解决方案8】:

      哎呀。摆脱 CStrings...

      也可以试试探查器。 你确定你不只是在运行调试代码吗?

      使用 std::string 代替。

      编辑:

      我只是做了一个简单的 ctor 和 dtor 比较测试。

      CStringW 似乎需要 2 到 3 倍的时间来执行新/删除操作。

      为每种类型迭代 1000000 次新/删除。没有别的了——在每个循环之前和之后都有一个 GetTickCount() 调用。 CStringW 始终获得两倍的时间。

      尽管我怀疑这并不能解决您的全部问题。

      编辑: 我也不认为使用 string 或 CStringW 是真正的问题 - 还有其他事情正在导致您的问题。

      (但看在上帝的份上,无论如何都要使用 stl!)

      您需要对其进行概要分析。那是一场灾难。

      【讨论】:

      • 我会试试 std::string,敬请期待! :-)
      • CString 不好。首先,正如我刚刚确认的,它比 std::string 慢。它不是便携式的。这是一个 MFC hack - 以及所有其他 MFC 垃圾。不要误会我的意思,我已经使用它们很多年了,但是我们使用的 MFC 越少越好……
      【解决方案9】:

      在使用字符串类时,您应该始终查看不必要的操作,例如,不要过于频繁地使用构造函数、连接和此类操作,尤其是避免在循环中使用它们。我想你使用 CStringW 有一些字符编码的原因,所以你可能不能使用不同的东西,这将是另一种优化代码的方法。

      【讨论】:

        【解决方案10】:

        您将字符串存储在哪种容器中?如果它是CStringW 中的std::vector,并且如果您事先没有reserve-ed 足够的内存,那么您一定会受到打击。 vector 通常会在达到其限制(不是很高)后调整大小,然后将整个复制到新的内存位置,这会给您带来很大的影响。随着您的vector 呈指数增长(即,如果初始大小为 1,则下一次分配 2,下一次分配 4,命中变得越来越少)。

        了解各个字符串的长度也很有帮助。 (有时:)

        【讨论】:

        • 固定数组...您是预先分配所有内存还是使用每个新字符串调整数组大小?
        • 不不不。我从不重新分配它。这是一个哈希表,在 rare 的情况下,我得到了一个重复的哈希键,做了一些额外的事情,但在我的基准测试期间,我实际上丢弃了重复项,只是为了确保它没有任何性能问题。
        • 看起来你已经完美地设置了一切。你试过 std::wstring 吗?此外,是时候寻求分析器的帮助了。
        猜你喜欢
        • 1970-01-01
        • 2011-10-22
        • 2014-10-10
        • 1970-01-01
        • 2021-07-28
        • 2013-06-17
        • 1970-01-01
        • 2017-01-31
        • 2019-02-25
        相关资源
        最近更新 更多