Разработка под Apple iOS

индекс
167,94

Оптимизация приложений (Iphone armv6)

    Совсем недавно стукнул год, с тех пор как на просторах AppleStore появилось наше первое приложение. По началу было довольно сложно во всем разобраться. Особенно если учесть, что до этого я разработкой приложений под MacOS не занимался. За этот год много чего было написано. Приложения, которые мы написали, я к сожалению назвать не могу (не помню всех, да и руководство не одобряет такие вещи), но о нескольких способах оптимизации приложений под данную платформу я могу вам смело рассказать.
    Где-то пол года (или даже больше) назад пришлось мне писать приложение основной задачей которого была обработка звука. Для этого был написан свой несложный движок, который все это делал. Приложение было выпущено в свет и постепенно этот движок начал часто использоваться в других приложениях подобного рода. Но вот недавно началась разработка 2-й версии этой программы. Требования возросли, а ресурсы стареньких ифонов не изменились. Вот тут и пришлось поискать пути улучшения уже написанного кода.


Настройки компилятора (thumb)



    Первое что приходит в голову — попытаться выжать все что умеет компилятор. Пожалуй самый главный параметр, который тут можно поменять — компиляция приложения под режим thumb. Если оный режим включен — то для выполнения наших задач будет использоваться сокращенный набор команд. Этот набор команд будет кодироваться более компактным кодом, но и использовать все ресурсы процессора мы не сможем. В частности использовать VFP напрямую не получится. В местах где мы проводим операции над числами с плавающей точкой можно встретить код наподобие такого:

double prevTime=CFAbsoluteTimeGetCurrent();
{
  ...
}
double nextTime=CFAbsoluteTimeGetCurrent();
double dt = nextTime-prevTime;
printf("dt=%f",dt);


* This source code was highlighted with Source Code Highlighter.


    После компиляции это будет выглядеть примерно так:

blx  L_CFAbsoluteTimeGetCurrent$stub
mov  r5, r1
blx  L_CFAbsoluteTimeGetCurrent$stub
mov  r3, r5
mov  r2, r4
blx  L___subdf3vfp$stub
ldr  r6, L7
mov  r2, r1
mov  r1, r0
mov  r0, r6
blx  L_printf$stub


* This source code was highlighted with Source Code Highlighter.


    Не в режиме thumb код будет таким:

bl  L_CFAbsoluteTimeGetCurrent$stub
fmdrr  d8, r0, r1
bl  L_CFAbsoluteTimeGetCurrent$stub
fmdrr  d6, r0, r1
ldr  r0, L7
fsubd  d7, d6, d8
fmrrd  r1, r2, d7
bl  L_printf$stub


* This source code was highlighted with Source Code Highlighter.


    Как видим разница довольно существенная. Отсутствует лишний вызов функции и все операции с плавающей точкой происходят на месте, а не где-то далеко и не у нас. Наверно у вас возникнет вопрос а быстрее ли это работает? Ответ естественно да, работает это быстрее. Хотя если ваша программа не проводит тяжелые расчеты — то и так сойдет. Как плюс режиму thumb — более компактый код, а значит теоретически программа будет загружаться быстрее.
    Кстати в XCode tools можно задавать каждому файлу персональные параметры, и режим thumb можно выключить (или наоборот включить) только для отдельных фрагментов проекта, что довольно удобно.

Оптимизация алгоритма


    Следующй этап для ускорения расчетов — выкинуть как можно больше операций с плавающей точкой. Вместо этого преобразовывать наши числа в целые, умноженные на нейкий коэфффициент. Естественно коэффициент лучше выбирать кратным степени 2-йки, чтобы было потом удобно получить нужные данные.
    Ну вот мы заставили компилятор использовать все ресурсы процессора в местах где это важно, избавились по возможности от операций с плавающей точкой. Теперь давайте глянем на спеку по ArmV6 (например тут). Если внимательно почитать описания функций то можно там увидеть очень много интересных команд (многие из них тоже не доступны в режиме thumb).
    Например есть у вас задача сделать простой ФНЧ или ФВЧ. Алгоритм в итоге сводится к расчету вот такой формулы:
    tmp = b0*in0+b1*in1+b2*in2 -a1*out1-a2*out2;

* This source code was highlighted with Source Code Highlighter.

(b0,b1,b2,a1,a2 — это константы при заданной частоте отсечки)

    А теперь загляните в описание команды «smlad». Эта команда делат умножение 2-х 16-ти битных чисел, суммирует результаты и указанный вами регистр. Формулой это будет выглядеть так (в квадратных скобках указаны биты):

    result[0:31] = a[0:15]*b[0:15] + a[16:31]*b[16:31] + с[0:31]

* This source code was highlighted with Source Code Highlighter.


    Т.е. сам расчет нашей формулы можно сделать в 3 операции. Осталось только решить вопрос как же использовать эту функцию. Благо опыта с ассемблером у меня много еще с времен Доси, а в gcc просто замечательно работаю вставки написанные на ассеблере. Вобщем напишем функцию, которая будет использовать данную команду:

inline volatile int SignedMultiplyAccDual(int32_t x, int32_t y, int32_t addVal)
{
  register int32_t result;
  asm volatile("smlad %0, %1, %2, %3"
         : "=r"(result)
         : "r"(x), "r"(y), "r"(addVal)
        );
  return result;
}


* This source code was highlighted with Source Code Highlighter.


    Кстати для удобства можно сделать версию функции для симулятора. А то тестить будет не удобно. У меня это получилось вот так:

#if defined __arm__
inline volatile int SignedMultiplyAccDual(int32_t x, int32_t y, int32_t addVal)
{
  register int32_t result;
  asm volatile("smlad %0, %1, %2, %3"
         : "=r"(result)
         : "r"(x), "r"(y), "r"(addVal)
         );
  return result;
}

inline volatile int SignedMultiplyAcc(int32_t x, int32_t y, int32_t addVal)
{
  register int32_t result;
  asm volatile("mla %0, %1, %2, %3"
         : "=r"(result)
         : "r"(x), "r"(y), "r"(addVal)
          );
  return result;
}

#else

inline volatile int SignedMultiplyAcc(int32_t x, int32_t y, int32_t addVal)
{
  register int32_t result;
  result = x*y+addVal;
  return result;
}

inline volatile int SignedMultiplyAccDual(int32_t x, int32_t y, int32_t addVal)
{
  register int32_t result;
  result = int16_t(x & 0x0000FFFF) * int16_t(y & 0x0000FFFF);
  result += int16_t(x >> 16) * int16_t(y >> 16);  
  result += addVal;
  return result;
}
#endif  


* This source code was highlighted with Source Code Highlighter.


    В итоге расчет нашей формулы будет выглядеть так:
  tmp = fParamsHigh[0]*fValsHigh[0];
  tmp = SignedMultiplyAccDual(*(int32_t *)&fParamsHigh[1],*(int32_t *)&fValsHigh[1],tmp);
  tmp = SignedMultiplyAccDual(*(int32_t *)&fParamsHigh[3],*(int32_t *)&fValsHigh[3],tmp);
  tmp = tmp >> PARAMS_SHL_VAL; 


* This source code was highlighted with Source Code Highlighter.


    Заглянем в дизасм:
ldrh  r3, [r4, #196]
ldrh  r0, [r4, #206]
ldr  r2, [r4, #208]
smulbb  r3, r3, r0
smlad r3, r1, r2, r3
ldr  r1, [r4, #202]
ldr  r2, [r4, #212]
smlad r3, r1, r2, r3
mov  r3, r3, asr #10


* This source code was highlighted with Source Code Highlighter.


    Все красиво и понятно. Как сказал один мой знакомый «загрузил. выполнил. загрузил. выплюнул». То что было до этого лучше не смотреть. Там был просто ужас. Итак у меня в программе было 2 канала, на которых был эффект задержки. На каждый такой эффект нужно было 2 фильтра (один ФНЧ, другой ФВЧ). Итого 4 фильтра. После оптимизации, глянув в Instruments загрузку процессора — видим что вместо ~45% программа кушает ~35% процессорного времени. Довольно не плохой результат :)
    Кстати, почитав документацию я с удивлением обнаружил отсутствие целочисленного деления. В итоге немного модифицировав алгоритм линейной интерполяции (используется при ресемплинге на всех активных каналах) загрузка вообще упала до ~30% :)
  Вот так пару простых и довольно очевидных оптимизаций снизили загрузку процессора примерно на 1/3.
P.S. Тестировалось все на iPhone 3g.
+18
28 октября 2009, 19:53
21

комментарии (11)

0
destman #
Неплохо было бы перенести сие добро в тематический раздел. Заранее спасибо тем кто поможет :)
0
rgaliull #
я не программер, и но с удовольствием почитал. Умеют же некоторые люди сложные вещи понятно объяснить :)
0
slatvick #
Прямо какое то исследование.
Спасибо, хороший анализ. Надеюсь не понадобится. ;)
+1
destman #
Насчет «исследование» это наверно влияние университета :)
А вообще эплы сделали замечательную среду разработки под iPhone. В этом конкретном случае мне было достаточно открыть инструменты (CPU Sampler) да глянуть что у меня больше всего грузит процессор…
З.Ы. Вообще armv6 очень хорош для обработки звука.
0
slatvick #
Не сильно понимаю в различиях между процессарами. Каким образом он подходит больше остальных? Ну или в чем удобсвта?
+1
destman #
Набор комманд. У armv6 (по сравнению с armv5) есть целый класс комманд, который сильно ускоряет обработку аудио данных. Яркий пример (то чего в этой статье я забыл упомянуть) — сложение с насыщением.
Вот такой код сделает сумму 2-х пар 16-ти битных чисел.
inline volatile int SignedSaturatedAddDual(SInt32 x, SInt32 y)
{
    register int32_t result;
    asm volatile("qadd16 %0, %1, %2"
        : "=r"(result)
        : "r"(x), "r"(y)
    );
    return result;
}

Причем если результат сложения не будет вмещатся в 16 бит — будет происходить насыщение а не переполнение. Т.е. не надо делать 32 битное сложение с 2-мя последующими проверками (или еще какие подобные выкрунтасы).
+1
beeruser #
>> Кстати, почитав документацию я с удивлением обнаружил отсутствие целочисленного деления.

Почему с удивлением? Деление противоречит RISC идеологии (уникальная длинная инструкция блокирующая конвеер). Редко когда оно действительно нужно.
В Itanium вон даже целочисленное умножение делается через FPU =)
0
destman #
С удивлением — т.к. это для меня была новость :)
Асмом я занимался на x86 процах (286-686 и SSE2 немного поковырял). Ну и немного с программируемыми микросхемами поколдовал.
0
Kochegarov #
Хотелось бы знать названия приложений, авторы которых уделяют такое внимание оптимизации. Это же потенциальное конкурентное преимущество, не видное пользователю, стоящему перед проблемой (кстати, пишется с одним «м» :) выбора. А мы, пользователи, ведь можем и поддержать трудовым баксом. :)
0
destman #
Хех. В мои задачи входило создание приложения. Продет его совсем другой человек в японии (аутсорсинг или как это зовется по современному). Были мысли чтонить сове на основе этого движка написать — да пока не слишком времени хватает (кроме постоянной работы помогаю своему другу писать приложение «Galileo» (на AppleStore уже есть кстати) ).
0
Kochegarov #
А, понятно.
На Galileo посмотрел. Жаль, для России это пока неактуально. Приходится пользоваться самодельными кэшами Google Maps.
Кстати, если решитеcь выводить на App Store свои разработки — можете обращаться. :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.