高效的C BitStream实现

时间:2014-03-05 16:04:16

标签: c compression codec bitstream

我目前正在研究在纯C中实现高效BitStream的各种可能策略。我需要这个来实现各种基于位的压缩算法。但是,我找不到很多关于这个主题的文献,似乎没有很多我能找到的好例子。

以下是我要找的内容:

  • 主要是基于宏的实现,以避免函数调用
  • 用于向/从BitStream读取/写入'n'位的函数。
  • 读取/写入特定位数(如5位)的功能,优化通用位数。

我想知道以下事项:

  • 应该在BitStream中维护的变量。可以有BYTE指针,字节位置,当前字节中的位索引,当前字节中剩余的位数等。
  • 如何减少要维护的变量数量。我们拥有的变量越多,我们需要更新的变量就越多。
  • 如何在单个读/写操作的上下文中使用少量中间/临时变量。
  • 如果操作应在BYTE级别或UINT16级别或UINT32级别完成。也许可以将位累积到UINT32中并在字节填满时写入字节(或者在完成写入时使用刷新操作)比完成每个字节的操作要快得多。
  • 我们如何尽可能避免循环。理想情况下,我们应该不惜一切代价避免循环写入BitStream的位数。

这可能看起来有些过分,但是当压缩中涉及的其余代码已经过极大优化时,看起来BitStream部分只是破坏了整个事情。例如,在图像压缩代码中使用SIMD CPU指令来优化部分编码过程并不罕见,但最后一步是写入BitStream。

想法,参考,任何人?谢谢!

2 个答案:

答案 0 :(得分:1)

嗯。不幸的是,我要指出的不一定是一个有效的实现,我希望有人发布了一个更好的答案(我没有像多媒体迈克建议的那样检查ffmpeg)。但是我可以在你的理解中看到足够的漏洞让我认为将这个作为答案发布是值得的。

首先,宏不是良好性能所必需的!这是一种高度过时的方法,除非您编写的是非常古老或不成熟的编译器。内联函数与宏一样有效(有警告,有时会提高效率)。通过明智地使用静态函数,编译器可以决定应该内联的内容和不应该内联的内容。虽然gcc可能不是一个优秀的编译器,但它确定值何时是常量,甚至是指针!这也消除了不断放入宏的需要。就是这样:

#define UINT_BIT_SIZE (sizeof(uint) * 8)

完全一样有效
static const size_t UINT_BIT_SIZE = sizeof(uint) * 8;

除了后者有类型。对于gcc的最新版本(最近4年左右),你甚至不需要const来将它视为编译时常量,只要它被标记为静态(或本地)并且值不会被编译单元中的任何代码修改,它会将其视为编译时常量。

CPU缓存的出现彻底改变了使一段代码快速或(相对)缓慢的原因。如果您的代码的热部分不适合上层缓存(L1 / L2),那么您的内联和/或宏最终会减慢您的速度,特别是如果您必须访问主内存。同样,一次触摸多个位置的数据会导致大量缓存未命中。

话虽如此,我写了“Bit Creek”,这是一个针对Linux设备驱动程序的非性能关键部分的小型实现(“creek”,如“不太流”)。 struct bit_creek表示您将其视为位流的内存段,函数creek_get_bitscreek_put_bits作为返回-EOVERFLOW的流来读取或写入它,如果您溢出你的缓冲区。

如果我将skip_bits存储在结构中,我可能会挤出更多的性能,甚至,正如你所建议的那样,在我有一个完整的字节之前,甚至都不会将字节写入主存储器,但性能根本不重要此

祝你好运!

答案 1 :(得分:1)

对于记录,这是我最终得到的BitStream实现。它主要是基于宏的,并使用32位累加器。比特从最高有效位到最低有效位存储在累加器中。例如,为了测试下一位是否设置,可以做(累加器和0x80000000)。这使得在不必操作BitStream的情况下进行多次测试非常实用。为了写入,我在累加器中累加位,并在它满时自动将它们刷新到输出缓冲区。当写入所有位时,也可以手动触发刷新。您的里程可能会有所不同,但我对此非常满意。我用它来实现微软点对点压缩(MPPC),它使用霍夫曼编码,性能很好。

struct _wBitStream
{
    BYTE* buffer;
    BYTE* pointer;
    DWORD position;
    DWORD length;
    DWORD capacity;
    UINT32 mask;
    UINT32 offset;
    UINT32 prefetch;
    UINT32 accumulator;
};
typedef struct _wBitStream wBitStream;

#define BitStream_Prefetch(_bs) do { \
    (_bs->prefetch) = 0; \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 4)) \
        (_bs->prefetch) |= (*(_bs->pointer + 4) << 24); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 5)) \
        (_bs->prefetch) |= (*(_bs->pointer + 5) << 16); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 6)) \
        (_bs->prefetch) |= (*(_bs->pointer + 6) << 8); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 7)) \
        (_bs->prefetch) |= (*(_bs->pointer + 7) << 0); \
} while(0)

#define BitStream_Fetch(_bs) do { \
    (_bs->accumulator) = 0; \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 0)) \
        (_bs->accumulator) |= (*(_bs->pointer + 0) << 24); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 1)) \
        (_bs->accumulator) |= (*(_bs->pointer + 1) << 16); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 2)) \
        (_bs->accumulator) |= (*(_bs->pointer + 2) << 8); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) <(_bs->capacity + 3)) \
        (_bs->accumulator) |= (*(_bs->pointer + 3) << 0); \
    BitStream_Prefetch(_bs); \
} while(0)

#define BitStream_Flush(_bs) do { \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 0)) \
        *(_bs->pointer + 0) = (_bs->accumulator >> 24); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 1)) \
        *(_bs->pointer + 1) = (_bs->accumulator >> 16); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 2)) \
        *(_bs->pointer + 2) = (_bs->accumulator >> 8); \
    if (((UINT32) (_bs->pointer - _bs->buffer)) < (_bs->capacity + 3)) \
        *(_bs->pointer + 3) = (_bs->accumulator >> 0); \
} while(0)

#define BitStream_Shift(_bs, _nbits) do { \
    _bs->accumulator <<= _nbits; \
    _bs->position += _nbits; \
    _bs->offset += _nbits; \
    if (_bs->offset < 32) { \
        _bs->mask = ((1 << _nbits) - 1); \
        _bs->accumulator |= ((_bs->prefetch >> (32 - _nbits)) & _bs->mask); \
        _bs->prefetch <<= _nbits; \
    } else { \
        _bs->mask = ((1 << _nbits) - 1); \
        _bs->accumulator |= ((_bs->prefetch >> (32 - _nbits)) & _bs->mask); \
        _bs->prefetch <<= _nbits; \
        _bs->offset -= 32; \
        _bs->pointer += 4; \
        BitStream_Prefetch(_bs); \
        if (_bs->offset) { \
            _bs->mask = ((1 << _bs->offset) - 1); \
            _bs->accumulator |= ((_bs->prefetch >> (32 - _bs->offset)) & _bs->mask); \
            _bs->prefetch <<= _bs->offset; \
        } \
    } \
} while(0)

#define BitStream_Write_Bits(_bs, _bits, _nbits) do { \
    _bs->position += _nbits; \
    _bs->offset += _nbits; \
    if (_bs->offset < 32) { \
        _bs->accumulator |= (_bits << (32 - _bs->offset)); \
    } else { \
        _bs->offset -= 32; \
        _bs->mask = ((1 << (_nbits - _bs->offset)) - 1); \
        _bs->accumulator |= ((_bits >> _bs->offset) & _bs->mask); \
        BitStream_Flush(bs); \
        _bs->accumulator = 0; \
        _bs->pointer += 4; \
        if (_bs->offset) { \
            _bs->mask = ((1 << _bs->offset) - 1); \
            _bs->accumulator |= ((_bits & _bs->mask) << (32 - _bs->offset)); \
        } \
    } \
} while(0)

void BitStream_Attach(wBitStream* bs, BYTE* buffer, UINT32 capacity)
{
    bs->position = 0;
    bs->buffer = buffer;
    bs->offset = 0;
    bs->accumulator = 0;
    bs->pointer = bs->buffer;
    bs->capacity = capacity;
    bs->length = bs->capacity * 8;
}

wBitStream* BitStream_New()
{
    wBitStream* bs = NULL;

    bs = (wBitStream*) calloc(1, sizeof(wBitStream));

    return bs;
}

void BitStream_Free(wBitStream* bs)
{
    free(bs);
}