| I have been advocating for the PrefixVarint encoding you mention for a while. One thing I'd mention though: as you've specified it here, it puts the continuation bits as the high bits of the first byte. I think it may be better to put them in the lower bits of that byte instead. It would allow for a simple loop-based implementation of the encoder/decoder (LEB128 also allows this). With continuation bits in the high bits of the first byte, you pretty much have to unroll everything. You have to give each length its own individual code-path, with hard-coded constants for the shifts and continuation bits. The downside is one extra shift of latency in the one-byte case, imposed on all encoders/decoders. Unrolling is probably a good idea for optimization anyway, but it seems better to standardize on something that at least allows a simple implementation. Here is some sample code for a loop-based implementation that uses low bits for continuation bits: // Little-endian only. Untested.
char *encode(char *p, uint64 val) {
int len = 1;
uint64 encoded = val << 1;
uint64 max = 1 << 7;
while (val > max) {
if (max == 1ULL << 63) {
// Special case so 64 bits fits in 9 bytes.
*p++ = 0xff;
memcpy(p, &val, 8);
return p + 8;
}
encoded = (encoded << 1) | 1;
max <<= 7;
len++;
}
memcpy(p, &encoded, len);
return p + len;
}
const char *decode(const char *p, uint64* val) {
if (*p == 0xff) {
// 9-byte special case
memcpy(val, p + 1, 8);
return p + 9;
}
// Can optimize with something like
// int len = __builtin_ctz(!*p);
unsigned char b = *p;
int len = 1;
while (b & 1) {
len++;
b >>= 1;
}
*val = 0;
memcpy(val, p, len);
*val >>= len;
return p + len;
}
|