我有一个大数组(>数百万)的Item
s,其中每个Item
都有以下形式:
struct Item { void *a; size_t b; };
有一些不同的a
字段 - 意味着许多项目具有相同的a
字段。
我想将这些信息“分解”出来以节省大约50%的内存使用量。
然而,问题是这些Item
具有重要的排序,并且可能会随着时间的推移而发生变化。因此,我不能继续为每个不同的Item[]
单独a
,因为这将失去项目相对于彼此的相对排序。
另一方面,如果我存储 size_t index;
字段中所有项目的排序,那么删除void *a;
字段会减少任何内存节省
那么有没有办法在这里实际节省内存,或者没有?
(注意:我已经可以想到例如使用unsigned char
为a
索引到一个小数组,但我想知道是否有更好的方法。那将要求我使用未对齐的内存或将每个Item[]
拆分为两个,这对于内存位置并不好,所以我更喜欢别的东西。)
答案 0 :(得分:5)
结合使用轻量级压缩方案(请参阅this获取示例和一些参考资料)来表示a*
值。例如,@ Frank的回答雇用了DICT,然后是NS。如果你有相同指针的长时间运行,你可以考虑RLE(运行长度编码)。
答案 1 :(得分:3)
这有点像黑客,但我过去曾经使用它并取得了一些成功。对象访问的额外开销通过显着的内存减少得到补偿。
典型的用例是这样一种环境,其中(a)值是实际区分的联合(即,它们包括类型指示符),具有有限数量的不同类型,(b)值大多数保存在大的连续向量中。 / p>
在这种环境下,(某些类型)值的有效载荷部分很可能会占用为其分配的所有比特。数据类型也可能需要(或受益于)存储在对齐的内存中。
实际上,现在大多数主流CPU都不需要对齐访问,我只会使用压缩结构而不是下面的hack。如果你不支付未对齐访问,那么将{one-byte type + 8-byte value}存储为九个连续字节可能是最佳的;唯一的代价是你需要乘以9而不是8来进行索引访问,这是微不足道的,因为9是编译时常量。
如果您确实需要支付未对齐的访问权限,则可以执行以下操作。 “增强”值的向量具有以下类型:
// Assume that Payload has already been typedef'd. In my application,
// it would be a union of, eg., uint64_t, int64_t, double, pointer, etc.
// In your application, it would be b.
// Eight-byte payload version:
typedef struct Chunk8 { uint8_t kind[8]; Payload value[8]; }
// Four-byte payload version:
typedef struct Chunk4 { uint8_t kind[4]; Payload value[4]; }
向量是Chunks的向量。为了使hack工作,它们必须分配在8(或4)字节对齐的内存地址上,但我们已经假设有效负载类型需要对齐。
hack的关键是我们如何表示指向单个值的指针,因为该值在内存中不是连续的。我们使用指向它的kind
成员的指针作为代理:
typedef uint8_t ValuePointer;
然后使用以下低但非零开销函数:
#define P_SIZE 8U
#define P_MASK P_SIZE - 1U
// Internal function used to get the low-order bits of a ValuePointer.
static inline size_t vpMask(ValuePointer vp) {
return (uintptr_t)vp & P_MASK;
}
// Getters / setters. This version returns the address so it can be
// used both as a getter and a setter
static inline uint8_t* kindOf(ValuePointer vp) { return vp; }
static inline Payload* valueOf(ValuePointer vp) {
return (Payload*)(vp + 1 + (vpMask(vp) + 1) * (P_SIZE - 1));
}
// Increment / Decrement
static inline ValuePointer inc(ValuePointer vp) {
return vpMask(++vp) ? vp : vp + P_SIZE * P_SIZE;
}
static inline ValuePointer dec(ValuePointer vp) {
return vpMask(vp--) ? vp - P_SIZE * P_SIZE : vp;
}
// Simple indexed access from a Chunk pointer
static inline ValuePointer eltk(Chunk* ch, size_t k) {
return &ch[k / P_SIZE].kind[k % P_SIZE];
}
// Increment a value pointer by an arbitrary (non-negative) amount
static inline ValuePointer inck(ValuePointer vp, size_t k) {
size_t off = vpMask(vp);
return eltk((Chunk*)(vp - off), k + off);
}
我遗漏了一堆其他黑客,但我相信你可以解决它们。
交错价值的一个很酷的事情是它具有适度的参考局部性。对于8字节版本,几乎一半的时间随机访问一种类型和一个值只能访问一个64字节的高速缓存行;其余的时间是两个连续的高速缓存行被击中,结果向前(或向后)通过向量向前走动就像通过普通向量一样缓存友好,除了它使用更少的高速缓存行,因为对象是一半大小。四字节版本甚至更加缓存。
答案 2 :(得分:2)
我想我自己想出了理论上最佳的信息方式......在我的案例中它并不值得获得,但我会在这里解释它,以防它帮助某人别的。
但是,它需要未对齐的内存(在某种意义上)
也许更重要的是,您失去了动态轻松添加a
新值的能力。
这里真正重要的是不同Item
s的数量,即不同(a,b)
对的数量。毕竟,对于一个a
来说,可能有十亿个不同的b
,但对于其他的只有少数几个,所以你想要利用它。
如果我们假设有N
个不同的项可供选择,那么我们需要n = ceil(log2(N))
位来表示每个Item
。所以我们真正想要的是一个n
位整数数组,其中n
计算在运行时。然后,一旦获得n
位整数,您就可以在log(n)
时间内进行二进制搜索,根据您对{的计数的了解,找出它对应的a
每个b
{1}}。 (这可能会受到性能影响,但这取决于不同a
的数量。)
你不能以良好的记忆协调的方式做到这一点,但这并不是太糟糕。你要做的是创建一个a
数据结构,其中每个元素的位数是一个动态可指定的数量。然后,要随机访问它,您需要执行一些除法或mod操作以及位移以提取所需的整数。
这里需要注意的是,除以变量可能会严重损害您的随机访问性能(尽管它仍然是O(1))。减轻这种情况的方法可能是为uint_vector
的公共值编写一些不同的过程(C ++模板在这里帮助!)然后用各种n
或if (n == 33) { handle_case<33>(i); }
等分支到它们中。因此,编译器将除数视为常量,并根据需要生成移位/加法/乘法,而不是除法。
这在信息理论上是最优的,只要您需要每个元素的常数位数,这是您想要随机访问的。但是,如果放宽约束,你可以做得更好:你可以将多个整数打包成switch (n) { case 33: handle_case<33>(i); }
位,然后用更多的数学提取它们。这也可能会扼杀性能。
(或者,长话短说:C和C ++确实需要高性能的k * n
数据结构......)
答案 3 :(得分:2)
Array-of-Structures方法可能会有所帮助。也就是说,有三个向量......
vector<A> vec_a;
vector<B> vec_b;
SomeType b_to_a_map;
您可以将数据视为...
Item Get(int index)
{
Item retval;
retval.a = vec_a[b_to_a_map[index]];
retval.b = vec_b[index];
return retval;
}
现在你需要做的就是为SomeType选择合理的东西。例如,如果vec_a.size()为2,则可以使用vector&lt; bool&gt;或者boost :: dynamic_bitset。对于更复杂的情况,您可以尝试进行位打包,例如,为了支持A的4值,我们只需更改我们的函数...
int a_index = b_to_a_map[index*2]*2 + b_to_a_map[index*2+1];
retval.a = vec_a[a_index];
你总是可以通过使用范围打包来打败比特打包,使用div / mod来存储每个项目的小数位长度,但复杂性会快速增长。
这里有一本很好的指南http://number-none.com/product/Packing%20Integers/index.html