【问题标题】:Trouble implementing a "rope" data structure in C++在 C++ 中实现“绳索”数据结构时遇到问题
【发布时间】:2012-09-05 17:34:37
【问题描述】:

我正在尝试创建一个rope 数据结构。它是一种二叉树,即递归数据结构。

绳索的目的是分裂和连接应该快速,这意味着你避免复制整个绳索
因此,例如,用户应该能够说 rope1 + rope2 并期望在 ~ 对数时间内得到结果。

但是,这带来了一个问题:
如果一条绳索被修改,它的父项也会被间接修改。
因为我的目标是让rope 成为string 的替代品,所以这是不可接受的。

对此我的解决方案是:每当rope 发生任何 类型的更改时,我都会创建一个 节点,稍作更改,留下旧的未修改。

理论上,我认为这会很好。

但实际上,它涉及为(几乎?)字符串的每次修改进行堆分配。
即使是一个字符的变化也会导致一个新的堆对象,这不仅本身很慢,而且还会显着降低内存局部性,对性能产生非常负面的影响。

我应该如何解决这个问题?

【问题讨论】:

  • 那么,您希望拆分和连接速度快,而且其他一切都快吗?我认为这是一个“你不能吃蛋糕也不能吃蛋糕”的例子。
  • 如果您有 GCC,请检查 <ext/rope> 标头以获取示例实现。
  • “因为我的目标是让绳索成为字符串的替代品,”你不能rope 成为string 的替代品。根据定义,它们是两种不同的数据结构,具有两组不同的操作。 string,在 C++11 中,必须是连续的,而你的绳索不是。
  • 如果它也被另一个字符串引用,您只需要进行堆分配/复制。您很快就会陷入所有这些副本最终都发生的情况,只是稍后发生并且使用更多开销。
  • @R.MartinhoFernandes:是的,我很害怕...

标签: c++ data-structures ropes


【解决方案1】:

传统的方法是写时复制:为此,您需要重新计算每个分配的节点。

如果修改的节点的引用计数为 1(没有其他人引用它),则不需要复制它。

这样做的实际问题是有效地分离变异和非变异操作:

    char& rope::operator[] (std::string::pos)

可能改变被引用的字符,但是当它实际上不会时,没有简单的方法可以强制选择 const 重载。这意味着您要么必须假设会发生突变,并可能触发不必要的副本,要么返回一些重载 char 转换和赋值的代理。

这种方法曾尝试用于std::string(其中字符串相当于单节点绳索)iirc 的早期实现,但后来失宠了;部分原因是变异问题,部分原因是如果您不得不担心多线程,COW 和所需的引用计数会变得越来越昂贵。


正如您所说,如果您的节点包含两种独立类型的状态,则绳索还有一个问题:它自己的字符串和对其子节点的引用(因为这会导致 refcount/child 引用突变沿树传播)。

如果相反,字符仅存储在叶节点上,并且您对非叶节点进行 完整 副本(因此每个绳索都有自己的“目录结构”),您仍然可以避免复制字符,并且引用的共享状态要简单得多。

这能得到你想要的对数时间连接吗?也许不是:

  • 你必须复制所有非叶子节点(并添加一个新的根),这些节点的数量是叶子的数量
  • 您还必须增加叶引用计数,这是线性的

看起来是否更接近线性时间或对数时间取决于增加引用计数与复制目录树的相对成本。

如果没有这个,您可以获得快速连接,但如果必须将 COW 操作向上传播到树上,任意字符访问可能(不可预测地)退化为对数时间。

我想,如果它适合您的用例,您可以实现移动连接:这可能会给出恒定时间的加法,并且您仍然可以避免额外的 COW 复杂性。

【讨论】:

  • +1 用于引用计数,似乎是合理的。 (仍在阅读其余部分——到目前为止看起来很棒,感谢您提供的信息。)
【解决方案2】:

不过,在实践中,它涉及(几乎?)字符串的每次修改都需要为堆分配。

如果你想避免频繁的堆分配性能问题,那么我建议为你的类使用一个内存池来分配一块内存,并且只需要在它已满时从操作系统请求新的分配,这应该很少发生。然后,您可以优化对内存池的访问,以分配诸如char 等小块数据类型。

Andrei Alexandrescu 在他的“现代 C++ 设计”一书中有一个很好的小块内存分配器示例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多