如何压缩指针?例如。任意位指针

时间:2013-01-23 04:00:32

标签: c++ algorithm data-structures bit-manipulation gpu

我正在编写一个复杂的树数据结构,它存储了许多指针。指针本身占用了大量空间,这就是我期望保存的内容。

所以我在这里询问是否有这方面的例子。例如:对于64位数据类型,如果指向的数据肯定是连续的,我可以使用32位或更少的指针吗?

我发现了一篇名为Transparent Pointer Compression for Linked Data Structures的论文,但我认为可能有一个更简单的解决方案。

5 个答案:

答案 0 :(得分:5)

不使用指针,而是使用索引到数组中。如果数组长度小于65536,则索引可以是short,如果小于2147483648,则索引可以是int32_t

任意指针实际上可以在内存中的任何位置,因此无法将其缩短超过几位。

答案 1 :(得分:1)

在某些情况下,您可以简单地使用数组来保存节点。 arr[i]处的二叉树节点将生成从arr[(i*2)+1]arr[(i+1)*2]的子项。它的父级将在arr[(i-1)/2],如果i!= 0.并且要想出真正的指针地址,当然,你可以说&arr[i]。对于按规范填充的树,它实际上是一种相当常见的实现,就像用于堆的树一样。

为了让节点知道如何找到它的子节点,你可能需要索引或指向容器的指针。 (即便如此,只有两件中的一件,你需要做一些箍跳;你真的需要两件才能轻松地做事。但是必须要计算东西而不是记住它,有点像你不记得太多时付出的代价。)为了保持合理节省空间的东西,你必须愚弄节点;使它们基本上是结构,甚至只是值,让树类做所有节点查找的东西。它只是分发指向节点的指针,并且指针将是所有容器需要找出节点的索引(因此,它的子节点将在哪里)。您还必须将树指针和节点指针都传递给任何想要遍历树的函数。

请注意,除非您的树一直接近满(除非您的大多数/所有叶节点都在最后),否则这将不会节省太多空间。对于不在树底部的每个叶节点(顶部是根),你会浪费类似((节点大小)*(树大小/ i))字节的东西。

如果您不能指望树已满,或者节点处于某个受限制的空间中,那么这里不需要进行优化。树的整个点是节点指向他们的孩子;你可以用一个数组来伪造它,但是为了让树变得有价值,它必须能够以某种方式找到节点的子节点。

答案 2 :(得分:1)

一种选择是编写自定义分配器来分配大块连续内存,然后将节点连续存储在那里。然后,每个节点都可以通过一个简单的索引引用,该索引可以使用简单的指针算法映射回内存(例如:node_ptr = mem_block_ptr + node_index)。

很快你意识到拥有多个这些内存块意味着你不再知道特定节点所在的位置。这是partitioning进入场景的地方。您可以选择水平和/或垂直分区。两者都大大提高了复杂程度,两者都有利有弊(见[1][2])。

这里的关键是确保以可预测的方式分割数据

参考文献:

  1. Building Scalable Databases: Pros and Cons of Various Database Sharding Schemes
  2. 37signals - Mr. Moore gets to punt on sharding

答案 3 :(得分:1)

如果指针的利用率需要占用大量空间:

使用指针数组,并用该数组中的索引替换指针。这增加了另一个间接性。如果指针少于64k,则需要[]数组(Linux)

简单实施

#define   MAX_PTR  60000

void *aptr[MAX_PTR];
short nb = 0;

short ptr2index(void *ptr) {
  aptr[nb] = ptr;
  return (short)nb++;
}

void *index2ptr(short index) {
  return aptr[index];
}

... utilization ...

... short next; // in Class

Class *c = new Class();
mystruct->next = ptr2index((void *)c);

...

Class *x = (Class *)index2ptr(otherstruct->next);

答案 4 :(得分:0)

处理问题的一种非常简单的方法就是使用较少的指针(看起来很傻)?

比较以下两种方法:

template <typename T>
struct OctreeNaiveNode {
    T value;
    Point center;
    OctreeNaiveNode* parent;
    std::unique_ptr<OctreeNaiveNode> children[8];
}; // struct OctreeNaiveNode

// sizeof(OctreeNaiveNode) >= sizeof(T) + sizeof(Point) + 9 * sizeof(void*)

template <typename T>
struct OctreeNode {
    T value;
    Point center;
    std::unique_ptr<OctreeNode[]> children; // allocate for 8 only when necessary
}; // struct OctreeNode

// sizeof(OctreeNode) >= sizeof(T) + sizeof(Point) + sizeof(void*)

它是如何运作的:

  • 父指针只对于简单迭代器是必需的,如果迭代器的余量远远少于节点,那么使用深度迭代器就更经济了:即保存父堆栈直到根的迭代器。请注意,在RB树中它不能很好地工作(平衡),但在八叉树中它应该更好,因为分区是固定的。
  • 单个子指针:不是有一个指向子节点的数组,而是构建一个指向子数组的指针。它不仅意味着1个动态分配而不是8个(更少的堆碎片/开销),但它也意味着1个指针而不是节点内的8个。

开销:

  • Point = std::tuple<float,float,float> =&gt; sizeof(T) + sizeof(Point) >= 64 =&gt; 100%
  • Point = std::tuple<double,double,double> =&gt; sizeof(T) + sizeof(Point) >= 256 =&gt; 25%

所以,我建议你不要再深入研究压缩指针策略,而应该重新设计你的数据结构,以便首先减少指针/间接。