【问题标题】:Allocate constant strings in container contiguously在容器中连续分配常量字符串
【发布时间】:2011-12-31 11:02:28
【问题描述】:

假设我有一个 std::vectorconst std::strings。

std::vector<const std::string> strs;

现在这里的默认行为是实际的字符串容器可以分配在堆上的任何位置,这几乎可以在迭代包含的字符串时禁用任何数据预取。

strs.push_back("Foo"); // allocates char block on heap
strs.push_back("Boo"); // allocates char block on heap

但是,由于字符串是“const”,我希望 char 块被连续分配或彼此靠近(如果可能),以便在遍历字符串时获得最有效的缓存行为。

有没有办法实现这种行为?

【问题讨论】:

  • 除了答案中显示的问题之外,您甚至不能拥有 const 对象的向量。存储类型的要求包括可复制(或可移动,取决于 C++ 版本)。 const 对象会失败。
  • @BoPersson:好点。我想由程序员来确保字符串没有被改变。
  • 如果将向量本身设为 const,则无法更改其成员。但是,向量和字符串都不会真正意识到这一点。

标签: c++ string optimization memory-management


【解决方案1】:

您需要一个称为内存区域分配器的自定义分配器。您可以在 Wikipedia 或 Google 上查看更多信息,但基本思想类似于硬件堆栈——分配一个大块,然后简单地增加指针以将其标记为已使用。它可以非常快速地处理许多连续的请求,但不能处理释放和分配——所有释放都是一次性完成的。

【讨论】:

  • 嗯,它可以以有限的方式处理释放 - 当你释放最近分配的块时,你可以重置内部指针。
  • @DeadMG 你能解决我在回答中提到的小字符串优化问题吗?
【解决方案2】:

如果真的这么简单——推送永远不会改变的字符串,那么编写自己的分配器就很容易了。分配一大块内存,将指针free设置为块内偏移量0。当您需要存储新字符串 strncpy 时,将其存储为 free 并使用 strlen 增加 free。跟踪内存块的末尾,并在需要时分配另一个块。

【讨论】:

  • 用 C 风格的字符串处理模拟不是一个好主意。 std::basic_string 是分配器感知的,所以我会使用它。
【解决方案3】:

不是真的。

std::string 不是 POD,它不会将其内容保存在“对象内部”。更重要的是 - 它甚至不需要将其内容存储在单个内存块中。

另外,std::vector(与所有数组一样)需要其内容为一种类型(= 大小相等),因此您不能创建不同长度的字符串的文字“数组”。

你最好的方法是假设一个长度并使用std::vector&lt;std::array&lt;char, N&gt; &gt;

如果您需要真正不同的长度,另一种方法是使用 std::vector&lt;char&gt; 表示数据加上 std::vector&lt;unsigned&gt; 表示连续字符串开始的索引。


为字符串滚动你自己的分配器是一个诱人的想法,你可以将它基于std::vector&lt;char&gt;,然后在其上滚动你自己的std::basic_string,然后将它们集合起来。

请注意,您实际上很大程度上依赖于特定的std::string 实现。有些确实有一个 N 字符的内部缓冲区,并且仅在字符串长度大于缓冲区时才在外部分配内存。如果您的实现是这种情况,您仍然不会为整个字符串缓冲区获得连续的内存。

基于此,我得出结论,使用std::string,您通常无法完成您想要的(除非您依赖特定的 STL 实现),您需要提供另一个字符串实现以满足您的需求。

【讨论】:

  • 好吧,我想到的是某种用于字符串的自定义分配器,例如std::vector<:basic_string std::char_traits>, contigious_string_allocator>。不过不确定它是如何工作的。
  • 嗯,我在再次阅读主题/答案后发现了这一点;查看编辑
  • std::string 被间接强制将其内容存储在连续内存中。对于某些在 C++11 中,我认为也已经在 C++03 中。每个主要实现(MSVC、GCC、libc++)都使用连续的std::strings。
  • @up 但仍然有可能使用内部或外部缓冲区,对吧?
  • @Kos:这就是所谓的“小字符串优化”。由于缓冲区是内部的,它会在分配向量空间的同时被分配,这意味着它比外部缓冲区版本更紧凑。
【解决方案4】:

自定义分配器很棒,但为什么不将所有字符串存储在单个std::vector&lt;char&gt;std::string 中,并通过偏移量访问原始字符串?

简单有效。

【讨论】:

  • 因为当我想访问特定字符串时,我需要将子字符串复制到我返回的新 std::string 中。
  • 由于字符串不能改变大小,这确实是一个不错的选择。
  • @ronag:您也许可以将带有pair&lt;iterator,iterator&gt; 的字符串引用模拟到std::stringstd::vector&lt;char&gt;
  • @Xeo:好主意。虽然它没有那么优雅,但它会比自定义分配器更简单。
  • @ronag:不可变字符串(也称为 ref-strings)确实是 C++ 缺少的东西。它对字符串文字之类的东西有很大帮助,只要你不写信就不需要复制它们。
【解决方案5】:

您始终可以编写一个私有分配器(std::vector 的第二个模板参数),它将分配来自连续池的所有字符串。 另外您可以使用std::basic_string 代替std::string(这是std::basic_string 的私有案例),它允许类似地指定您自己的分配器。一般来说,我会说这是“过早优化”的情况,但我相信您已经测量并看到了这里的性能下降......不过,我相信付出的代价会浪费一些内存。

【讨论】:

【解决方案6】:

向量保证是连续的内存并且是 可与数组互操作。它不是一个单链表。

“连续性实际上是向量抽象的一部分。它是如此重要,事实上,C++03 标准已被修改为明确添加了保证。”

来源:http://herbsutter.com/2008/04/07/cringe-not-vectors-are-guaranteed-to-be-contiguous/

使用reserve() 强制它是连续的而不是重新分配。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
using namespace std;

int main()
{
    // create empty vector for strings
    vector<const string> sentence;

    // reserve memory for five elements to avoid reallocation
    sentence.reserve(5);

    // append some elements
    sentence.push_back("Hello,");
    sentence.push_back("how");
    sentence.push_back("are");
    sentence.push_back("you");
    sentence.push_back("?");

    // print elements separated with spaces
    copy (sentence.begin(), sentence.end(),
          ostream_iterator<string>(cout," "));
    cout << endl;

return 0;
}

【讨论】:

  • 向量可能是连续的,但是不同的std::string:s分配的用于存储字符串数据的内存块不是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-10-26
  • 1970-01-01
  • 2011-07-02
  • 1970-01-01
  • 1970-01-01
  • 2011-11-09
  • 1970-01-01
相关资源
最近更新 更多