В статье я хотел бы дать краткое описание работы технологии Renderscript внутри Android, сравнить ее производительность с Dalvik на конкретном примере аndroid-устройства с процессором Intel и рассмотреть небольшой прием оптимизации renderscript.
Renderscript – это API, который включает функции для 2D/3D рендеринга и математических вычислений с высокой производительностью. Он позволяет описать какую-либо задачу с однотипными независимыми вычислениями над большим объемом данных и разбить ее на однородные подзадачи, которые могут быть выполнены быстро и параллельно на многоядерных Android-платформах.
Такая технология может повысить производительность ряда dalvik приложений, связанных с обработкой изображений, распознаванием образов, физическим моделированием, клеточно-автоматной моделью и др., которые, в свою очередь, не потеряют аппаратной независимости.
1. Технология Renderscript внутри Android
Приведу краткий обзор механизма работы технологии Renderscript внутри Android, ее достоинства и недостатки.
1.1 Renderscript offline-компиляция
Renderscript начал поддерживаться в Honeycomb/Android 3.0 (API 11). А именно, в Android SDK в директории platform-tools появился llvm-rs-cc (offline compiler) для компиляции renderscript (*.rs файл) в байт-код (*.bc файл) и генерации java классов объектов (*.java файлы) для структур, глобальных переменных внутри renderscript и самого renderscript. В основе llvm-rs-cc лежит Clang компилятор с небольшими изменениями под Android, который представляет собой front-end для LLVM компилятора.
1.2 Renderscript run-time компиляция
В Android появился framework, построенный на базе LLVM back-end, который отвечает за run-time компиляцию байт-кода, линковку с нужными библиотеками, запуск и контроль выполнения renderscript. Этот framework состоит из следующих частей: libbcc занимается инициализацией LLVM контекста в соответствии с указанными прагмами и другими метаданными в байт-коде, компиляцией байт-кода и динамической линковкой с нужными библиотеками из libRS; libRS содержит реализацию библиотек (math, time, drawing, ref-counting,…), структур и типов данных (Script, Type, Element, Allocation, Mesh, various matrices,…).
Преимущества:
- Аппаратно-независимое приложение получается за счет того, что renderscript байт-код, входящий в apk файл, в run-time будет скомпилирован в машинный код того аппаратно-вычислительного модуля (CPU) платформы, где будет запущен;
- Быстрота исполнения достигается благодаря распараллеливанию вычислений, run-time компиляторной оптимизации и нативному исполнению кода.
Недостатки:
- Отсутствие подробной документации для работы с renderscript усложняет разработку приложений. Все ограничивается коротким описанием предлагаемого renderscript run-time API, представленного здесь;
- Отсутствие поддержки исполнения renderscript потоков на GPU, DSP. Возможны проблемы с run-time балансировкой потоков в гетерогенном запуске, управлением общей памятью.
2. Dalvik vs. Renderscript в монохромной обработке изображения
Рассмотрим dalvik-функцию Dalvik_MonoChromeFilter (преобразование цветного RGB-изображения в черно-белое (монохромное) ):
private void Dalvik_MonoChromeFilter() {
float MonoMult[] = {0.299f, 0.587f, 0.114f};
int mInPixels[] = new int[mBitmapIn.getHeight() * mBitmapIn.getWidth()];
int mOutPixels[] = new int[mBitmapOut.getHeight() * mBitmapOut.getWidth()];
mBitmapIn.getPixels(mInPixels, 0, mBitmapIn.getWidth(), 0, 0,
mBitmapIn.getWidth(), mBitmapIn.getHeight());
for(int i = 0;i < mInPixels.length;i++) {
float r = (float)(mInPixels[i] & 0xff);
float g = (float)((mInPixels[i] >> 8) & 0xff);
float b = (float)((mInPixels[i] >> 16) & 0xff);
int mono = (int)(r * MonoMult[0] + g * MonoMult[1] + b * MonoMult[2]);
mOutPixels[i] = mono + (mono << 8) + (mono << 16) + (mInPixels[i] & 0xff000000);
}
mBitmapOut.setPixels(mOutPixels, 0, mBitmapOut.getWidth(), 0, 0,
mBitmapOut.getWidth(), mBitmapOut.getHeight());
}
Что можно сказать? Простой цикл с независимыми итерациями, «перемалывающий» кучу пикселов. Посмотрим, как быстро он работает!
Для эксперимента возьмем МегаФон Mint на Intel® Atom™ Z2460 1.6GHz с Android ICS 4.0.4 и 600x1024 картинку с лего-роботом, несущим новогодние подарки.
Замеры затраченного времени на обработку будем делать по следующей схеме:
private long startnow;
private long endnow;
startnow = android.os.SystemClock.uptimeMillis();
Dalvik_MonoChromeFilter();
endnow = android.os.SystemClock.uptimeMillis();
Log.d("Timing", "Exеcution time: "+(endnow-startnow)+" ms");
Сообщение с тегом «Timing» можно получить с помощью ADB. Сделаем десяток замеров, перед каждым из которых сделаем перезагрузку устройства и убедимся, что разброс результатов измерений небольшой.
Время обработки изображения dalvik-реализацией составило 353 мсек.
Замечание: используя средства многопоточности (к примеру, класс AsyncTask для описания задач, выполняющихся в отдельных потоках), в лучшем случае можно выжать двухкратное ускорение, в силу наличия двух логических ядер на Intel Atom Z2460 1.6GHz.
Теперь рассмотрим renderscript-реализацию RS_MonoChromeFilter того же самого фильтра:
//mono.rs
//or our small renderscript
#pragma version(1)
#pragma rs java_package_name(com.example.hellocompute)
//multipliers to convert a RGB colors to black and white
const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
void root(const uchar4 *v_in, uchar4 *v_out) {
//unpack a color to a float4
float4 f4 = rsUnpackColor8888(*v_in);
//take the dot product of the color and the multiplier
float3 mono = dot(f4.rgb, gMonoMult);
//repack the float to a color
*v_out = rsPackColorTo8888(mono);
}
private RenderScript mRS;
private Allocation mInAllocation;
private Allocation mOutAllocation;
private ScriptC_mono mScript;
…
private void RS_MonoChromeFilter() {
mRS = RenderScript.create(this);/*создание Renderscript-контекста*/
mInAllocation = Allocation.createFromBitmap(mRS, mBitmapIn,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);/*выделение и инициализация общей памяти для dalvik и renderscript контекстов */
mOutAllocation = Allocation.createTyped(mRS, mInAllocation.getType());
mScript = new ScriptC_mono(mRS, getResources(), R.raw.mono);/*создание и
привязка renderscript к renderscript-контексту */
mScript.forEach_root(mInAllocation, mOutAllocation);/*вызываем renderscript-функцию root c SMP параллелизмом в 2 потока */
mOutAllocation.copyTo(mBitmapOut);
}
Замечание: производительность реализации будем оценивать как для dalvik.
Время обработки того же изображения renderscript-реализацией составило 112 мсек.
Получили выигрыш в производительности равный 3.2x (сравнение времени работы dalvik и renderscript: 353/112 = 3,2).
Замечание: время работы renderscript-реализации включает создание renderscript-контекста, выделение и инициализацию необходимой памяти, создание и привязку renderscript к контексту и работу функции root в mono.rs.
Замечание: Критичным местом для разработчиков мобильных приложений является размер получаемого apk файла. В этой реализации размер apk файла может увеличиться только на размер renderscript в байт-коде (*.bc файл) по сравнению с dalvik-реализацией. В моем случае размер dalvik-версии был равен 404KB, а размер renderscript-версии стал равен 406KB, из которых 2KB это renderscript байт-код (mono.bc).
3. Оптимизация renderscript
Текущую производительность renderscript можно повысить, отказавшись немного от точности арифметических операций с вещественными числами, что непринципиально для рассматриваемой задачи. Для этого в renderscript добавим прагму rs_fp_imprecise:
//mono.rs
//or our small renderscript
#pragma version(1)
#pragma rs java_package_name(com.example.hellocompute)
#pragma rs_fp_imprecise
//multipliers to convert a RGB colors to black and white
const static float3 gMonoMult = {0.299f, 0.587f, 0.114f};
void root(const uchar4 *v_in, uchar4 *v_out) {
//unpack a color to a float4
float4 f4 = rsUnpackColor8888(*v_in);
//take the dot product of the color and the multiplier
float3 mono = dot(f4.rgb, gMonoMult);
//repack the float to a color
*v_out = rsPackColorTo8888(mono);
}
Как следствие этого, получаем дополнительный 10%ный прирост производительности у renderscript-реализации: 112 мсек. -> 99 мсек.
Замечание: в результате получаем визуально такое же монохромное изображение без каких-либо артефактов и искажений.
Замечание: У renderscript отсутствует механизм явного управления run-time компиляторной оптимизацией в отличие от NDK, т.к. компиляторные ключи предварительно прописаны внутри Android для каждой платформы (x86, ARM,…).
4. Зависимость времени работы dalvik и renderscript реализаций от размеров изображений
Исследуем следующий вопрос: какая зависимость времени работы каждой реализации от размера обрабатываемого изображения? Для этого возьмем 4 изображения размерами 300x512, 600x1024 (наше исходное изображение с лего-роботом), 1200x1024, 1200x2048 и сделаем соответствующие замеры времени монохромной обработки изображений. Результаты представлены ниже на графике и в таблице.
300x512 | 600x1024 | 1200x1024 | 1200x2048 | |
dalvik | 85 | 353 | 744 | 1411 |
renderscript | 75 | 99 | 108 | 227 |
выигрыш | 1.13 | 3.56 | 6.8 | 6.2 |
Заметим линейную зависимость времени для dalvik относительно размера изображения в отличие от renderscript. Это отличие можно объяснить наличием времени инициализации renderscript-контекста.
Для изображений сравнительно малых размеров выигрыш несущественный, т.к. время инициализации renderscript-контекста около 50-60 мсек. Однако на изображениях средних размеров, которые чаще всего используются на android-устройствах, выигрыш составляет 4-6x.
Заключение
В статье были рассмотрены dalvik и renderscript реализации монохромной обработки изображений разных размеров. За счет распараллеливания, компиляторной оптимизации и нативного исполнения кода renderscript солидно превосходит dalvik в производительности для изображений средних размеров. Этим небольшим примером я старался показать, когда renderscript может стать помощником повышения производительности приложений, которые при этом останутся аппаратно-независимыми.
Автор: rkazantsev