我目前正在开发一个嵌入式设备项目,我遇到了性能问题。分析已经找到了我想要消除的O(N)操作。
我基本上有两个数组int A[N]
和short B[N]
。 A
中的条目是唯一的,并由外部约束排序。最常见的操作是检查a
中是否显示特定值A[]
。不太常见但仍然常见的是对A[]
元素的更改。新值与之前的值无关。
由于最常见的操作是查找,这就是B[]
的位置。它是A[]
中的排序索引数组,当{且仅当A[B[i]] < A[B[j]]
时i<j
}。这意味着我可以使用二进制搜索在A
中找到值。
当然,当我更新A[k]
时,我必须在k
中找到B
并将其移至新位置,以维持搜索顺序。由于我知道A[k]
的旧值和新值,因此memmove()
的旧位置和新位置之间只有B[]
的{{1}}个k
。这是我需要解决的O(N)操作;由于A[k]
的旧值和新值基本上是随机的,我平均在 N / 2 N / 3元素上移动。
我使用std::make_heap
作为谓词来查看[](int i, int j) { return A[i] < A[j]; }
。在这种情况下,我可以轻松地使B[0]
指向A
的最小元素,而更新B
现在是一种廉价的O(log N)重新平衡操作。但是,我通常不需要A的最小值,我需要查找是否存在任何给定值。现在,这是B
中的O(N log N)搜索。 (我的N个元素中有一半是堆深度log N,四分之一(log N)-1等),这与直接在A
中的哑O(N)搜索相比没有任何改进。
考虑到std::set
有O(log N)插入和查找,我想说应该可以在这里获得相同的性能以进行更新和查找。但是我该怎么做?我需要B
的其他订单吗?另一种类型?
B
目前是short [N]
,因为A
和B
一起大约是我的CPU缓存的大小,而我的主内存要慢得多。从6 * N到8 * N字节不是很好,但如果我的查找和更新都转到O(log N),那么仍然可以接受。
答案 0 :(得分:7)
如果唯一的操作是(1)检查值'a'是否属于A和(2)更新A中的值,为什么不使用哈希表代替已排序的阵列B?特别是如果A不会增大或缩小尺寸而且值只会改变,这将是一个更好的解决方案。哈希表不需要比数组多得多的内存。 (或者,B应该更改为堆而不是二进制搜索树,可以自我平衡,例如splay树或红黑树。但是,树需要额外内存,因为左右指针。)
增加内存使用从6N到8N字节的实用解决方案是针对准确的50%填充哈希表,即使用由2N短路阵列组成的哈希表。我建议实施 Cuckoo Hashing 机制(参见http://en.wikipedia.org/wiki/Cuckoo_hashing)。进一步阅读文章,你会发现你可以通过使用更多的哈希函数来获得高于50%的负载因子(即将内存消耗从8N推向7N)。 “只使用三个哈希函数可将负载增加到91%。”
来自维基百科:
Zukowski等人的一项研究。已经表明杜鹃散列很多 对于小的,缓存驻留的哈希表而言,比链式哈希更快 现代处理器。肯尼斯罗斯已经展示了bucketized版本 cuckoo散列(使用包含多个存储桶的变体) key)比传统方法更快也适用于大哈希 表,当空间利用率很高时。的表现 Askitis进一步调查了bucketized cuckoo hash table, 其性能与其他散列方案相比较。
答案 1 :(得分:1)
std::set
通常使用二叉搜索树提供 O(log(n))插入和删除。不幸的是,这对于大多数基于指针的实现使用3 * N空间。假设字大小的数据,1表示数据,2表示每个节点上左右子节点的指针。
如果你有一个常数N并且可以保证ceil(log2(N))
小于字大小的一半,你可以使用每个2 * N大小的固定长度的树节点数组。使用1表示数据,1表示两个子节点的索引,存储为单词的上半部分和下半部分。这是否会让您使用某种方式的自平衡二叉搜索树取决于您的N和字大小。对于16位系统,您只能获得N = 256,但是对于32,它是65k。
答案 2 :(得分:0)
由于你的N有限,你不能std::set<short, cmp, pool_allocator> B
使用Boost's pool_allocator
吗?