首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >优化可变长度编码

问优化可变长度编码
EN

Stack Overflow用户
提问于 2011-05-02 22:51:46
回答 6查看 3.6K关注 0票数 12

我有一个例子,我需要压缩很多通常很小的值。因此,我使用可变长度的字节编码(具体地说,ULEB128)来压缩它们:

代码语言:javascript
复制
size_t
compress_unsigned_int(unsigned int n, char* data)
{
  size_t size = 0;
  while (n  > 127)
  {
    ++size;
    *data++ = (n & 127)|128;
    n >>= 7;
  }
  *data++ = n;
  return ++size;
}

有没有更有效的方法来做到这一点(也许使用SSE)?

编辑:在此压缩之后,结果以size字节存储到data中。然后,在下一个无符号int上调用压缩函数。

EN

回答 6

Stack Overflow用户

回答已采纳

发布于 2011-05-03 10:07:43

如果你的unsigned int值被限制在一个特定的范围内--比如32位--你可以展开循环:

代码语言:javascript
复制
size_t
compress_unsigned_int(unsigned int n, char* data)
{
  size_t size;

  if (n < 0x00000080U) {
    size = 1;
    goto b1;
  }
  if (n < 0x00004000U) {
    size = 2;
    goto b2;
  }
  if (n < 0x00200000U) {
    size = 3;
    goto b3;
  }
  if (n < 0x10000000U) {
    size = 4;
    goto b4;
  }
  size = 5;

  *data++ = (n & 0x7f) | 0x80;
  n >>= 7;
b4:
  *data++ = (n & 0x7f) | 0x80;
  n >>= 7;
b3:
  *data++ = (n & 0x7f) | 0x80;
  n >>= 7;
b2:
  *data++ = (n & 0x7f) | 0x80;
  n >>= 7;
b1:
  *data = n;
  return size;
}
票数 3
EN

Stack Overflow用户

发布于 2011-05-03 10:58:16

您要做的第一件事是针对当前代码测试任何可能的解决方案。

我认为你可能想试着摆脱数据依赖,让处理器同时做更多的工作。

什么是数据依赖?当数据流经函数时,n的当前值取决于n的前一个值,而前一个值又取决于之前的值...这是一个很长的数据依赖链。在下面的代码中,n从来没有被修改过,所以处理器可以“跳过”,同时做几件不同的事情,而不必等待计算新的n。

代码语言:javascript
复制
// NOTE: This code is actually incorrect, as caf noted.
// The byte order is reversed.
size_t
compress_unsigned_int(unsigned int n, char *data)
{
    if (n < (1U << 14)) {
        if (n < (1U << 7)) {
            data[0] = n;
            return 1;
        } else {
            data[0] = (n >> 7) | 0x80;
            data[1] = n & 0x7f;
            return 2;
        }
    } else if (n < (1U << 28)) {
        if (n < (1U << 21)) {
            data[0] = (n >> 14) | 0x80;
            data[1] = ((n >> 7) & 0x7f) | 0x80;
            data[2] = n & 0x7f;
            return 3;
        } else {
            data[0] = (n >> 21) | 0x80;
            data[1] = ((n >> 14) & 0x7f) | 0x80;
            data[2] = ((n >> 7) & 0x7f) | 0x80;
            data[3] = n & 0x7f;
            return 4;
        }
    } else {
        data[0] = (n >> 28) | 0x80;
        data[1] = ((n >> 21) & 0x7f) | 0x80;
        data[2] = ((n >> 14) & 0x7f) | 0x80;
        data[3] = ((n >> 7) & 0x7f) | 0x80;
        data[4] = n & 0x7f;
        return 5;
    }
}

我通过在0..UINT_MAX的一个紧密循环中执行它来测试它的性能。在我的系统上,执行时间是:

代码语言:javascript
复制
(Lower is better)
Original: 100%
caf's unrolled version: 79%
My version: 57%

一些小的调整可能会产生更好的结果,但我怀疑除非你去组装,否则你不会得到更多的改进。如果您的整数往往在特定范围内,那么您可以使用性能分析让编译器将正确的分支预测添加到每个分支中。这可能会让您的速度提高几个百分点。(编辑:我从重新排序分支中获得了8%,但这是一个不合理的优化,因为它依赖于每个数字0...UINT_MAX以相同的频率出现的事实。我不推荐这样做。)

SSE帮不上忙。SSE被设计为同时对相同宽度的多个数据进行操作,众所周知,要让SIMD对任何具有可变长度编码的数据进行加速都是非常困难的。(这不一定是不可能的,但也可能是不可能的,您必须非常聪明才能弄清楚。)

票数 8
EN

Stack Overflow用户

发布于 2011-12-30 21:38:45

您可能会在google协议缓冲区中找到快速实现:

http://code.google.com/p/protobuf/

看看CodedOutputStream::WriteVarintXXX方法。

第一个方法可以重写为:

代码语言:javascript
复制
char *start = data;
while (n>=0x80)
{
    *data++=(n|0x80);
    n>>=7;
}
*data++=n;
return data-start;

根据我的测试,google buffers的实现是最好的,然后是其他的实现。然而,我的测试是相当人工的,最好在您的应用程序中测试每种方法并选择最好的。提出的优化在特定的数值上工作得更好。

以下是我的测试应用程序的代码。(请注意,我已经从compress_unsigned_int_google_buf中删除了代码。您可能会在以下来自google buffer protocol的文件中找到实现: CodedOutputStream::WriteVarint32FallbackToArrayInline)方法coded_stream.cc

代码语言:javascript
复制
size_t compress_unsigned_int(unsigned int n, char* data)
{
    size_t size = 0;
    while (n  > 127)
    {
        ++size;
        *data++ = (n & 127)|128;
        n >>= 7;
    }
    *data++ = n;
    return ++size;
}

size_t compress_unsigned_int_improved(unsigned int n, char* data)
{
    size_t size;

    if (n < 0x00000080U) {
        size = 1;
        goto b1;
    }
    if (n < 0x00004000U) {
        size = 2;
        goto b2;
    }
    if (n < 0x00200000U) {
        size = 3;
        goto b3;
    }
    if (n < 0x10000000U) {
        size = 4;
        goto b4;
    }
    size = 5;

    *data++ = (n & 0x7f) | 0x80;
    n >>= 7;
b4:
    *data++ = (n & 0x7f) | 0x80;
    n >>= 7;
b3:
    *data++ = (n & 0x7f) | 0x80;
    n >>= 7;
b2:
    *data++ = (n & 0x7f) | 0x80;
    n >>= 7;
b1:
    *data = n;
    return size;
}

size_t compress_unsigned_int_more_improved(unsigned int n, char *data)
{
    if (n < (1U << 14)) {
        if (n < (1U << 7)) {
            data[0] = n;
            return 1;
        } else {
            data[0] = (n >> 7) | 0x80;
            data[1] = n & 0x7f;
            return 2;
        }
    } else if (n < (1U << 28)) {
        if (n < (1U << 21)) {
            data[0] = (n >> 14) | 0x80;
            data[1] = ((n >> 7) & 0x7f) | 0x80;
            data[2] = n & 0x7f;
            return 3;
        } else {
            data[0] = (n >> 21) | 0x80;
            data[1] = ((n >> 14) & 0x7f) | 0x80;
            data[2] = ((n >> 7) & 0x7f) | 0x80;
            data[3] = n & 0x7f;
            return 4;
        }
    } else {
        data[0] = (n >> 28) | 0x80;
        data[1] = ((n >> 21) & 0x7f) | 0x80;
        data[2] = ((n >> 14) & 0x7f) | 0x80;
        data[3] = ((n >> 7) & 0x7f) | 0x80;
        data[4] = n & 0x7f;
        return 5;
    }
}

size_t compress_unsigned_int_simple(unsigned int n, char *data)
{
    char *start = data;
    while (n>=0x80)
    {
        *data++=(n|0x80);
        n>>=7;
    }
    *data++=n;
    return data-start;
}

inline size_t compress_unsigned_int_google_buf(unsigned int value, unsigned char* target) {

          // This implementation might be found in google protocol buffers

}



#include <iostream>
#include <Windows.h>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    char data[20];
    unsigned char udata[20];
    size_t size = 0;
    __int64 timer;

    cout << "Plain copy: ";

    timer = GetTickCount64();

    size = 0;

    for (int i=0; i<536870900; i++)
    {
        memcpy(data,&i,sizeof(i));
        size += sizeof(i);
    }

    cout << GetTickCount64() - timer << " Size: " << size <<  endl;

    cout << "Original: ";

    timer = GetTickCount64();

    size = 0;

    for (int i=0; i<536870900; i++)
    {
        size += compress_unsigned_int(i,data);
    }

    cout << GetTickCount64() - timer << " Size: " << size << endl;

    cout << "Improved: ";

    timer = GetTickCount64();

    size = 0;

    for (int i=0; i<536870900; i++)
    {
        size += compress_unsigned_int_improved(i,data);
    }

    cout << GetTickCount64() - timer << " Size: " << size <<  endl;

    cout << "More Improved: ";

    timer = GetTickCount64();

    size = 0;

    for (int i=0; i<536870900; i++)
    {
        size += compress_unsigned_int_more_improved(i,data);
    }

    cout << GetTickCount64() - timer << " Size: " << size <<  endl;

    cout << "Simple: ";

    timer = GetTickCount64();

    size = 0;

    for (int i=0; i<536870900; i++)
    {
        size += compress_unsigned_int_simple(i,data);
    }

    cout << GetTickCount64() - timer << " Size: " << size <<  endl;

    cout << "Google Buffers: ";

    timer = GetTickCount64();

    size = 0;

    for (int i=0; i<536870900; i++)
    {
        size += compress_unsigned_int_google_buf(i,udata);
    }

    cout << GetTickCount64() - timer << " Size: " << size <<  endl;

    return 0;
}

在我的装有Visual C++编译器的机器上,我得到了以下结果:

普通文本: 358毫秒

原稿:2497ms

改进后:2215ms

更多改进: 2231毫秒

简单: 2059毫秒

Google Buffers: 968ms

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5858646

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档