延迟分配是在“桶”中只有一个项时使用Node**作为Node*的内存优化技巧。因此," item“成员要么保存此类孤独项的索引,要么保存一个负值(如果桶被完全分配)。
这里的缓存感知仅仅是在正确的位置添加编译器的__builtin_prefetch(),这样就可以提高大约30%的速度。
#ifndef TRIE4D_H
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
/* define your own value type if needed */
#define VALUE_TYPE int
struct Node {
struct Node **N;
VALUE_TYPE value;
char item;
};
struct Node* newNode();
int addNode(struct Node *C, void *key, int count, VALUE_TYPE value);
struct Node* findNode(struct Node * C, void *key, int count);
void freeNode(struct Node* C);
#endif // TRIE4D_H#include "trie4d.h"
struct Node* newNode() {
struct Node *node;
node = malloc(sizeof (struct Node));
node->value = -1;
node->item = -1;
return node;
}
int addNode(struct Node *C, void *key, int count, VALUE_TYPE value) {
int pos = 0;
struct Node* tmp;
while (1) {
char u;
int bpos = pos >> 1;
if (bpos >= count) {
if (C->value == -1) {
C->value = value;
return 1; /* value added */
}
C->value = value;
return 0; /* value replaced */
}
unsigned char b = ((unsigned char*)key)[bpos];
if (pos++ & 1) u = (b >>= 4);
else u = b & 0xf;
if (C->item == -1) {
C->item = u;
C->N = (struct Node**)newNode();
C = (struct Node*) C->N;
}
else if (C->item >= 0) {
if (C->item == u) {
C = (struct Node*)C->N;
}
else {
tmp = (struct Node*) C->N;
C->N = malloc(sizeof (void*) * 16);
memset(C->N, 0, sizeof(void*) << 4);
C->N[C->item] = tmp;
C->item = -2;
C = C->N[u] = newNode();
}
} else {
if (C->N[u])
C = C->N[u];
else
C = C->N[u] = newNode();
}
}
}
struct Node* findNode(struct Node * C, void *key, int count) {
int pos = 0;
while (1) {
char u;
int bpos = pos >> 1;
if (bpos >= count) return C;
unsigned char b = ((unsigned char*)key)[bpos];
if (pos++ & 1) u = (b >>= 4);
else u = b & 0xf;
if (C->item == -1) return 0;
else if (C->item >= 0) {
if (u == C->item) C = (struct Node*)C->N;
else return 0;
} else {
__builtin_prefetch(C->N[u]);
C = C->N[u];
if (C == 0) return 0;
}
}
}
#ifdef DEBUG
/* this will count upon destruction, how much memory was used */
int mem_count = 0;
#endif
void freeNode(struct Node* C) {
int i;
if (C->item == -2) {
for (i = 0; i < 16; i++) {
if (C->N[i]) freeNode(C->N[i]);
}
free(C->N);
#ifdef DEBUG
mem_count += 16 * sizeof(struct Node);
#endif
}
else if (C->item >= 0) {
freeNode((struct Node*)C->N);
}
free(C);
#ifdef DEBUG
mem_count += sizeof(struct Node);
#endif
}这个trie主要是作为非常快的键/值字典(与哈希表竞争)制作的,因此您将找不到用于尝试函数“从节点遍历”(对于“自动完成”样式实现有用)的通用函数。
虽然这个实现比哈希表使用更多的内存,但是它几乎同样快而且非常简单,只有100个LOC。使之成为移植到低级语言(如程序集或Forth)的好人选。
我们的目标是制作一个具有O(K)查找时间的非常快的字典,其中K是一个键的长度。问题是,现代CPU在指针删除时非常慢,而树的本质是在每一步上执行指针取消引用,因此应该重新加载缓存。
发布于 2015-07-22 02:35:03
总的来说,我发现您的代码相当合理。您使用可变长度键的每个4位作为"trie“的”字符“。
node->item设置为-1,但不将node->N初始化为任何东西。这是因为您的代码中有一个隐含的假设,即如果是node->item == -1,那么node->N是不可用的。但是在调试时,为了您自己的理智,我建议您无论如何设置node->N = NULL。* 16 one place和<< 4。但实际上,您应该将该代码替换为: C->N = calloc(16,sizeof(void *));bpos是“字节位置”,而pos是“咬点位置”。不过,我还是不知道C或u代表什么。我认为count应该改名为keyLength。在您的addNode()和findNode()函数中,您从键加载一个字节,并使用其上半部分或下半部分(每trie深度4位)。因此,您最终会从键中加载两次字节(每一半加载一次)。您可以通过保存该字节的值来做得更好,并且在第二次迭代中,您可以使用保存的值来获取上半部分,而不是从内存中重新加载该字节。
如前所述,我不认为您的__builtin_prefetch()调用会有任何作用。在要取消对指针的引用之前,就把预取调用放在这里,所以即使编译器在那里插入了预取指令,也无助于加快速度。我编译了你的程序,不管有没有这一行,这两个版本都生成了相同的汇编代码(没有预取指令)。从builtin_prefetch()中获得好处的方法是,在您真正使用该地址之前,将其称为一段时间。例如,在while循环的顶部,您可以这样做:
if (C->item != -1)
__builtin_prefetch(C->N);不过,我不确定你会从中得到多少好处。您将不得不运行一些测试,以确定这是否真的有帮助或伤害。
https://codereview.stackexchange.com/questions/97169
复制相似问题