【发布时间】:2014-05-24 09:09:53
【问题描述】:
我正在优化名为 PackJPG 的 C++ 库的编码步骤
我用 Intel VTune 分析了代码,发现当前的瓶颈是 PackJPG 使用的算术编码器中的以下函数:
void aricoder::encode( symbol* s )
{
// update steps, low count, high count
unsigned int delta_plus_one = ((chigh - clow) + 1);
cstep = delta_plus_one / s->scale;
chigh = clow + ( cstep * s->high_count ) - 1;
clow = clow + ( cstep * s->low_count );
// e3 scaling is performed for speed and to avoid underflows
// if both, low and high are either in the lower half or in the higher half
// one bit can be safely shifted out
while ( ( clow >= CODER_LIMIT050 ) || ( chigh < CODER_LIMIT050 ) ) {
if ( chigh < CODER_LIMIT050 ) { // this means both, high and low are below, and 0 can be safely shifted out
// write 0 bit
write_zero();
// shift out remaing e3 bits
write_nrbits_as_one();
}
else { // if the first wasn't the case, it's clow >= CODER_LIMIT050
// write 1 bit
write_one();
clow &= CODER_LIMIT050 - 1;
chigh &= CODER_LIMIT050 - 1;
// shift out remaing e3 bits
write_nrbits_as_zeros();
}
clow <<= 1;
chigh = (chigh << 1) | 1;
}
// e3 scaling, to make sure that theres enough space between low and high
while ( ( clow >= CODER_LIMIT025 ) && ( chigh < CODER_LIMIT075 ) ) {
++nrbits;
clow &= CODER_LIMIT025 - 1;
chigh ^= CODER_LIMIT025 + CODER_LIMIT050;
// clow -= CODER_LIMIT025;
// chigh -= CODER_LIMIT025;
clow <<= 1;
chigh = (chigh << 1) | 1;
}
}
这个函数似乎借鉴了一些想法:http://paginas.fe.up.pt/~vinhoza/itpa/bodden-07-arithmetic-TR.pdf。我已经设法在一定程度上优化了该功能(主要是通过加快位写入),但现在我被卡住了。
目前最大的瓶颈似乎是一开始的分裂。这张来自 VTune 的屏幕截图显示了它所花费的时间以及创建的程序集(右侧的蓝色程序集对应于左侧选择的源代码中的行)。
s->scale 不一定是 2 的偶数次方,因此不能用模运算代替除法。
代码是使用 MSVC(来自 Visual Studio 2013)编译的,具有以下设置:
/GS /Qpar- /GL /analyze- /W3 /Gy- /Zc:wchar_t /Zi /Gm- /Ox /sdl /Fd"Release\vc120.pdb" /fp:precise /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_USRDLL" /D "PACKJPG_EXPORTS" /D "_CRT_SECURE_NO_WARNINGS" /D "BUILD_DLL" /D "_WINDLL" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /arch:IA32 /Gd /Oy- /Oi /MT /Fa"Release\" /EHsc /nologo /Fo"Release\" /Ot /Fp"Release\PackJPG.pch"
关于如何进一步优化的任何想法?
更新 1 到目前为止,我已经尝试了所有建议,这是目前最快的版本:
void aricoder::encode( symbol* s )
{
unsigned int clow_copy = clow;
unsigned int chigh_copy = chigh;
// update steps, low count, high count
unsigned int delta_plus_one = ((chigh_copy - clow_copy) + 1);
unsigned register int cstep = delta_plus_one / s->scale;
chigh_copy = clow_copy + (cstep * s->high_count) - 1;
clow_copy = clow_copy + (cstep * s->low_count);
// e3 scaling is performed for speed and to avoid underflows
// if both, low and high are either in the lower half or in the higher half
// one bit can be safely shifted out
while ((clow_copy >= CODER_LIMIT050) || (chigh_copy < CODER_LIMIT050)) {
if (chigh_copy < CODER_LIMIT050) { // this means both, high and low are below, and 0 can be safely shifted out
// write 0 bit
write_zero();
// shift out remaing e3 bits
write_nrbits_as_one();
}
else { // if the first wasn't the case, it's clow >= CODER_LIMIT050
// write 1 bit
write_one();
clow_copy &= CODER_LIMIT050 - 1;
chigh_copy &= CODER_LIMIT050 - 1;
// shift out remaing e3 bits
write_nrbits_as_zeros();
}
clow_copy <<= 1;
chigh_copy = (chigh_copy << 1) | 1;
}
// e3 scaling, to make sure that theres enough space between low and high
while ((clow_copy >= CODER_LIMIT025) & (chigh_copy < CODER_LIMIT075)){
++nrbits;
clow_copy &= CODER_LIMIT025 - 1;
chigh_copy ^= CODER_LIMIT025 + CODER_LIMIT050;
// clow -= CODER_LIMIT025;
// chigh -= CODER_LIMIT025;
clow_copy <<= 1;
chigh_copy = (chigh_copy << 1) | 1;
}
clow = clow_copy;
chigh = chigh_copy;
}
这是使用此版本更新的 VTune 结果: 此新版本包括以下更改:
- 在最后一个 while 循环中使用 & 而不是 && 来避免一个分支(该技巧在第一个循环中没有帮助)。
- 将类字段复制到局部变量。
很遗憾,以下建议没有提高性能:
- 用带有 goto 语句的 switch 替换第一个 while 循环。
- 使用定点算法进行除法(它会产生舍入误差)。
- 在 s->scale 上进行切换并进行位移而不是除以 2 的偶数次幂。
@example 建议不是除法很慢,而是除法操作数之一的内存访问。这似乎是正确的。根据 VTune,我们经常在这里遇到缓存未命中。有关如何解决此问题的任何建议?
【问题讨论】:
-
这篇文章是关于 lz4 解码而不是算术编码但它可能会给你一些想法,无论如何它是一个很好的阅读:cbloomrants.blogspot.ca/2013/10/10-14-13-oodle-fast-lz4.html
-
在汇编输出中它说,将结果存储在内存中是该代码行中花费的时间,而不是实际的除法。还是我弄错了?可能是由页面错误引起的。也许你可以改变内存布局来解决这个问题。
-
您可以尝试在函数开始时将所有必要的类变量读入局部变量,并在最后存储修改后的变量。
-
那么查找表就这么多了。如果由于对除数的内存访问而不是除法本身,除法很慢,您可以做几件事。 1)您可以尝试将除数移动到一个将存储在寄存器中的值中,以便生成寄存器操作数除法,而不是在内存上操作的除法。然后你也许可以更容易地从 VTune 中看到哪个部分慢,尽管仍然很难说。也许更好的方法是用乘法代替除法,看看它是否仍然很慢,即使结果不正确。
-
2) 如果因为内存读取而变慢。
s指向的对象来自哪里?s指向的所有对象是否都分配在传染性内存中并按照它们在缓冲区中出现的顺序传递给编码?如果不能,你能做到吗?如果在这样的缓冲区上重复调用此函数,这将有助于优化您的内存读取情况,因为大多数时候此值将在缓存中。
标签: c++ performance optimization assembly x86