Аннотация
В этой статье подробно описываются способы разработки и портирования приложений Android для платформы Intel Atom, а также рассматриваются наиболее применяемые способы разработки приложений с помощью Android Native Development Kit (NDK) и оптимизации их производительности.
1. Классификация приложений Android
Приложения Android можно разделить на два типа, как показано на Рис. 1.
- Приложения Dalvik включают код Java* и используют только официальные API из пакета SDK, а также необходимые файлы ресурсов, например XML- и PNG-файлы. Такие приложения компилируются в APK-файлы.
- Приложения Android NDK, содержащие код Java и файлы ресурсов, а также исходный код на языке C/C++ и (иногда) код на ассемблере. Весь собственный код компилируется в библиотеку динамической компоновки (SO-файл), а затем вызывается кодом Java в основной программе с помощью механизма JNI.
Рисунок 1:Два типа приложений Android
2. Android Native Development Kit
2.1 Введение
Android Native Development Kit (NDK) — это мощное средство для разработки приложений Android, обладающее следующими возможностями:
- Создание важных с точки зрения производительности частей приложений на собственном коде. При использовании кода Java исходный код необходимо интерпретировать на машинный язык с помощью виртуальной машины. Напротив, встроенный код компилируется и оптимизируется в двоичный код непосредственно перед выполнением. Используя собственный код нужным образом, можно создавать высокопроизводительный код в составе приложений. например, для аппаратного кодирования и декодирования видео, обработки графики и вычислений.
- Использование существующего собственного кода. Код на языке C/C++ можно скомпилировать в динамическую библиотеку, которую код Java может вызвать с помощью механизма JNI.
2.2 Описание средств
При разработке можно использовать Intel® Hardware Execution Manager (Intel® HAXM) для повышения производительности эмулятора Android. HAXM — это ядро аппаратной виртуализации (гипервизор), основанное на технологии виртуализации Intel (Intel® VT) и ускоряющее эмуляцию приложений Android на основном компьютере. В сочетании с образами эмулятора Android x86, предоставляемыми корпорацией Intel, и официальным выпуском Android SDK Manager решение Intel HAXM обеспечивает более быструю эмуляцию Android в системах с поддержкой Intel VT. Дополнительные сведения о HAXM см. по адресу: http://software.intel.com.
2.3 Установка HAXM
Установить HAXM можно либо с помощью Android SDK Manager (рекомендуется), либо вручную, загрузив программу установки с веб-сайта Intel. Для автоматического обновления используйте установку с помощью Android SDK Manager, как показано на Рис. 2. [1]
Рисунок 2:Установка Intel HAXM с помощью Android SDK Manager
Также можно загрузить соответствующий установочный пакет с сайта http://www.intel.com/software/androidна базовую платформу, а затем следовать пошаговым инструкциям для установки.
2.3.1 Настройте HAXM
При запуске HAXM требуется системный образ Android x86 корпорации Intel. Загрузить системный образ можно с помощью Android SDK Manager или вручную с сайта Intel® Developer Zone.
После успешной установки образы эмулятора автоматически выполняются с помощью двоичного файла emulator-x86 в составе Android SDK. Работа эмулятора Android ускоряется с помощью Intel VT, что ускоряет процесс разработки.
3. Разработка и преобразование приложений NDK для архитектуры Intel Atom
3.1 Разработка приложений NDK Intel Atom
После успешной установки NDK уделите несколько минут ознакомлению с документами, находящимися в папке /docs/, особенно OVERVIEW.html и CPU-X86.html. Это полезно для понимания механизма NDK и принципов его использования.
Разработку приложений NDK можно разделить на 5 этапов, как показано на Рис. 3:
Рисунок 3:Процесс разработки приложений NDK
Для иллюстрации этих 5 этапов используется демонстрационное приложение hello-jni. Демонстрационное приложение находится в папке NDK Root\samples\hello-jni [5]. Демонстрационное приложение hello-jni — это простое приложение в составе NDK. Это приложение получает строку из собственного метода в общей библиотеке и использует эту строку в пользовательском интерфейсе приложения.
3.1.1. Создайте собственный код
Создайте новый проект Android и поместите собственный исходный код в папку /jni/. Содержимое проекта показано на рис. 4. Эта демонстрация включает простую функцию в собственном коде с именем Java_com_example_hellojni_HelloJni_stringFromJNI(). Как показано в исходном коде, эта функция возвращает строку из JNI.
Рисунок 4:Создание собственного кода
3.1.2 Создайте файл MakeFile Android.mk
Приложения NDK по умолчанию предназначены для платформы ARM. Для сборки приложений NDK для платформы Intel Atom необходимо добавить строку APP_ABI := x86 в файл MakeFile.
Рисунок 5:Создание файла MakeFile
3.1.3 Скомпилируйте собственный код
Соберите собственный код, выполнив сценарий ndk-build в папке проекта. Сценарий находится в папке NDK верхнего уровня. Результат показан на Рис. 6.
Рисунок 6:Скомпилированный собственный код
Средства сборки автоматически копируют очищенные общие библиотеки в нужное расположение в папке проекта приложения.
3.1.4 Вызов собственного кода из Java
После успешного развертывания общей библиотеки можно вызвать функцию со стороны Java. Код показан на рис. 7. В коде Java создается вызов собственной функции stringFromJNI(), которая загружает общую библиотеку с помощью System.loadlibrary().
Рисунок 7:Вызов собственного кода из Java
3.1.5 Отладка с помощью GDB
Для отладки приложения NDK с помощью GDB необходимо выполнение следующих условий:
- Приложение NDK собрано с помощью ndk-build
- Приложению NDK установлен атрибут debuggable в Android.manifest
- Приложение NDK выполняется под управлением Android 2.2 (или более поздней версии)
- Запущено только одно устройство назначения
- Папка, в которой находится adb, добавлена в PATH
Используйте команду ndk-gdb для отладки приложения. Можно либо задать точку останова, либо выполнять пошаговую отладку, чтобы отслеживать изменения значения переменной, как показано на Рис. 8.
Рисунок 8:Отладка приложения NDK с помощью GDB
3.2 Портирование существующих приложений NDK на устройства с Intel Atom
В этом разделе предполагается, что у нас есть приложение Android для платформы ARM, и перед его развертыванием на платформе Intel Atom требуется его преобразовать.
Процесс портирования приложений Android на платформу Intel Atom аналогичен процессу разработки. Действия показаны на Рис. 9.
Рисунок 9:Преобразование приложений Android для платформы Intel Atom
3.2.1 Преобразование приложений Dalvik
Приложения Dalvik могут выполняться на устройствах с процессорами Intel Atom напрямую. Пользовательский интерфейс необходимо адаптировать к устройству назначения. У устройств с экраном высокого разрешения, например у планшетов с экраном разрешением 1280*800 или выше, выделяемая по умолчанию память может не соответствовать требованиям приложения, из-за чего может оказаться невозможно запустить это приложение. Для устройств с экраном высокого разрешения рекомендуется увеличить объем выделяемой по умолчанию памяти.
3.2.2 Портирование приложений Android NDK
Портирование приложений NDK немного сложнее, чем для приложений Dalvik. Все приложения NDK можно разделить на три типа на основе следующих свойств в собственном коде:
- Приложение включает только код C/C++, не связанный с оборудованием
- Приложение использует стороннюю библиотеку динамической компоновки
- Приложение использует код ассемблера, тесно связанный с платформами архитектуры, отличной от IA
Только код C/C++, не связанный с оборудованием
- Перекомпилируйте код, чтобы приложение успешно выполнялось на платформе Intel Atom.
- Откройте проект NDK, найдите файл Android.mk, добавьте строку APP_ABI := armeabi armeabi-v7a x86в файл Android.mk и заново скомпилируйте код с помощью сценария ndk-build.
- Если файл Android.mk не найден, используйте команду ndk-build APP_ABI="armeabi armeabi-v7a x86"для сборки проекта.
- Заново упакуйте приложение для поддерживаемых платформ x86.
Если собственный код использует стороннюю библиотеку динамической компоновки, общую библиотеку необходимо перекомпилировать в версию x86 для платформы Intel Atom.
Если собственный код включает код ассемблера, тесно связанный с платформами архитектуры, отличной от IA, необходимо переписать код на ассемблере для IA или на C/C++.
4. Рекомендуемые способы разработки собственного кода
4.1 Принудительное выравнивание памяти
Из-за различий между архитектурами, платформами и компиляторами размер данных одних и тех же структур данных может различаться на разных платформах. Без принудительного выравнивания памяти возможны ошибки загрузки или несовпадение объема данных. [2]
В следующем примере кода показана разница в размере одной и той же структуре данных на разных платформах:
struct TestStruct {
int mVar1;
long long mVar2;
int mVar3;
};
Это простая структура с тремя переменными: mVar1, mVar2, и mVar3.
mVar1 — целочисленное значение, оно будет занимать 4 байта
mVar2 — длинное целое, оно будет занимать 8 байтbr /> mVar3 — также целочисленное и будет занимать 4 байта.
Сколько места потребуется на платформах ARM и Intel Atom?
Объем скомпилированных данных для платформ ARM и Intel Atom (с использованием компилятора по умолчанию) показан на Рис. 10. На платформе ARM автоматически применяется двойное выравнивание malign, и переменные занимают 24 байта, тогда как на платформе x86 они занимают 16 байт.
Рисунок 10:Выделение памяти при компиляции с флагами по умолчанию
8-байтовая (64-разрядная) переменная mVar2 дает другой результат для TestStruct, поскольку в ARM требуется 8-байтовое выравнивание для 64-разрядных переменных, таких как mVar2. В большинстве случаев это не приводит к неполадкам, поскольку при сборке приложения для платформы x86 вместо ARM требуется полная повторная сборка.
Тем не менее, если в приложении выполняется сериализация классов или структур, возможно несоответствие размеров. Например, мы создаем файл в приложении ARM; приложение записывает тестовую структуру в файл. Если потом загрузить данные из этого файла на платформе x86, размер класса в приложении будет отличаться от размера класса в файле. Аналогичные неполадки выравнивания памяти могут возникать в сетевом трафике, ожидающем определенной компоновки памяти.
Параметр компилятора GCC -malign-double обеспечивает одинаковое выравнивание памяти на платформах x86 и ARM.
Рисунок 11:Выделение памяти при добавлении флагов -malign-double
4.2 Портирование инструкций NEON* в SSE [3]
4.2.1 NEON
Технология ARM NEON* используется главным образом в мультимедиа, например в смартфонах и приложениях для телевидения высокой четкости. Согласно документации ARM*, это технология на базе 128-разрядной системы SIMD, представляющая собой расширение процессоров ARM* серии Cortex*–A. Это расширение обеспечивает по крайней мере трехкратное повышение производительности по сравнению с архитектурой ARM* v5 и двукратное — по сравнению с ARM* v6. Дополнительные сведения о технологии NEON см. по адресу: http://www.arm.com/products/processors/technologies/neon.php.
4.2.2 SSE: аналог Intel
SSE — это потоковое расширение SIMD для архитектуры Intel IA. Процессоры Intel Atom в настоящее время поддерживают набор расширений SSSE3 и более ранние версии, но пока не поддерживают SSE4.x. SSE — это 128-разрядная система, обрабатывающая упаковку данных с плавающей запятой. Началом данной модели выполнения следует считать технологию MMX. SSx — новое поколение, заменившее MMX. Дополнительные сведения см. в разделе "Volume 1: Basic Architecture"в Intel 64 and IA-32 Architectures Software Developer’s Manuals. Описание SSE, приведенное в разделе 5.5, содержит инструкции по SSE, SSE2, SSE3 и SSSE3. Эти операции с данными перемещают упакованные значения с плавающей запятой с заданной точностью между регистрами XMM или между этими регистрами и памятью. Регистры XMM служат в качестве замены регистров MMX.
4.2.3 Из NEON в SSE на уровне ассемблера
В руководстве для разработчиков содержатся ссылки на все команды SSE(x), но разработчикам также рекомендуется ознакомиться с различными командами SSE на уровне ассемблера, доступными по этой ссылке: http://neilkemp.us/src/sse_tutorial/sse_tutorial.html. Для доступа к базовой информации и к образцам кода используйте оглавление.
Аналогичным образом, следующее руководство ARM содержит сведения о NEON и небольшие образцы кода ассемблера в разделе 1.4 ”Developing for NEON”: http://infocenter.arm.com/help/topic/com.arm.doc.dht0002a/DHT0002A_introducing_neon.pdf.
Основные различия при сравнении ассемблерного кода NEON и SSE:
- • Порядок следования байтов. Процессоры Intel поддерживают только прямой порядок (от младшего к старшему), а процессоры ARM* поддерживают как прямой, так и обратный порядок (от старшего к младшему). В приведенных примерах в коде ARM* используется прямой порядок байтов, как и необходимо для платформы Intel. Примечание. При компиляции для ARM подразумевается использование определенных флагов. Например, при компиляции для ARM* с помощью GCC* используются флаги –mlittle-endian и –mbig-endian. Дополнительные сведения см. по адресу http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html.
- Степень детализации. В приведенных примерах простого ассемблерного кода сравним инструкцию ADDPS в SSE (Intel) с инструкцией VADD.ix в NEON (например, при x = 8 или 16). В последнем случае уровень детализации обрабатываемых данных более высокий.
Примечание. Разумеется, приведенный список различий не является исчерпывающим. Существуют и другие различия между NEON и SSE.
4.2.4 Из NEON в SSE на уровне C/C++
При преобразовании кода C/C++ NEON в SSE могут возникать самые разные проблемы с API. Всегда помните: предполагается, что встроенный код сборки не используется, а используется только настоящий код C/C++. Инструкции NEON также включают некоторые собственные библиотеки C. Эти инструкции представляют собой код C, но их невозможно выполнить на платформе Intel, их необходимо переписывать.
5. Оптимизация производительности приложений
5.1 Настройка производительности
В процессе кодирования используйте следующие методы оптимизации производительности приложения на платформе Intel Atom.
5.1.1 Используйте встроенные функции
Встроенными лучше всего делать небольшие функции, такие как, например, функция доступа к частным элементам данных. При использовании коротких функций увеличиваются издержки на их вызовы. Для длинных функций последовательность вызова и возврата занимает соответственно меньше времени, поэтому встраивание обеспечит меньший выигрыш. [4]
Встраивание позволяет сократить издержки в следующих случаях:
- Вызовы функций (включая передачу параметров и размещение адреса объектов в стеке)
- Сохранение фрейма стека вызывающей стороны
- Настройка нового стека
- Передача возвращенного значения
- Восстановление прежнего фрейма стека
- Возврат
5.1.2 Используйте Float вместо Double
В большинстве случаев использование значений типа Float вместо Double ускоряет процесс вычисления данных и позволяет снизить нагрузку на память устройств с процессором Intel Atom.
5.1.3 Многопоточный код
Многопоточное кодирование позволяет использовать функцию гиперпоточности процессоров Intel Atom для повышения производительности всей системы и ускорения обработки данных. Дополнительные сведения о многопоточности см. по адресу: http://www.intel.com/content/www/us/en/architecture-and-technology/hyper-threading/hyper-threading-technology.html.
5.2 Создание высокопроизводительных приложений с помощью установки различных флагов компилятора
Сборка собственного кода в приложениях Android выполняется с помощью GCC. При компиляции на платформе Intel Atom рекомендуется добавить следующие флаги:
-march=atom
-msse4
-mavx
-maes
Дополнительные сведения о параметрах компилятора см. в разделе: http://gcc.gnu.org/onlinedocs/gcc/i386-and-x86_002d64-Options.html
6. Заключение
В этом документе описывается разработка и оптимизация приложений для платформ Intel Atom, а также разработка и портирование приложений NDK.
Основные рассмотренные вопросы:
- В большинстве случаев приложения Android можно запускать на платформе Intel Atom напрямую. Для приложений NDK необходимо перекомпилировать собственный код. Если приложение содержит код ассемблера, эту часть кода необходимо переписать.
- Для повышения производительности приложений Android используйте функции архитектуры Intel (IA).
- Добавьте флаги компиляции для данной платформы, чтобы повысить эффективность кода сборки GCC.