Использование Renderscript на android-устройствах с процессорами Intel®

draft

Данный пост изначально был размещен в корпоративном блоге Intel на площадке Habrahabr.ru.

В статье я хотел бы дать краткое описание работы технологии 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 может стать помощником повышения производительности приложений, которые при этом останутся аппаратно-независимыми.

Para obter mais informações sobre otimizações de compiladores, consulte Aviso sobre otimizações.