9 декабря 2014 г.
Недавно была выпущена новая 64-разрядная ОС Android L. В этой статье мы продемонстрируем, насколько Intel® Threading Building Blocks (Intel® TBB) упрощает разработку приложений с поддержкой параллельных вычислений для 64-разрядной ОС Android L. Intel TBB — это межплатформенная библиотека шаблонов для создания программ, поддерживающих параллельные вычисления. Она создает и синхронизирует потоки данных, что позволяет скрыть особенности архитектуры и работать на более высоком уровне абстракции. Библиотека Intel TBB совместима с любыми архитектурами. При разработке приложений Android рекомендуется использовать версию 4.3 или более позднюю.
Сборка библиотеки Intel® TBB
Для ОС Linux*:
- Загрузить Intel TBB можно по адресу https://www.threadingbuildingblocks.org/. В этой статье представлен исходный код последнего стабильного выпуска (версия 4.3, обновление 1).
- Добавьте компонент NDK в переменную среды PATH.
Для ОС Windows:- $ SET PATH=%PATH%; <path_to_ndk>
- $ export PATH=$PATH: <path_to_ndk>
- Распакуйте Intel TBB и перейдите к папке src.
$ cd <tbb_sources>/src/ - Соберите библиотеки Intel TBB для Android, выполнив следующую команду: $ <path_to_ndk>/ndk-build –C <tbb_sources>/src/ arch=intel64 compiler=gcc target=android clean tbb tbbmalloc -j
- Процесс сборки библиотеки завершен. Папка build (<tbb_sources>/build/) содержит подпапки с библиотеками libgnustl_shared.so, libtbbmalloc_proxy.so, libtbbmalloc.so и libtbb.so. Для приложения, описываемого в этой статье, потребуются библиотеки libtbb.so и libgnustl_shared.so.
Настройка эмулятора
В среде Eclipse* нажмите Window -> Android Virtual Device Manager. В открывшемся окне нажмите Create и выберите параметры, как показано на снимке экрана ниже.
Нажмите кнопку ОК. Чтобы проверить работоспособность нового виртуального устройства, выберите его в списке и нажмите Start… В новом окне нажмите кнопку Launch, чтобы запустить эмулятор.
Создание приложения
В качестве примера будет создано приложение для перемножения матриц (3x2) и (2x3). Рассмотрим все этапы процесса.
Создайте новое приложение Android.
Добавьте поддержку нативного кода, щелкнув правой кнопкой мыши в обозревателе проектов и выбрав последовательно Android Tools -> Add Native Support…
В следующем окне введите название библиотеки, используемой для проекта и нажмите кнопку Finish.
В папке проекта была создана новая папка jni. Необходимо добавить библиотеки Intel TBB в MK-файл для этого проекта — Android.mk.
LOCAL_PATH := $(call my-dir) TBB_PATH := <tbb_sources> TBB_BUILD_PATH := /build/linux_intel64_gcc_android_cc4.9_NDKr10b_version_android-L_release include $(CLEAR_VARS) LOCAL_MODULE := TBBMatrixMult LOCAL_SRC_FILES := TBBMatrixMult.cpp LOCAL_CFLAGS += -DTBB_USE_GCC_BUILTINS -std=c++11 -fexceptions -Wdeprecated-declarations -I$(TBB_PATH)/include -I$(TBB_PATH)$(TBB_BUILD_PATH) LOCAL_LDLIBS := -llog -ltbb -L./ -L$(TBB_PATH)$(TBB_BUILD_PATH) LOCAL_SHARED_LIBRARIES += libtbb include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libtbb LOCAL_SRC_FILES := $(TBB_PATH)$(TBB_BUILD_PATH)/libtbb.so include $(PREBUILT_SHARED_LIBRARY)
Создайте в папке jni файл Application.mk и добавьте в него следующие строки.
APP_ABI := x86_64 APP_GNUSTL_FORCE_CPP_FEATURES := exceptions rtti APP_STL := gnustl_shared
Файл Application.mk необходим для указания того, какие встроенные модули (например, статические или общие библиотеки) требуются для работы приложения. Строка APP_ABI := x86_64 задает целевую архитектуру. Это приложение предназначено для 64-разрядных архитектур.
Попробуем запустить приложение. Появление основного экрана приложения означает, что библиотека Intel TBB подключена успешно и теперь можно приступать к разработке программы для умножения матриц.
Вставьте в файл res/layout/activity_main.xmlследующий фрагмент кода.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity"><ScrollView android:id="@+id/scrollView1" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentTop="true" android:layout_centerHorizontal="true"><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"><TextView android:id="@+id/titleA" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@string/a" android:textAppearance="?android:attr/textAppearanceSmall" /><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><EditText android:id="@+id/a00" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/a01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned"><requestFocus /></EditText></LinearLayout><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><EditText android:id="@+id/a10" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/a11" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /></LinearLayout><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><EditText android:id="@+id/a20" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/a21" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /></LinearLayout><TextView android:id="@+id/titleB" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:text="@string/b" android:textAppearance="?android:attr/textAppearanceSmall" /><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><EditText android:id="@+id/b00" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/b01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/b02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /></LinearLayout><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><EditText android:id="@+id/b10" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/b11" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /><EditText android:id="@+id/b12" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:ems="3" android:inputType="numberDecimal|numberSigned" /></LinearLayout><Button android:id="@+id/button" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="17dp" android:text="@string/button" /><TextView android:id="@+id/titleC" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/c" android:textAppearance="?android:attr/textAppearanceSmall" /><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><TextView android:id="@+id/c00" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /><TextView android:id="@+id/c01" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /><TextView android:id="@+id/c02" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /></LinearLayout><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><TextView android:id="@+id/c10" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /><TextView android:id="@+id/c11" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /><TextView android:id="@+id/c12" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /></LinearLayout><LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"><TextView android:id="@+id/c20" android:layout_width="70dp" android:layout_height="wrap_content" android:layout_marginBottom="40dp" android:textAppearance="?android:attr/textAppearanceSmall" /><TextView android:id="@+id/c21" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /><TextView android:id="@+id/c22" android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" /></LinearLayout></LinearLayout></ScrollView></RelativeLayout>
В файл res/values/strings.xml необходимо добавить следующие строки кода.
<?xml version="1.0" encoding="utf-8"?><resources><string name="app_name">TBBMatrixMult</string><string name="action_settings">Settings</string><string name="a">A:</string><string name="b">B:</string><string name="c">C:</string><string name="button">A x B = C</string></resources>
После этого Main Activity будет выглядеть следующим образом.
Далее необходимо реализовать в этом действии интерфейс Java*. Добавьте в файл src/intel.example.tppmatrixmult/MainActivity.javaследующий фрагмент кода:
package intel.example.tbbmatrixmult; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private native double [][] onClickCalc(double [] a, double [] b, int aRow, int aCol); private EditText matrixA [][] = new EditText[3][2]; private EditText matrixB [][] = new EditText[2][3]; private TextView matrixC [][] = new TextView[3][3]; private TextView titleC; private Button mult; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initA(); initB(); initC(); mult = (Button) findViewById(R.id.button); mult.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getBaseContext(), "Unknown error!", Toast.LENGTH_SHORT).show(); double [][] a = getA(); double [][] b = getB(); if (a == null || b == null) { Toast.makeText(getBaseContext(), "Unknown error!", Toast.LENGTH_SHORT).show(); return; } double [][] c = onClickCalc(matrixToArray(a), matrixToArray(b), 3, 2); setC(c); setVisibleC(true); } }); setVisibleC(false); System.loadLibrary("tbb"); System.loadLibrary("TBBMatrixMult"); } private double [] matrixToArray(double [][] matrix) { double [] array = new double[matrix.length * matrix[0].length]; for (int row = 0; row < matrix.length; ++row) { for (int col = 0; col < matrix[row].length; ++col) { array[row * matrix[row].length + col] = matrix[row][col]; } } return array; } private void initA() { matrixA[0][0] = (EditText) findViewById(R.id.a00); matrixA[0][1] = (EditText) findViewById(R.id.a01); matrixA[1][0] = (EditText) findViewById(R.id.a10); matrixA[1][1] = (EditText) findViewById(R.id.a11); matrixA[2][0] = (EditText) findViewById(R.id.a20); matrixA[2][1] = (EditText) findViewById(R.id.a21); } private void initB() { matrixB[0][0] = (EditText) findViewById(R.id.b00); matrixB[0][1] = (EditText) findViewById(R.id.b01); matrixB[0][2] = (EditText) findViewById(R.id.b02); matrixB[1][0] = (EditText) findViewById(R.id.b10); matrixB[1][1] = (EditText) findViewById(R.id.b11); matrixB[1][2] = (EditText) findViewById(R.id.b12); } private void initC() { titleC = (TextView) findViewById(R.id.titleC); matrixC[0][0] = (TextView) findViewById(R.id.c00); matrixC[0][1] = (TextView) findViewById(R.id.c01); matrixC[0][2] = (TextView) findViewById(R.id.c02); matrixC[1][0] = (TextView) findViewById(R.id.c10); matrixC[1][1] = (TextView) findViewById(R.id.c11); matrixC[1][2] = (TextView) findViewById(R.id.c12); matrixC[2][0] = (TextView) findViewById(R.id.c20); matrixC[2][1] = (TextView) findViewById(R.id.c21); matrixC[2][2] = (TextView) findViewById(R.id.c22); } private double[][] getA() { double [][] ret = new double [matrixA.length][]; for (int i = 0; i < matrixA.length; ++i) { ret[i] = new double [matrixA[i].length]; for (int j = 0; j < matrixA[i].length; ++j) { if (matrixA[i][j].getText().toString().length() == 0) { Toast.makeText(getBaseContext(), "Error! One field is empty!", Toast.LENGTH_SHORT).show(); return null; } ret[i][j] = Double.parseDouble(matrixA[i][j].getText().toString()); } } return ret; } private double[][] getB() { double [][] ret = new double [matrixB.length][]; for (int i = 0; i < matrixB.length; ++i) { ret[i] = new double [matrixB[i].length]; for (int j = 0; j < matrixB[i].length; ++j) { if (matrixB[i][j].getText().toString().length() == 0) { Toast.makeText(getBaseContext(), "Error! One field is empty!", Toast.LENGTH_SHORT).show(); return null; } ret[i][j] = Double.parseDouble(matrixB[i][j].getText().toString()); } } return ret; } private void setC(double [][] cVal) { if (matrixC.length != cVal.length) { Toast.makeText(getBaseContext(), "Unknown error!", Toast.LENGTH_SHORT).show(); return; } for (int i = 0; i < matrixC.length; ++i) { if (matrixC[i].length != cVal[i].length) { Toast.makeText(getBaseContext(), "Unknown error!", Toast.LENGTH_SHORT).show(); return; } for (int j = 0; j < matrixC[i].length; ++j) { matrixC[i][j].setText(String.valueOf(cVal[i][j])); } } } private void setVisibleC(boolean cond) { if (cond == true) titleC.setVisibility(View.VISIBLE); else titleC.setVisibility(View.GONE); for (int i = 0; i < matrixC.length; ++i) { for (int j = 0; j < matrixC[i].length; ++j) { if (cond == true) { matrixC[i][j].setVisibility(View.VISIBLE); } else { matrixC[i][j].setVisibility(View.GONE); } } } } } А теперь мы используем конструкции Intel TBB для параллельного кода. Следующий фрагмент кода представляет собой реализацию перемножения матриц в jni/TBBMatrixMult.cpp. Код выполняется в один поток. double ** arrayToMatrix(double * array, int row, int col) { double ** matrix = new double * [row]; for (int i = 0; i < row; ++i) { matrix[i] = new double [col]; for (int j = 0; j < col; ++j) { matrix[i][j] = array[i * col + j]; } } return matrix; } extern "C" JNIEXPORT jobjectArray JNICALL Java_intel_example_tbbmatrixmult_MainActivity_onClickCalc( JNIEnv *env, jobject obj, jdoubleArray aMatrix, jdoubleArray bMatrix, jint aRow, jint aCol) { double * aArray = (*env).GetDoubleArrayElements(aMatrix, 0); double ** a = arrayToMatrix(aArray, aRow, aCol); double * bArray = (*env).GetDoubleArrayElements(bMatrix, 0); double ** b = arrayToMatrix(bArray, aCol, aRow); double ** c = new double * [aRow]; for (int i = 0 ; i < 3; ++i) { c[i] = new double [aRow]; } for (int row = 0; row < aRow, ++row) { for (int col = 0; col < aRow; ++col) { c[row][col] = 0; for(int k = 0; k < aCol; ++k) { c[row][col] += a[row][k] * b[k][col]; } } }; jclass doubleArrayClass = (*env).FindClass("[D"); jobjectArray cMatrix = (*env).NewObjectArray((jsize) aRow, doubleArrayClass, 0); for (int i = 0; i < aRow; i++) { jdoubleArray doubleArray = (*env).NewDoubleArray(aRow); (*env).SetDoubleArrayRegion(doubleArray, (jsize) 0, (jsize) aRow, c[i]); (*env).SetObjectArrayElement(cMatrix, (jsize) i, doubleArray); (*env).DeleteLocalRef(doubleArray); } return cMatrix; } Для добавления параллелизма в этот код мы должны включить заголовок Intel TBB: #include "tbb/tbb.h"и изменить цикл tbb::parallel_for (0, aRow, 1, [=](int row) { for (int col = 0; col < aRow; ++col) { c[row][col] = 0; for(int k = 0; k < aCol; ++k) { c[row][col] += a[row][k] * b[k][col]; } } });
А теперь мы используем конструкции Intel TBB для параллельного кода. Следующий фрагмент кода представляет собой реализацию перемножения матриц в jni/TBBMatrixMult.cpp. Код выполняется в один поток.
#include <jni.h> double ** arrayToMatrix(double * array, int row, int col) { double ** matrix = new double * [row]; for (int i = 0; i < row; ++i) { matrix[i] = new double [col]; for (int j = 0; j < col; ++j) { matrix[i][j] = array[i * col + j]; } } return matrix; } extern "C" JNIEXPORT jobjectArray JNICALL Java_intel_example_tbbmatrixmult_MainActivity_onClickCalc( JNIEnv *env, jobject obj, jdoubleArray aMatrix, jdoubleArray bMatrix, jint aRow, jint aCol) { double * aArray = (*env).GetDoubleArrayElements(aMatrix, 0); double ** a = arrayToMatrix(aArray, aRow, aCol); double * bArray = (*env).GetDoubleArrayElements(bMatrix, 0); double ** b = arrayToMatrix(bArray, aCol, aRow); double ** c = new double * [aRow]; for (int i = 0 ; i < 3; ++i) { c[i] = new double [aRow]; } for (int row = 0; row < aRow, ++row) { for (int col = 0; col < aRow; ++col) { c[row][col] = 0; for(int k = 0; k < aCol; ++k) { c[row][col] += a[row][k] * b[k][col]; } } }; jclass doubleArrayClass = (*env).FindClass("[D"); jobjectArray cMatrix = (*env).NewObjectArray((jsize) aRow, doubleArrayClass, 0); for (int i = 0; i < aRow; i++) { jdoubleArray doubleArray = (*env).NewDoubleArray(aRow); (*env).SetDoubleArrayRegion(doubleArray, (jsize) 0, (jsize) aRow, c[i]); (*env).SetObjectArrayElement(cMatrix, (jsize) i, doubleArray); (*env).DeleteLocalRef(doubleArray); } return cMatrix; }
Для добавления параллелизма в этот код мы должны включить заголовок Intel TBB: #include "tbb/tbb.h"и изменить цикл
tbb::parallel_for (0, aRow, 1, [=](int row) { for (int col = 0; col < aRow; ++col) { c[row][col] = 0; for(int k = 0; k < aCol; ++k) { c[row][col] += a[row][k] * b[k][col]; } } });
Результат работы приложения представлен на снимке экрана ниже.