Методика портирования приложений NDK Android*

Общие сведения

Это руководство поможет разработчикам портировать существующие приложения NDK для платформы ARM* на платформу x86. Если у вас уже есть действующее приложение, и нужно понять, как скоро можно будет представить это приложение на сайте Android* Market для устройств x86, то в этом документе вы найдете необходимые сведения, чтобы приступить к работе. Также здесь предоставляются советы и методики устранения проблем при компиляции в ходе портирования.

Содержание

  1. Общие сведения
  2. Описание NDK
  3. Портирование
  4. Советы по портированию ARM* на архитектуру x86
  5. Заключение

 

Описание NDK

NDK — великолепный инструмент, позволяющий сочетать мощь кода x86 с графическим интерфейсом приложения-оболочки Android*. Этот инструмент можно использовать для повышения производительности некоторых приложений, но необходимо соблюдать меры предосторожности, поскольку желаемого результата можно добиться не всегда.

Основная цель NDK заключается в предоставлении разработчикам следующих возможностей:

  • Компиляция встроенной библиотеки C/C++ для использования (при вызове кодом Java*) в пакете Android*.
  • Перекомпиляция встроенных библиотек ARM* для x86 (микроархитектура Intel® Atom™) с возможностью портирования при необходимости.

Возможны случаи, когда для выполнения второй задачи достаточно изменить флаг сборки и запустить перекомпиляцию, но иногда это не настолько просто. Например, если встроенная библиотека содержит встроенный код сборки внутри кода C, этот код не сможет работать «просто так» на двух разных архитектурах, потребуется переписать часть кода (см. раздел, где описываются различия между ARM* NEON* и Intel SSE).

Влияние на производительность и затраты ресурсов на JNI

Интерфейс, соединяющий Android* Java* со встроенным кодом, скомпилированным в NDK, называется Java* Native Interface (JNI). Дополнительные сведения см. на этой странице: http://java.sun.com/docs/books/jni/.

По приведенной выше ссылке находится обширное и подробное техническое описание и спецификация JNI. Для более краткого ознакомления будет достаточно и вики-страницы (в случае сомнения всегда следует проверять информацию, приведенную на вики-странице, в спецификации): http://en.wikipedia.org/wiki/Java_Native_Interface.

Применение JNI образует повышенную нагрузку на вычислительные ресурсы, поэтому объем вызовов JNI в приложении следует сводить к минимуму. Использование встроенного кода в приложении Android* вовсе не гарантирует повышения производительности. Как правило, некоторое повышение производительности достигается в случаях, когда встроенный код включает операции с интенсивным использованием ЦП (например, с активным использованием инструкций SSE), но в других случаях, например, когда приложение просто предоставляет пользователю веб-интерфейс, применение встроенного кода с помощью JNI приведет лишь к снижению производительности. Не существует четко оговоренных правил, касающихся того, в каких случаях NDK нужно использовать, а в каких — нет. Данный материал следует рассматривать как описание общих советов и факторов, которые следует принимать во внимание.

Загрузка пакета NDK

Последнюю версию пакета можно загрузить здесь: http://developer.android.com/sdk/ndk/index.html.Начиная с версии NDK r6b набор NDK можно использовать для сборки библиотек и на базе ARM*, и на базе x86 (архитектура Intel® Atom™). Таким образом, разработчикам предоставляются универсальные возможности портирования исходного кода.

Краткое описание особенностей NDK

Сборочные файлы

Разработчик создаёт сборочный файл Android.mk для проекта, а также файл Application.mk (не обязательно). Файл Application.mk служит для описания встроенных модулей, необходимых для приложения. Файл Android.mk управляет тем, как и из каких компонентов (статические или общие библиотеки) осуществляется сборка модулей. Вот фрагмент кода простого файла Android.mk:


Рисунок 1. Содержимое простого файла Android.mk.

 

Система сборки добавит папку lib и создаст библиотеку с именем libtest.so. В строке LOCAL_SRC_FILES, как и следует ожидать, разработчик указывает имена исходных файлов проекта. В строках LOCAL_LDLIBS и LOCAL_CFLAGS можно указать, соответственно, флаги компоновки и флаги компиляции.

Компоновка командной строки

В командной строке можно указать, что сборка осуществляется для архитектуры x86: ndk-build APP_ABI=x86

Вызвать встроенную библиотеку можно двумя методами: System.loadLibrary("относительный путь и имя") и System.load("полный путь к файлу библиотеки"). Первый из этих двух методов более надежен и используется чаще. При использовании второго метода можно опустить фрагмент «lib» имени библиотеки, указанной в файле Android.mk. Вот пример такого вызова:


Рисунок 2. Пример вызова встроенного кода.

 

Кроме того, во встроенном коде разработчику нужно убедиться, что метод входа во встроенный код обладает правильной подписью метода JNIEXPORT вместо типового заголовка C/C++. Дополнительные сведения см. по приведенным выше ссылкам на JNI.

Обработка файла встроенной библиотеки

Разработчик может загрузить встроенную библиотеку двумя способами: либо добавить ее в APK-пакет Android* и сослаться на нее во время выполнения, либо указать абсолютный путь на расположение библиотеки в файловой системе Android*. Выбор того или иного способа — на усмотрение разработчика.

Проверка во время выполнения

С помощью команды adb logcat разработчик может проверить успешную загрузку нужной встроенной библиотеки во время выполнения. Вот пример журнала системы, описывающего загрузку встроенной библиотеки. Обратите внимание, что указан полный путь к файлу встроенной библиотеки.


Рисунок 3. Пример вызова встроенного кода

Итоги

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

Портирование

Для большинства существующих приложений NDK портирование на архитектуру x86 осуществляется предельно просто. Если во встроенном коде нет функций, работающих только с архитектурой ARM*, то для портирования приложения достаточно его перекомпилировать, переупаковать и заново опубликовать.

Ниже перечислены действия по портированию приложения NDK на архитектуру x86.

  1. Получите последнюю версию NDK. Поддержка x86 была впервые реализована в версии android-ndk-r6, но первая версия содержала ряд ошибок, которые были исправлены через некоторое время. Итак, убедитесь, что вы загрузили и установили наиболее позднюю версию NDK (на момент написания этой статьи наиболее поздней была версия android-ndk-r6b) с веб-сайта Android* NDK site.

     

  2. При наличии файла Application.mk необходимо отредактировать строку APP_ABI, добавив архитектуру x86. Пример:

    APP_ABI := armeabi armeabi-v7a x86

    If you didn’t use an Application.mk file, add x86 to the command line build, here is the command line and output from building one of the NDK sample applications.
    $ ndk-build APP_ABI="armeabi armeabi-v7a x86"

    Install : test-libstl => libs/armeabi/test-libstl
    Install : test-libstl => libs/armeabi-v7a/test-libstl
    Install : test-libstl => libs/x86/test-libstl
  3. На предыдущем шаге мы видим, что внутри папки libs создана папка с двоичными файлами каждой архитектуры. Теперь нужно переупаковать APK, добавив новые библиотеки. Поскольку папка libs находится в корневой папке проекта, инструмент сборки, создающий APK, уже «знает» о наличии двоичных файлов в этой папке. Для добавления нового двоичного файла x86 достаточно просто заново собрать проект APK в Eclipse. Это же касается и командной строки. Ниже приведен образец результата сборки демо hello-jni:

    $ android.bat update project --path C:/Tools/android-ndk-r6b/samples/hello-jni
    Updated local.properties
    Added file C:\Tools\android-ndk-r6b\samples\hello-jni\build.xml
    Added file C:\Tools\android-ndk-r6b\samples\hello-jni\proguard.cfg
    $ ant -f hello-jni/build.xml debug
    Buildfile: C:\Tools\android-ndk-r6b\samples\hello-jni\build.xml

    debug:
    [echo] Running zip align on final apk...
    [echo] Debug Package: android-ndk-r6b\samples\hello-jni\bin\HelloJni-debug.apk
    BUILD SUCCESSFUL
  4. Вот и все. Осталось запустить код и протестировать его на устройстве с архитектурой Intel или на эмуляторе x86. В заключение осталось только проверить правильность упаковки всех двоичных файлов. Для этого можно открыть APK с помощью любой программы, поддерживающей формат архивов ZIP, и убедиться в наличии всех двоичных файлов. Вот снимок экрана структуры APK: виден двоичный файл x86.

Советы по портированию ARM* на архитектуру x86

Портирование приложений на архитектуру x86 должно осуществляться без затруднений, но в некоторых случаях может потребоваться устранить проблемы в коде, связанные с различием между архитектурами Intel® Atom™ и ARM*. В следующих разделах описываются некоторые возможные проблемы и способы их решения.

Совместимость наборов инструментов

Возможно, что в вашей среде программирования используется непосредственно цепочка инструментов вместо сценариев сборки Android*. В случае с ARM* используется следующий путь:

android-ndk\toolchains\arm-linux-androideabi-4.4.3
Для архитектуры x86 нужно использовать следующий путь:
android-ndk\toolchains\x86-4.4.3
Дополнительные сведения см. в документе по NDK, находящемся по адресу android-ndk/docs/STANDALONE-TOOLCHAIN.html.

Выравнивание памяти: ARM* vs. Intel® Atom™

При портировании кода С/C++ между архитектурами ARM* и Intel® Atom™ могут возникнуть несоответствия выравнивания памяти. Пример такой проблемы приведен в следующей статье: /en-us/blogs/2011/08/18/understanding-x86-vs-arm-memory-alignment-on-android. Разработчикам следует использовать в коде явное принудительное выравнивание данных в памяти в необходимых случаях. Иначе правильность обработки данных на другой платформе не гарантируется.

Вычисления с плавающей запятой: ARM vs. Intel® Atom™

При построении библиотек NDK поддерживаются три параметра интерфейса двоичных файлов приложений (ABI):

  1. armeabi — параметр по умолчанию; создаются двоичные файлы для устройств на базе ARM* v5TE. При этом вычисления с плавающей запятой осуществляются программно. Двоичные файлы, созданные с этим параметром ABI, работают на всех устройствах ARM*.
  2. armeabi-v7a — создаются двоичные файлы для устройств на базе ARM* v7, вычисления осуществляются аппаратно.
  3. x86 — двоичные файлы поддерживают набор инструкций IA-32 с аппаратной обработкой вычислений с плавающей запятой.

Все эти параметры ABI поддерживают операции с плавающей запятой и не вызовут затруднений при портировании кода на платформу x86, если не используются какие-либо инструкции, поддерживающие только архитектуру ARM*. Кроме того, если до этого вы компилировали программу с параметром armeabi, а теперь реализовали поддержку x86, то большинство операций с плавающей запятой будут выполняться быстрее.

Портирование инструкций ARM* NEON* на Intel® SSE для Intel® Atom™

В одной статье невозможно перечислить все, но предоставленные здесь сведения помогут понять различия в реализации расширений SIMD (обработка нескольких потоков данных одним потоком команд) в архитектурах Intel и ARM*. Разработчики получат средства, позволяющие проводить простые эксперименты с кодом.

Что такое NEON?

NEON* — это технология ARM*, используемая главным образом в мультимедиа-решениях (смартфоны, HDTV и т.п.). Согласно документации ARM*, это технология на базе 128-разрядной системы SIMD, представляющая собой расширение процессоров ARM* серии Cortex*–A. Это расширение обеспечивает по крайней мере трехкратное повышение производительности по сравнению с архитектурой ARM* v5 и двукратное — по сравнению с ARM* v6. Дополнительные сведения об этой технологии, а также более подробная информация об использовании NEON и других факторах, влияющих на производительность, см. по ссылке: http://www.arm.com/products/processors/technologies/neon.php

В основе этого расширения используется следующий принцип: регистры объединяются в векторы, причем каждый регистр в составе вектора представляет собой элемент, тип данных которого совпадает с типом данных других элементов. После этого операции применяются сразу к рядам элементов. Такая методика называется технологией Пакетов SIMD.

SSE: аналог Intel

SSE — это потоковое расширение SIMD для архитектуры Intel IA. Процессор Intel® Atom™ в настоящий момент поддерживает версию SIMD вплоть до SSSE3. Процессоры Atom™ не поддерживают SSE4.x. Упаковкой данных с плавающей запятой также занимается 128-разрдяная система. Началом данной модели выполнения следует считать технологию MMX. SSx — новое поколение, заменившее MMX. Дополнительные сведения см. в разделе «Том 1. Базовая архитектура» руководствах разработчиков Intel по архитектуре IA-32 и IA-64: http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html. В настоящее время описание SSE приведено в разделе 5.5. Приводятся коды операций SSE, SSE2, SSE3 и SSSE3Note. Операции с данными обычно подразумевают работу с упакованными значениями с плавающей запятой с заданной точностью; можно выполнять массовое перемещение данными между регистрами XMM или между этими регистрами и памятью. Регистры XMM служат в качестве замены регистров MMX.

NEON vs. SSE на уровне ассемблера

В упомянутом выше руководстве для разработчиков содержатся ссылки на все команды SSE(x), но разработчикам также рекомендуется ознакомиться с различными командами SSE на уровне ассемблера, доступными по этой ссылке: http://neilkemp.us/src/sse_tutorial/sse_tutorial.html.

По этой ссылке откроется страница с оглавлением, с помощью которого можно либо перейти непосредственно к образцам кода, либо сначала прочесть вводный текст. В следующем руководстве ARM* содержится аналогичная информация и небольшие образцы кода NEON*: /sites/default/files/m/b/4/c/DHT0002A_introducing_neon.pdf. См. раздел 1.4 в документе ARM*.

Here are some key takeaways in comparing NEON and SSE assembly code in general (note that information is aНиже приведены некоторые различия в ассемблерном коде NEON и SSE. Помните, что эта информация может быть уже устаревшей, поскольку технологии постоянно развиваются, и в технологиях SIMD и особенностях кодирования приложений эти проблемы могут быть уже решены, а могут существовать и другие проблемы).

Порядок следования байтов. Процессоры Intel поддерживают только прямой порядок (от младшего к старшему), а процессоры ARM* поддерживают как прямой, так и обратный порядок (от старшего к младшему). В приведенных примерах в коде ARM* используется прямой порядок байтов, как и необходимо для платформы Intel. Обратите внимание, что при работе с платформой ARM* могут существовать определенные особенности использования компилятора. Например, при компиляции для ARM* с помощью GCC* существуют флаши –mlittle-endian и –mbig-endian. Дополнительные сведения: http://gcc.gnu.org/onlinedocs/gcc/ARM-Options.html

Степень детализации. В приведенных примерах простого ассемблерного кода (вновь напомним, что это не исчерпывающий перечень всех различий между NEON и SSE, с которыми могут столкнуться раработчиков), сравним инструкцию ADDPS в SSE (Intel) с инструкцией VADD.ix в NEON (например, при x = 8 или 16). В последнем случае уровень детализации обрабатываемых данных более высокий.

NEON vs. SSE на уровне C/C++

При портировании кода C/C++ NEON на SSE могут возникать самые разные проблемы с API. Всегда помните: предполагается, что встроенный код сборки не используется, а используется только настоящий код C/C++.

Одно из таких различий между NEON и SSE на высокоуровневом программировании касается работы с большими фрагментами данных (128 разрядов). В этой статье описывается данный аспект портирования: http://stackoverflow.com/questions/7203231/neon-vs-intel-sse-equivalence-of-certain-operations

Заключение

Надеемся, что это руководство помогло портировать существующие приложения NDK на платформу x86. Портирование на архитектуру x86 позволит загружать вашу программу, приобретать ее и использовать на совершенно новой категории устройств Android*. Если в процессе портирования возникнут проблемы, оставьте комментарии к этой статье. Мы с радостью ответим на вопросы.

Примечания

* Прочие наименования и товарные знаки могут быть собственностью третьих лиц.

© Intel Corporation, 2011. Все права защищены.

Intel и Atom являются охраняемыми товарными знаками Intel Corporation в США и в других странах.

ИНФОРМАЦИЯ В ДАННОМ ДОКУМЕНТЕ ПРИВЕДЕНА ТОЛЬКО В ОТНОШЕНИИ ПРОДУКТОВ INTEL. ДАННЫЙ ДОКУМЕНТ НЕ ПРЕДОСТАВЛЯЕТ ЯВНОЙ ИЛИ ПОДРАЗУМЕВАЕМОЙ ЛИЦЕНЗИИ, ЛИШЕНИЯ ПРАВА ВОЗРАЖЕНИЯ ИЛИ ИНЫХ ПРАВ НА ИНТЕЛЛЕКТУАЛЬНУЮ СОБСТВЕННОСТЬ. КРОМЕ СЛУЧАЕВ, УКАЗАННЫХ В УСЛОВИЯХ И ПРАВИЛАХ ПРОДАЖИ ТАКИХ ПРОДУКТОВ, INTEL НЕ НЕСЕТ НИКАКОЙ ОТВЕТСТВЕННОСТИ И ОТКАЗЫВАЕТСЯ ОТ ЯВНЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ ГАРАНТИЙ В ОТНОШЕНИИ ПРОДАЖИ И/ИЛИ ИСПОЛЬЗОВАНИЯ СВОИХ ПРОДУКТОВ, ВКЛЮЧАЯ ОТВЕТСТВЕННОСТЬ ИЛИ ГАРАНТИИ ОТНОСИТЕЛЬНО ИХ ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ, ОБЕСПЕЧЕНИЯ ПРИБЫЛИ ИЛИ НАРУШЕНИЯ КАКИХ-ЛИБО ПАТЕНТОВ, АВТОРСКИХ ПРАВ ИЛИ ИНЫХ ПРАВ НА ИНТЕЛЛЕКТУАЛЬНУЮ СОБСТВЕННОСТЬ.

КРОМЕ СЛУЧАЕВ, СОГЛАСОВАННЫХ INTEL В ПИСЬМЕННОЙ ФОРМЕ, ПРОДУКТЫ INTEL НЕ ПРЕДНАЗНАЧЕНЫ ДЛЯ ИСПОЛЬЗОВАНИЯ В СИТУАЦИЯХ, КОГДА ИХ НЕИСПРАВНОСТЬ МОЖЕТ ПРИВЕСТИ К ТРАВМАМ ИЛИ ЛЕТАЛЬНОМУ ИСХОДУ.

Продукты, описанные в данном документе, могут содержать ошибки и неточности, из-за чего реальные характеристики продуктов могут отличаться от приведенных здесь. Уже выявленные ошибки могут быть предоставлены по запросу.

Перед размещением заказа получите последние версии спецификаций в региональном офисе продаж Intel или у местного дистрибьютора.

Номерные копии документов, на которые имеются ссылки в данном документе, а также другие материалы Intel можно заказать по телефону 1-800-548-4725 или загрузить по адресу: http://www.intel.com/design/literature.htm

Уведомление об оптимизации

Einzelheiten zur Compiler-Optimierung finden Sie in unserem Optimierungshinweis.