我有一个例子,我需要压缩很多通常很小的值。因此,我使用可变长度的字节编码(具体地说,ULEB128)来压缩它们:
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上调用压缩函数。
发布于 2011-05-03 10:07:43
如果你的unsigned int值被限制在一个特定的范围内--比如32位--你可以展开循环:
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;
}发布于 2011-05-03 10:58:16
您要做的第一件事是针对当前代码测试任何可能的解决方案。
我认为你可能想试着摆脱数据依赖,让处理器同时做更多的工作。
什么是数据依赖?当数据流经函数时,n的当前值取决于n的前一个值,而前一个值又取决于之前的值...这是一个很长的数据依赖链。在下面的代码中,n从来没有被修改过,所以处理器可以“跳过”,同时做几件不同的事情,而不必等待计算新的n。
// 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的一个紧密循环中执行它来测试它的性能。在我的系统上,执行时间是:
(Lower is better)
Original: 100%
caf's unrolled version: 79%
My version: 57%一些小的调整可能会产生更好的结果,但我怀疑除非你去组装,否则你不会得到更多的改进。如果您的整数往往在特定范围内,那么您可以使用性能分析让编译器将正确的分支预测添加到每个分支中。这可能会让您的速度提高几个百分点。(编辑:我从重新排序分支中获得了8%,但这是一个不合理的优化,因为它依赖于每个数字0...UINT_MAX以相同的频率出现的事实。我不推荐这样做。)
SSE帮不上忙。SSE被设计为同时对相同宽度的多个数据进行操作,众所周知,要让SIMD对任何具有可变长度编码的数据进行加速都是非常困难的。(这不一定是不可能的,但也可能是不可能的,您必须非常聪明才能弄清楚。)
发布于 2011-12-30 21:38:45
您可能会在google协议缓冲区中找到快速实现:
http://code.google.com/p/protobuf/
看看CodedOutputStream::WriteVarintXXX方法。
第一个方法可以重写为:
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
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
https://stackoverflow.com/questions/5858646
复制相似问题