Создание оптимизированного кода NDK для нескольких архитектур? - PullRequest
53 голосов
/ 23 февраля 2011

У меня есть некоторый C-код для Android, который выполняет множество низкоуровневых вычислений. Я хотел бы знать, какие настройки мне следует использовать (например, для моих Android.mk и Application.mk), чтобы полученный код работал на всех текущих устройствах Android, но также использовал преимущества оптимизации для конкретных наборов микросхем. Я ищу подходящие настройки Android.mk и Application.mk по умолчанию, и я хочу избежать засорения моего C-кода ветками #ifdef.

Например, я знаю, что ARMv7 имеет инструкции с плавающей запятой, и некоторые микросхемы ARMv7 поддерживают инструкции NEON, и что ARM по умолчанию не поддерживает ни один из них. Можно ли установить флаги, чтобы я мог собрать ARMv7 с NEON, ARMv7 без NEON и сборку ARM по умолчанию? Я знаю, как сделать последние два, но не все 3. Я осторожен в отношении того, какие настройки я использую, поскольку я предполагаю, что текущие значения по умолчанию являются самыми безопасными настройками и каковы риски для других вариантов.

Для оптимизации GCC я использую следующие флаги:

LOCAL_CFLAGS=-ffast-math -O3 -funroll-loops

Я проверил все 3 из них, чтобы ускорить мой код. Могу ли я добавить еще какие-нибудь распространенные?

Еще один совет, который у меня есть, - добавить «LOCAL_ARM_MODE: = arm» в Android.mk, чтобы ускорить работу новых чипов рук (хотя я не совсем понимаю, что именно происходит и что происходит со старыми чипами).

Ответы [ 2 ]

112 голосов
/ 23 февраля 2011

Процессоры ARM имеют 2 общих набора команд, которые они поддерживают: «ARM» и «Thumb». Хотя есть разные варианты обоих, инструкции ARM - 32 бита каждая, а инструкции Thumb - 16 бит. Основное различие между ними состоит в том, что инструкции ARM имеют возможность делать больше за одну инструкцию, чем Thumb. Например, одна инструкция ARM может добавить один регистр в другой регистр, выполняя сдвиг влево по второму регистру. В Thumb одна инструкция должна выполнить сдвиг, затем вторая инструкция добавит.

Инструкции ARM не в два раза лучше, но в некоторых случаях они могут быть быстрее. Это особенно актуально для сборки ARM, свернутой вручную, которая может быть настроена новыми способами, чтобы наилучшим образом использовать «сдвиги бесплатно». Инструкции для большого пальца имеют свои преимущества и размер: они меньше разряжают батарею.

В любом случае, это то, что делает LOCAL_ARM_MODE - это означает, что вы компилируете свой код как инструкции ARM вместо инструкций Thumb. Компиляция в Thumb является значением по умолчанию в NDK, так как он имеет тенденцию создавать меньший двоичный файл, и разница в скорости не так заметна для большинства кода. Компилятор не всегда может воспользоваться преимуществами дополнительной «поддержки», которую может предоставить ARM, так что в конечном итоге вам все равно понадобится более или менее одинаковое количество инструкций.

Результат того, что вы видите из кода C / C ++, скомпилированного в ARM или Thumb, будет идентичным (исключая ошибок компилятора ).

Это совместимо между новыми и старыми процессорами ARM для всех телефонов Android, доступных сегодня. Это связано с тем, что по умолчанию NDK компилируется в «двоичный интерфейс приложения» для процессоров на основе ARM, которые поддерживают набор инструкций ARMv5TE. Этот ABI известен как «armeabi» и может быть явно установлен в Application.mk, поставив APP_ABI := armeabi.

Более новые процессоры также поддерживают специфичный для Android ABI, известный как armeabi-v7a, который расширяет armeabi для добавления набора команд Thumb-2 и набора аппаратных команд с плавающей запятой под названием VFPv3-D16. Совместимые с armeabi-v7a процессоры также могут дополнительно поддерживать набор команд NEON, который вы должны проверять во время выполнения и предоставлять пути к коду, когда он доступен, а когда нет. В каталоге NDK / samples есть пример, который делает это (привет-неон). Под капотом Thumb-2 более «ARM-подобен» в том смысле, что его инструкции могут выполнять больше операций за одну инструкцию, но при этом имеют преимущество, занимая меньше места.

Чтобы скомпилировать «толстый двоичный файл», содержащий библиотеки armeabi и armeabi-v7a, вы должны добавить в Application.mk:

следующее
APP_ABI := armeabi armeabi-v7a

Когда файл .apk установлен, менеджер пакетов Android устанавливает лучшую библиотеку для устройства. Так что на старых платформах будет установлена ​​библиотека armeabi, а на новых устройствах - armeabi-v7a.

Если вы хотите проверить функции ЦП во время выполнения, вы можете использовать функцию NDK uint64_t android_getCpuFeatures(), чтобы получить функции, поддерживаемые процессором. Возвращает битовый флаг ANDROID_CPU_ARM_FEATURE_ARMv7 на процессорах v7a, ANDROID_CPU_ARM_FEATURE_VFPv3, если поддерживаются аппаратные плавающие точки, и ANDROID_CPU_ARM_FEATURE_NEON, если поддерживаются расширенные инструкции SIMD. У ARM не может быть NEON без VFPv3.

В итоге: по умолчанию ваши программы наиболее совместимы. Использование LOCAL_ARM_MODE может немного ускорить процесс за счет времени автономной работы из-за использования инструкций ARM - и оно так же совместимо, как и настройки по умолчанию. Добавив строку APP_ABI := armeabi armeabi-v7a, вы улучшите производительность на новых устройствах, оставаясь совместимыми со старыми, но ваш файл .apk будет больше (из-за наличия 2 библиотек). Чтобы использовать инструкции NEON, вам нужно написать специальный код, который определяет возможности ЦП во время выполнения, и это относится только к более новым устройствам, которые могут запускать armeabi-v7a.

23 голосов
/ 25 февраля 2012

Отличный ответ, просто добавьте, что вы должны использовать

APP_ABI := all

, это скомпилирует 4 бинарных файла, armv5, armv7, x86 и mips

, возможно, вам потребуется новая версия ndk

...