我一直在尝试实现一个霍夫曼解码器,my initial attempt由于解码算法的次优选择而导致性能低下。
我以为我尝试使用表查找来实现霍夫曼解码。但是,我有点坚持生成子表格,并希望有人能指出我正确的方向。
struct node
{
node* children; // 0 right, 1 left
uint8_t value;
uint8_t is_leaf;
};
struct entry
{
uint8_t next_table_index;
std::vector<uint8_t> values;
entry() : next_table_index(0){}
};
void build_tables(node* nodes, std::vector<std::array<entry, 256>>& tables, int table_index);
void unpack_tree(void* data, node* nodes);
std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> decode_huff(void* input)
{
// Initial setup
CACHE_ALIGN node nodes[512] = {};
auto data = reinterpret_cast<unsigned long*>(input);
size_t table_size = *(data++); // Size is first 32 bits.
size_t result_size = *(data++); // Data size is second 32 bits.
unpack_tree(data, nodes);
auto huffman_data = reinterpret_cast<long*>(input) + (table_size+32)/32;
size_t data_size = *(huffman_data++); // Size is first 32 bits.
auto huffman_data2 = reinterpret_cast<char*>(huffman_data);
// Build tables
std::vector<std::array<entry, 256>> tables(1);
build_tables(nodes, tables, 0);
// Decode
uint8_t current_table_index = 0;
std::vector<uint8_t, tbb::cache_aligned_allocator<uint8_t>> result;
while(result.size() < result_size)
{
auto& table = tables[current_table_index];
uint8_t key = *(huffman_data2++);
auto& values = table[key].values;
result.insert(result.end(), values.begin(), values.end());
current_table_index = table[key].next_table_index;
}
result.resize(result_size);
return result;
}
void build_tables(node* nodes, std::vector<std::array<entry, 256>>& tables, int table_index)
{
for(int n = 0; n < 256; ++n)
{
auto current = nodes;
for(int i = 0; i < 8; ++i)
{
current = current->children + ((n >> i) & 1);
if(current->is_leaf)
tables[table_index][n].values.push_back(current->value);
}
if(!current->is_leaf)
{
if(current->value == 0)
{
current->value = tables.size();
tables.push_back(std::array<entry, 256>());
build_tables(current, tables, current->value);
}
tables[table_index][n].next_table_index = current->value;
}
}
}
void unpack_tree(void* data, node* nodes)
{
node* nodes_end = nodes+1;
bit_reader table_reader(data);
unsigned char n_bits = ((table_reader.next_bit() << 2) | (table_reader.next_bit() << 1) | (table_reader.next_bit() << 0)) & 0x7; // First 3 bits are n_bits-1.
// Unpack huffman-tree
std::stack<node*> stack;
stack.push(&nodes[0]); // "nodes" is root
while(!stack.empty())
{
node* ptr = stack.top();
stack.pop();
if(table_reader.next_bit())
{
ptr->is_leaf = 1;
ptr->children = nodes[0].children;
for(int n = n_bits; n >= 0; --n)
ptr->value |= table_reader.next_bit() << n;
}
else
{
ptr->children = nodes_end;
nodes_end += 2;
stack.push(ptr->children+0);
stack.push(ptr->children+1);
}
}
}
答案 0 :(得分:1)
首先,避免所有这些向量。您可以指向一个预先分配的缓冲区,但是您不希望vector
在整个内存中分配这些微小的缓冲区的场景,并且您的缓存占用空间可以通过。
另请注意,非叶状态的数量可能远小于256.实际上,它可能低至128.通过为它们分配低状态ID,我们可以避免为整个状态节点集生成表条目(总共可能高达511个节点)。毕竟,在消耗输入之后,我们永远不会在叶子节点上结束;如果我们这样做,我们会生成输出,然后返回根目录。
然后,我们应该做的第一件事是将那些对应于内部节点的状态(即指针指向非叶子的状态)重新分配给低状态数。您可以使用它来减少状态转换表的内存消耗。
一旦我们分配了这些低状态数,我们就可以遍历每个可能的非叶状态,以及每个可能的输入字节(即,一个双重嵌套的for循环)。像对基于位的解码算法一样遍历树,并记录输出字节集,最终结束的最终节点ID(不能是叶子!),以及是否到达流末尾标记