Меню

Что такое armv7 – Android NDK. Очень много ABI: armeabi, armeabi-v7a, arm64-v8a… Под какую из них реально стоит строить нативные .so-либы?

Leave a comment

Конец эпохи ARMv7 или же немного о портировании игр / Habr

Вступление


Пару недель назад я столкнулся с проблемой отсутствия у игры поддержки архитектуры моего процессора в телефоне. Конечно, я знал, что ARMv7 устареет, но не ожидал, что это случится так быстро. В сети особо информации по downgrade приложения не было, поэтому пришлось немного импровизировать, вспоминая времена, когда пропала поддержка ARMv6. Так что, в данной статье речь пойдёт о переносе приложений с архитектуры ARMv8 на ARMv7, на основе моего опыта.
Тема с портированием андроид игр с одной архитектуры на другую весьма неновая. Изначально я столкнулся с этим ещё в 2015 году, только речь шла о портировании под ARMv6. С тех пор принцип переноса не поменялся, и перенести игру с одной архитектуры на другую может любой, если имеет необходимые библиотеки.

Разбор apk


Итак, что такое .apk? APK файл представляет из себя немного модифицированный ZIP архив, который содержит ресурсы игры и игровой движок. Выглядит он примерно так:

Папка lib — ключевая точка при переносе между архитектурами. Она содержит библиотеки движка нашей игры.
* armeabi — armv6 библиотеки

(не актуально)
* armeabi-v7a — armv7 библиотеки (при отсутствии папки — отсутствует и поддержка архитектуры)
* arm64-v8a — armv8 x64 библиотеки

Перенос


Шаг #1

Первым делом нам нужно узнать, возможно ли портировать игру. Для этого нужно определить движок игры. К примеру, файл lib/libunity.so — принадлежит Unity Engine, а по наличию папки assets/x-renpy можно догадаться, что игра разработана на RenPy Engine. Если у игры движок не является собственным, то переходим к шагу два.
Шаг #2

Итак, мы определили движок игры. Теперь нам нужно найти подходящего донора. Если эта игра раньше поддерживалась вашим аппаратом, а после обновления перестала — то можно попробовать вскрыть ресурсы прошлых версий. Если этот вариант не подошёл, то можно поискать игры, базирующиеся на этом же движке. Иногда разработчики просто не добавляют поддержку armv7, так что есть вероятность найти нужные компоненты.
Шаг #3

Мы нашли подходящего донора, теперь нам нужно добавить папку lib/armeabi-v7a в наш (name).apk. Добавляем и видим следующее:

В самом начале, как я уже сказал,

APK файл представляет из себя немного модифицированный ZIP архив, а после изменения его содержимого он становится обычным ZIP'ом.

Шаг #4

Для того, чтобы ваше устройство могло установить ваш (name).apk файл, его нужно «подписать». Для этого есть несколько различных утилит, к примеру apk-signer.

Устанавливаем утилиту на свой девайс, открываем, переходим в раздел Signing, нажимаем на карандаш снизу и выбираем наш (name).apk. После чего программа предложит вам выбрать, куда сохранить «подписанный файл». Выбираем и ждём. ??? PROFIT!!!

Шаг #5


Будь добрым человеком, выложи свой порт для общего пользования, к примеру, в топик игры на том же 4PDA. 😉

Некоторые lib'ы


Unity 3.* for ARMv6: yadi.sk/d/XGHn309ORuPgeg

RenPy ARMv7: yadi.sk/d/_U-hnz8D-J7D-Q

ARM аccемблер / Habr

Привет всем!
По роду деятельности я программист на Java. Последние месяцы работы заставили меня познакомиться с разработкой под Android NDK и соответственно написание нативных приложений на С. Тут я столкнулся с проблемой оптимизации Linux библиотек. Многие оказались абсолютно не оптимизированы под ARM и сильно нагружали процессор. Ранее я практически не программировал на ассемблере, поэтому сначала было сложно начать изучать этот язык, но все же я решил попробовать. Эта статья написана, так сказать, от новичка для новичков. Я постараюсь описать те основы, которые уже изучил, надеюсь кого-то это заинтересует. Кроме того, буду рад конструктивной критике со стороны профессионалов.

Введение

Итак, для начала разберёмся что же такое ARM. Википедия дает такое определение:

Архитектура ARM (Advanced RISC Machine, Acorn RISC Machine, усовершенствованная RISC-машина) — семейство лицензируемых 32-битных и 64-битных микропроцессорных ядер разработки компании ARM Limited. Компания занимается исключительно разработкой ядер и инструментов для них (компиляторы, средства отладки и т. п.), зарабатывая на лицензировании архитектуры сторонним производителям.

Если кто не знает, сейчас большая часть мобильных устройств, планшетов разработаны именно на этой архитектуре процессоров. Основным преимуществом данного семейства является низкое энергопотребление, благодаря чему он часто используется в различных встроенных системах. Архитектура развивалась с течением времени, и начиная с ARMv7 были определены 3 профиля: ‘A’(application) — приложения, ‘R’(real time) — в реальном времени,’M’(microcontroller) — микроконтроллер. Историю разработки этой технологии и другие интересный данные вы можете прочитать в Википедии или погуглив в интернете. ARM поддерживает разные режимы работы (Thumb и ARM, кроме того в последние время появился Thumb-2, являющийся смесью ARM и Thumb). В данной статье рассмотрим собственно режим ARM, в котором исполняется 32-битный набор команд.

Каждый ARM процессор создан из следующих блоков:

  • 37 регистров (из которых видимых при разработке только 17)
  • Арифметико-логи́ческое устройство (АЛУ) — выполняет арифметические и логические задачи
  • Barrel shifter — устройство, созданное для перемещения блоков данных на определенное количество бит
  • The CP15 — специальная система, контроллирующая ARM сопроцессоры
  • Декодер инструкций — занимается преобразованием инструкции в последовательность микроопераций

Это не все составляющие ARM, но углубление в дебри построения процессоров не входит в тему данной статьи.
Конвейерное исполнение (Pipeline execution)

В ARM процессорах используется 3-стадийный конвейер (начиная с ARM8 был реализова 5-стадийный конвейер). Рассмотрим простой конвейер на примере процессора ARM7TDMI. Исполнение каждой инструкции состоит из трёх ступеней:

1. Этап выборки (F)
На этом этапе инструкции поступают из ОЗУ в конвейер процессора.
2. Этап декодирования (D)
Инструкции декодируются и распознаётся их тип.
3. Этап исполнения (E)
Данные поступают в ALU и исполняются и полученное значение записывается в заданный регистр.

Но при разработке надо учитывать, что, есть инструкции, которые используют несколько циклов исполнения, например, load(LDR) или store. В таком случае этап исполнения (E) разделяется на этапы (E1, E2, E3...).

Условное выполнение

Одна из важнейших функций ARM ассемблера — условное выполнение. Каждая инструкция может исполняться условно и для этого используются суффиксы. Если суффикс добавляется к названию инструкции, то прежде чем выполнить ее, происходит проверка параметров. Если параметры не соответствуют условию, то инструкция не выполняется. Суффиксы:
MI — отрицательное число
PL — положительное или ноль
AL — выполнять инструкцию всегда
Суффиксов условного выполнения намного больше. Остальные суффиксы и примеры прочитать в официальной документации: ARM документация
А теперь пришло время рассмотреть…
Основы синтаксиса ARM ассемблера

Тем, кто раньше работал с ассемблером этот пункт можно фактически пропустить. Для всех остальных опишу основы работы с этим языком. Итак, каждая программа на ассемблере состоит из инструкций. Инструкция создаётся таким образом:
{метка} {инструкция|операнды} {@ комментарий}
Метка — необязательный параметр. Инструкция — непосредственно мнемоника инструкции процессору. Основные инструкции и их использование будет разобрано далее. Операнды — константы, адреса регистров, адреса в оперативной памяти. Комментарий — необязательный параметр, который не влияет на исполнение программы.
Имена регистров

Разрешены следующие имена регистров:
1.r0-r15

2.a1-a4

3.v1-v8 (переменные регистры, с r4 по r11)

4.sb and SB (статический регистр, r9)

5.sl and SL (r10)

6.fp and FP (r11)

7.ip and IP (r12)

8.sp and SP (r13)

9.lr and LR (r14)

10.pc and PC (программный счетчик, r15).

Переменные и костанты

В ARM ассемблере, как и любом (практически) другом языке программирования могут использоваться переменные и константы. Они разделяются на такие типы:
  • Числовые
  • Логические
  • Строковые

Числовые переменные инициализируются так:
a SETA 100; создается числовая переменная «a» с значением 100.
Строковые переменные:
improb SETS «literal»; создается переменная improb с значение «literal». ВНИМАНИЕ! Значение переменной не может превышать 5120 символов.
В логических переменных соответственно используются значения TRUE и FALSE.
Примеры инструкций ARM ассемблера

В данной таблице я собрал основные инструкции, которая потребуется для дальнейшей разработки (на самом базовом этапе:):
Название Синтаксис Применение
ADD (добавление) ADD r0, r1, r2
r0 = r1 + r2
SUB (вычитание) SUB r0, r1, r2
r0 = r1 — r2
RSB (обратное вычитание) RSB r0, r1, #10
r0 = 10 — r1
MUL (умножение) MUL r0, r1, r2
r0 = r1 * r2
MOV MOV r0, r1
r0 = r1
ORR( логическая операция) ORR r0, r1, r2
r0 = r1 | r2
TEQ TEQ r0, r1
r0 == r1
LDR (загрузка) LDR r4, [r5]
r4 = *r5
STR STR r4, [r5]
*r5 = r4
ADR ADR r3, a
a — переменная. r3 = &a

Чтобы закрепить использование основных инструкций давайте напишем несколько простых примеров, но сначала нам понадобится arm toolchain. Я работаю в Linux поэтому выбрал: frank.harvard.edu/~coldwell/toolchain (arm-unknown-linux-gnu toolchain). Ставится он проще простого, как и любая другая программа на Linux. В моем случае (Russian Fedora) понадобилось только установить rpm пакеты с сайта.
Теперь пришло время написать простейший пример. Программа будет абсолютно бесполезной, но главное, что будет работать:) Вот код, который я вам предлагаю:


start:                       @ Необязательная строка, обозначающая начало программы
        mov   r0, #3         @ Грузим в регистр r0 значение 3
        mov   r1, #2         @ Делаем тоже самое с регистром r1, только теперь с значением 2
        add   r2, r1, r0     @ Складываем значения r0 и r1, ответ записываем в r2
        mul   r3, r1, r0     @ Умножаем значение регистра r1 на значение регистра r0, ответ записываем в r3
stop:   b stop               @ Строка завершения программы

Компилируем программу до получения .bin файла:

/usr/arm/bin/arm-unknown-linux-gnu-as -o arm.o arm.s
/usr/arm/bin/arm-unknown-linux-gnu-ld -Ttext=0x0 -o arm.elf arm.o
/usr/arm/bin/arm-unknown-linux-gnu-objcopy -O binary arm.elf arm.bin

(код в файле arm.s, а toolchain в моем случае лежит в директории /usr/arm/bin/)
Если все прошло успешно, у вас будет 3 файла: arm.s (собственно код), arm.o, arm.elf, arm.bin (собственно исполняемая программа). Для того, чтобы проверить работу программы не обязательно иметь собственное arm устройство. Достаточно установить QEMU. Для справки:
QEMU — свободная программа с открытым исходным кодом для эмуляции аппаратного обеспечения различных платформ.

Включает в себя эмуляцию процессоров Intel x86 и устройств ввода-вывода. Может эмулировать 80386, 80486, Pentium, Pentium Pro, AMD64 и другие x86-совместимые процессоры; PowerPC, ARM, MIPS, SPARC, SPARC64, m68k — лишь частично.

Работает на Syllable, FreeBSD, FreeDOS, Linux, Windows 9x, Windows 2000, Mac OS X, QNX, Android и др.

Итак, для эмуляции arm понадобится qemu-system-arm. Этот пакет есть в yum, так что тем, у кого Fedora, можно не заморачиваться и просто выполнить комманду:
yum install qemu-system-arm

Далее надо запустить эмулятор ARM, так, чтобы он выполнил нашу программу arm.bin. Для этого создадим файл flash.bin, который будет флэш памятью для QEMU. Сделать это очень просто:


dd if=/dev/zero of=flash.bin bs=4096 count=4096
dd if=arm.bin of=flash.bin bs=4096 conv=notrunc

Теперь грузим QEMU с полученой flash памятью:

qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null

На выходе вы получите что-то вроде этого:

[[email protected] ~]$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
QEMU 0.15.1 monitor — type 'help' for more information
(qemu)

Наша программа arm.bin должна была изменить значения четырех регистров, следовательно для проверки правильности работы давайте посмотрим на эти самые регистры. Делается это очень простой коммандой: info registers
На выходе вы увидите все 15 ARM регистров, при чем у четырех из них будут измененные значения. Проверьте:) Значения регистров совпадают с теми, которые можно ожидать после исполнения программы:


(qemu) info registers
R00=00000003 R01=00000002 R02=00000005 R03=00000006
R04=00000000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00000000 R13=00000000 R14=00000000 R15=00000010
PSR=400001d3 -Z-- A svc32

P.S. В этой статье я постарался описать основы программирования на ARM ассемблер. Надеюсь вам понравилось! Этого хватит для того, чтобы далее углубляться в дебри этого языка и писать на нем программы. Если все получится, буду писать дальше о том, что узнаю сам. Если есть ошибки, прошу не пинать, так как я новичок в ассемблере.

Как узнать тип процессора на Android

После выхода ОС Android Lollipop установка приложения Xposed Framework немного усложнилась.

И не столько из-за того, что для каждой ОС нужны отдельные инсталляторы, сколько из-за того, что для скачивания нужных файлов нужно точно знать тип процессора.

По сути, архитектура процессора все больше становится определяющим фактором, в том числе для установки обновлений и, конечно, для пользовательских ROM.

В современных андроидах используются три вида архитектуры CPU:

  • ARM – возможно, самая распространенная, оптимизированная под расход батареи;
  • ARM64 – модернизированная ARM, которая поддерживает 64-битную обработку данных и все чаще устанавливается на новые девайсы;
  • x86 – намного мощнее, чем две предыдущие, но и более энергозатратная, поэтому менее распространенная.

Если пока не уверены, что знаете, какой тип процессора у вашего устройства, следуйте нашим подсказкам.

Шаг 1. Установите приложение Droid Hardware Info

Самый простой способ определить тип процессора – установить приложение, которое считает и выдаст информацию о начинке телефона или планшета.

Можно порекомендовать приложение Droid Hardware Info, которое можно бесплатно скачать в Google Play Store.

Шаг 2. Просмотрите данные полей CPU Architecture и Instruction Sets

После установки откройте Droid Hardware Info и зайдите в раздел System.

Здесь обратите внимание на два поля: CPU Architecture и Instruction Sets.

Эти данные пригодятся при выполнении следующего, последнего шага.

Шаг 3. Вычислите тип процессора

Данные из раздела System немного отличаются от технических требований для приложений или Xposed Installer.

Чтобы понять какой у вас тип процессора, сравните данные полей CPU Architecture и Instruction Sets со списком, который приведен ниже.

  • ARM: ARMv7 или armeabi
  • ARM64: AArch64 или arm64
  • x86: x86 или x86abi

Что получилось? Какой тип процессора у вашего девайса – ARM, ARM64 или x86? Нам интересно, пишите в комментах!

[Всего голосов: 17    Средний: 3.4/5]

ARM64 и Ты / Habr

Несколько запоздалый перевод заинтересовавшего меня блогпоста о том, что в действительности дает 64-битность процессора в iPhone без маркетинговой шелухи. Если текст покажется вам слишком очевидным, пропустите часть «Базовые преимущества и недостатки».

Как только был анонсирован iPhone 5S, технические медия были переполнены недостоверными статьями. К сожалению, написание хороших статей занимает время, а мир технической журналистики больше ценит скорость, чем достоверность. Сегодня, по просьбе нескольких своих читателей, я кратко изложу, что дает 64-bit ARM в iPhone 5S в плане производительности, возможностей и разработки.


Давайте для начала рассмотрим что, собственно, 64-битность означает. С этим термином связанно много путаницы, в основном из-за того, что нет единого устоявшегося определения. Однако, существует общее понимание этого термина. «Битность» обычно означает либо размер числового регистра, или размер указателя. К счастью, для большинства современных процессоров, их размер совпадает. Таким образом, 64-битность означает что процессор обладает 64-битными числовыми регистрами и 64-битными указателями.

Важно также отметить, что 64-битность не означает, т.к. и здесь есть много недопонимания. Итак, 64-битность не определяет:

  1. Размер адресуемой памяти. Количество бит, реально задействованных в указателе не связано с битностью процессора. Процессоры ARM использует от 26 до 40 бит, и это число может изменяться в отрыве от битности процессора.
  2. Ширину шины данных. Объем данных, запрашиваемых из RAM или кэша также не связано с битностью. Отдельные процессорные инструкции могут запрашивать произвольные объемы данных, но объем реально единовременно запрашиваемых данных может отличаться, либо разбивая запросы на части, или запрашивая больше чем необходимо. Уже в iPhone 5 размер запрашиваемого блока данных составляет 64 бита, а у PC доходит до 192 бит.
  3. Все что связано с вычислениями с плавающей запятой. Регистры FPU не связаны с архитектурой и процессоры ARM использовали 64-битные регистры задолго до ARM64.

Если вы будете сравнивать идентичные процессоры 32 и 64 битные CPU, вы не найдете больших различий, так что значительность перехода Apple на 64-битные ARM несколько преувеличена. Это важный шаг, но важный, в основном, из-за особенностей ARM и особенностью использования процессора компанией Apple. Тем не менее, некоторые различия имеются. Самым очевидным является 64-битные числовые регистры более эффективно работают с 64-битными числами. Вы можете работать с 64-битными числами и на 32-битном процессоре, но это обычно приводит к работе с двумя 32-битными частями, что работает ощутимо медленнее. 64-битные процессоры, обычно, выполняют операции над 64-битными числами также быстро как и над 32-битными, так что код активно использующий вычисления с 64-битными числами будет работать значительно быстрее.

Не смотря на то, что 64-битность не связана напрямую с объемом адресуемой памяти, она значительно облегчает использование большого объема RAM в рамках одной программы. Программа, запущенная на 32-битном процессоре может адресовать не больше 4GB адресного пространства. Часть памяти выделена под операционную систему и стандартные библиотеки, что оставляет 1-3GB на саму программу. Если у 32-битной системы больше 4GB RAM, то использование всего этого адресного пространства для программы значительно усложняется. Вам придется заняться махинациями вроде последовательного отображение разных частей RAM на часть виртуального адресного пространства или разбивание одной программы на несколько процессов.

Подобные трюки крайне трудозатраны и могут сильно замедлить систему, так что мало кто из программистов реально их использует. На практике, на 32-битных процессорах каждая программа используют до 1-3GB RAM, а вся ценность в обладании большего объема физической оперативной памяти заключается в возможности больше запускать программ одновременно и возможность кеширования больше данных с диска.

Увеличение объема адресного пространства полезно и для систем с небольшим объемом оперативной памяти — memory-mapped файлы, размеры которых могут быть  и больше доступной оперативной памяти, т.к. операционная система реально загружает только те части файла, к которым производились обращения и, кроме того, умеет «вытеснять» загруженные данные обратно в файл, освобождая оперативную память. На 32-битных системах нельзя отобразить файлы размером больше 1-3GB. На 64-битных системах, адресное пространство значительно больше, так что такой проблемы нет.

Увеличение размера указателя может быть и ощутимым минусом: таже программа будет использовать больше памяти (возможно, сильно больше) будучи запущенной на 64 битном процессоре. Увеличение используемой памяти также «забивает» кэш, что снижает производительность.

В двух словах: 64-битность может увеличить производительность некоторых частей кода и упрощает некоторые техники, вроде memory-mapped файлов. Однако, производительность может и пострадать из-за увеличения используемой памяти.


64-битный процессор в iPhone 5S не просто ARM с увеличенным размером регистров, есть и существенные изменения.

Во-первых, отмечу название: официального название от ARM — «AArch64», однако это — глупое название, печатать которое меня раздражает. Apple называет архитектуру ARM64 и я буду называть также.

ARM64 увеличил вдвое число целочисленных регистров. 32-битный ARM предоставляет 16 целочисленных регистров, из которых один — счетчик команд (program counter), еще два используются для указателя на стэк и регистра связи (link register) и 13 регистров общего назначения. В ARM64 32 целочисленных регистра, с выделенным нулевым регистром, регистром связи и регистром указателя кадра (frame pointer register). Еще один регистр зарезервирован платформой, что оставляет 28 регистров общего назначения.

ARM64 также увеличивает число регистров для чисел с плавающей запятой. Регистры в 32-битных ARM несколько странные, так что сложно сравнивать. У 32-битного ARM 32 32-битных регистров с плавающей запятой, которые могут быть представлены как 16 перекрывающихся 64-битных регистров. Кроме того, есть еще 16 независимых 64-битных регистров. ARM64 упрощает это до 32 неперекрывающихся 128-битных регистров, которые могут быть использован для данных меньшего размера.

Число регистров может значительно влиять на производительность. Память значительно медленнее процессора, и чтение/запись памяти занимает значительно больше времени, чем выполнение инструкций процессора. Процессор пытается исправить это при помощи кэшей, но даже самый быстрый кэш значительно медленнее регистров процессора. Больше регистров — больше данных могут храниться внутри процессора. Насколько это влияет на производительность зависит от конкретного кода и эффективности компилятора, который оптимизирует использование регистров. Когда архитектура Intel перешла от 32 к 64 битам, число регистров увеличилось с 8 до 16, и это было значительное изменение производительности. У ARM уже было больше регистров чем у 32-битной архитектуры Intel, так что увеличение регистров хоть  и меньше повлияет на производительность, но это изменение все еще будет заметно.

ARM64 также привнес существенные изменения помимо увеличения числа регистров.

Большинство 32-битных инструкций ARM могут выполняться/не выполняться в зависимости от состояние регистра-условия. Это позволяет транслировать условные выражения (if-statements) без использования ветвления. Предполагалось, что это увеличит  производительность, однако, судя по тому, что в ARM64 от этой возможности отказались, она порождала больше проблем, чем давала пользы.

В ARM64 набор SIMD (одна-инструкция-много-данных) NEON полностью поддерживает стандарт IEEE754 для чисел с плавающей запятой с двойной точностью, в то время как 32-битная версия NEON поддерживала только одинарную точность и не в точности следовала стандарту для некоторых битов.

В ARM64 добавили специализированные инструкции для AES шифрования и SHA-1 & SHA-256 хешей. Не слишком полезное в общем, однако существенный бонус если вы занимаетесь именно этими вопросами.

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


Важно отметить, что A7 включает в себя 32-битный режим совместимости, который позволяет запускать 32-битные приложения без каких либо изменений. Это означает, что iPhone 5S может исполнять любые старые приложения без какого-то влияния на производительность.


Apple использует преимущества новой архитектуры в своих библиотеках. Так как им нет надобности беспокоиться о бинарной обратной совместимости при таких изменениях, это отличное время чтобы внести изменения которые в противном случае «поломали» уже существующие приложения.

В Max OS X 10.7 Apple ввела меченные указатели (tagged pointers). Меченные указатели позволяют хранить некоторые классы с небольшим количеством данных в экземпляре напрямую в указателе. Это позволяет избежать выделений памяти в некоторых случаях, например NSNumber и может дать существенный прирост производительности. Меченные указатели поддерживаются только на 64-битной платформе, частично из-за вопроса производительности, а частично из-за того что в 32-битном указателе не так много остается места под «метки». Видимо по-этому, у iOS не было поддержки меченных указателей. Таким образом, в ARM64 в рантайме Objective-C включена поддержка меченных указателей, что дает те же преимущества, что в Mac.

Не смотря на то, что размер указателя составляет 64 бита, не все эти биты на самом деле используются. В Mac OS X на x86-64 используется только 47 битов. В iOS на ARM64 используется еще меньше — только 33 бита. Если маскировать эти биты каждый раз перед использованием то можно использовать остальные биты чтобы хранить дополнительные данные. Это позволило внести одно из самых значительных изменений в рантайм Objective-C за всю его историю.


Большая часть информации в этой секции почерпана из статьи Грега Паркера. Во первых, для освежения памяти: объекты в Objective-C представляют выделенные блоки памяти. Первый часть, размером с указатель, это isa. Обычно, isa это указатель на класс объекта. Чтобы узнать больше о том, как объекты хранятся  в памяти, читайте мою другую статью.

Использовать весь размер указателя на указатель isa несколько расточительно, особенно на 64-битной платформе, которая не использует все 64-бита. ARM64 на iOS реально использует 33 бита, оставляя 31 бит для других вещей. Классы в памяти выровнены по границе 8 байт, так что последние 3 бита можно отбросить, что дает 34 бита из isa доступные для хранения дополнительной информации. И Apple-овский рантайм в ARM64 использует это для повышения производительности.

Наверно, самой важной оптимизацией стало встраивание (inline) счетчика ссылок. Практически все объекты в Objective-C обладают счетчиком ссылок (за исключение неизменяемых объектов, таких как литералы NSString) и операции retain/release, которые меняют этот счетчик случаются очень часто. Это особенно критично для ARC, который вставляет вызовы retain/release чаще, чем бы это делал программист. Таким образом, высокая производительность retain/release методов крайне важна.

Обычно, счетчик ссылок не хранится в самом объекте. Можно было бы хранить счетчик ссылок в самом объекте, но это заняло бы слишком много места. Это не так важно сейчас, но тогда, давно, это было весьма существенно. Из-за этого, счетчик ссылок хранится во внешней хеш-таблице. Каждый раз при вызове retain, производятся следующие действия:

  1. Получить глобальную хеш-таблицу счетчиков указателей
  2. Заблокировать хеш-таблицу, чтобы операция была потокобезопасной
  3. Найти в хеш-таблице счетчик для заданного объекта
  4. Увеличить счетчик на один и сохранить его обратно в таблицу
  5. Отпустить блокировку хеш-таблицы

Достаточно медленно! Реализация хеш-таблицы счетчиков сделана очень эффективной, для хеш-таблицы, но это все еще значительно медленнее прямого доступа к памяти. В ARM64, 19 бит указателя isa используются для хранения счетчика ссылок. Это означает, что вызов retain упрощается до:
  1. Произвести атомарное увеличение части isa-поля

И все! Это должно быть гораздо, гораздо быстрее.

На самом деле все несколько сложнее из-за разных краевых случаев, которые тоже надо обработать. По-настоящему последовательность действий приблизительно такая:

  1. Последний бит в isa говорит, используются ли дополнительные биты в isa для хранения счетчика ссылок. Если нет, используется старый алгоритм с хеш-таблицами.
  2. Если объект уже удаляется, ничего не выполняется
  3. Если счетчик переполняется (что случается редко, но вполне возможно при 19 битах), используется старый алгоритм с хеш-таблицей
  4. Произвести атомарное изменение isa на новое значение

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

Использование оставшихся неиспользованных под счётчик ссылок биты в isa позволило ускорить деаллокацию объектов. Теоретически, нужно совершить кучу действий когда объект в Objective-C удаляется и возможность пропустить ненужные может значительно увеличить производительность.  Эти шаги:

  1. Если у объекта не было ассоциированных объектов, установленных используя objc_setAssociatedObject, их не надо удалять.
  2. Если объект не обладает C++-деструктором (который вызывается при dealloc), его тоже не нужно вызывать.
  3. Если на объект ни разу не ссылались слабым (__weak) указателем, то эти указатели не надо обнулять.

Раньше, все эти флаги хранились в классах. Например, если хоть один экземпляр класса когда-либо хранил ассоциированные объект, то все экземпляры этого класса удаляли ассоциированные объект при деаллокации. Отслеживание этих флагов для каждого экземпляра в отдельности позволило вызывать соответствующие методы только для тех экземпляров, для которых это действительно было нужно.

Суммарно, это существенный выигрыш. Мои бенчмарки показали, что создание и удаление простого объекта  занимает 380нс на 5S в  32-битном режиме, в то время как в 64-битном только 200нс. Если хоть один экземпляр когда-либо имел слабую ссылку на себя, то в 32-битном режим время удаления для всех увеличивалось до 480нс, в то время как в 64-битном режиме время осталось в районе 200нс для всех экземпляров, на которых слабых ссылок не было.

Короче говоря, улучшения в рантайме таковы, что в 64-битном режиме время аллокации занимают 40-50% от времени аллокации в 32-битном режиме. Если ваше приложение создает и удаляет много объектов, это может оказаться существенным.


64-битность A7 не просто маркетинговая уловка, но это и не поражающий воображение прорыв который позволит создавать новый класс приложений. Истина, как всегда, лежит посередине.

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

Архитектура ARM изменилась не только в 64-битности. Увеличенное число регистров и пересмотренный, модернизированный набор инструкций дает неплохой прирост производительности по сравнению с 32-битным ARM.

Apple использовала переход на новую архитектуру для улучшения в рантайме. Основное изменение — встраиваемый (inlined) счетчик ссылок, который позволяет избежать дорогого поиска по хеш-таблице. Так операции retain/release очень часты в Objective-C, это существенный выигрыш. Удаление ресурсов в зависимости от флагов делает удаление объектов почти вдвое быстрее. Меченные (tagged) указатели также добавляют производительность и уменьшают потребление памяти.

ARM64 — приятное добавление от Apple. Мы все знали, что это рано или поздно случится, но мало кто ожидал что так скоро. Но оно есть, и это отлично.

Отправить ответ

avatar
  Подписаться  
Уведомление о