Я занимаюсь оптимизацией шага кодирования библиотеки С++ под названием PackJPG
Я профилировал код с 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 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- > не обязательно равномерная мощность 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 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 (этот трюк не помог в первом цикле).
- Скопировать поля класса в локальные переменные.
К сожалению, следующие предложения не повышают производительность:
- Замена первого цикла while коммутатором с операторами goto.
- Использование арифметики с фиксированной точкой для деления (оно создало ошибки округления).
- Выполнение переключения в s- > шкале и выполнение бит-сдвигов вместо деления для четных степеней 2.
@example предположил, что это не деление, которое замедляется, но доступ к памяти для одного из операндов деления. Это кажется правильным. Согласно VTune, мы часто получаем пропуски кэша. Любые предложения о том, как исправить это?