Содержание статьи
Введение
Пользуясь самыми разными *nix-системами на протяжении последних десяти лет, я всегда удивлялся, с каким упорством некоторые, казалось бы, грамотные пользователи смартфонов пытаются впихнуть общественности свои идеи оптимальной настройки Android и лежащего в его основе ядра Linux. И ладно бы дело ограничивалось легким тюнингом подсистемы управления виртуальной памятью или включением экспериментальных опций. Нет, обычно нам предлагают применить длиннющие скрипты, изменяющие буквально каждую переменную ядра, перемонтирующие файловые системы с разными странными опциями, включающие swap, активирующие различные системные демоны и выполняющие еще миллиарды различных операций.
Нет, ну можно, конечно, предположить, что ядро Linux, Android и фирменные прошивки для смартфонов разрабатывают безграмотные идиоты, работу которых необходимо кардинальным образом переделывать, но на практике почему‑то оказывается, что самые известные инструменты тюнинга, опубликованные на XDA, — это не что иное, как сборная солянка из огромного количества разрозненных рекомендаций, придуманных непонятно кем и неизвестно зачем. Абсурд ситуации доходит того, что в этих инструментах можно обнаружить строки, без изменений скопированные из скриптов для увеличения производительности Linux-сервера в условиях высоких нагрузок (я не шучу, взгляни на содержимое известного скрипта ThunderBolt!).
В целом ситуация более чем запутанная. Все советуют всё, никто не советует ничего, а те, кто что‑то понимают, сидят и, попивая чай, смеются над происходящим балаганом. Но попробуем все‑таки разгрести всю эту кашу.
Swap
Начнем со swap — самой абсурдной идеи из всех, что только можно придумать для применения в смартфонах. Ее смысл в том, чтобы создать и подключить файл подкачки, за счет чего удастся освободить полезное пространство в оперативной памяти. Сама по себе идея, конечно, здравая, но только если речь идет о сервере, которому интерактивность никуда не упирается. На смартфоне регулярно используемый файл подкачки приведет к неиллюзорным лагам, возникающим вследствие промахов мимо кеша, — достаточно представить, что будет, если приложение попытается отобразить одну из своих пиктограмм, а она окажется в свопе, который придется вновь загружать с диска, предварительно освободив место путем помещения в своп данных другого приложения. Ужас.
Некоторые юзеры могут возразить, что на самом деле после включения swap никаких проблем не возникает, но за это надо благодарить механизм lowmemorykiller, который регулярно убивает особо раздувшиеся и давно не используемые приложения. Благодаря ему девайс с 1 Гб памяти может никогда и не дойти до необходимости сброса данных в своп. Он же и является причиной того, почему в отличие от Linux-десктопа в Android своп не нужен.
Вердикт: очень глупая идея, реализация которой чревата серьезными лагами.
zRAM
Swap действительно очень медленный, и даже на десктопе его существование зачастую неоправданно, но что, если обмануть систему? Создадим виртуальный диск прямо в оперативке с встроенной функцией сжатия данных, подключим его как swap — и вуаля. Функция сжатия данных довольно дешева даже для современных мобильных процессоров, поэтому мы сможем расширить размер оперативки практически без потерь производительности.
Идея настолько правильная, что даже Google рекомендует применять zRAM для основанных на KitKat устройствах в том случае, если объем оперативки не превышает 512 Мб. Загвоздка только в том, что способ работает лишь для современных бюджетников, то есть устройств, основанных на многоядерных бюджетных процах от какой‑нибудь MTK и 512 Мб оперативки. В этом случае поток шифрования можно вынести на отдельное ядро и вообще не париться о производительности.
На устаревших устройствах с одним ядром, для которых «гуру форумов» и рекомендуют применение данной технологии, мы вновь получим лаги, причем в довольно большом количестве. То же, кстати, относится и к технологии KSM (Kernel SamePage Merging), которая позволяет объединять одинаковые страницы памяти, освобождая таким образом пространство. Она также рекомендована Google, но на старых девайсах приводит к еще большим лагам, что вполне логично, учитывая постоянно активный ядерный поток, который непрерывно ходит по памяти в поисках дубликатов страниц (а так ли много этих дубликатов на самом деле?).
Вердикт: зависит от устройства, в большинстве случаев замедляет систему.
Seeder
В свое время это приложение наделало много шума и породило множество аналогов. В Сети появилось огромное количество сообщений о якобы феноменальном приросте производительности смартфона после его установки. Доморощенные сборщики кастомных прошивок начали включать его в свои сборки, а автор был объявлен спасителем. И все это при том, что Seeder не выполнял никаких грязных хаков, а просто исправлял один глупый баг Android.
Если вкратце, то баг состоял в том, что некоторые высокоуровневые компоненты среды исполнения Android активно использовали файл /dev/random для получения энтропии/соли. В какие‑то моменты буфер /dev/random опустошался, и система оказывалась заблокирована до момента его заполнения необходимым количеством данных. А так как заполнялся он тем, что поступало с разных датчиков, кнопок и сенсоров смартфона, то времени на эту процедуру уходило столько, что пользователь успевал заметить лаг.
Для решения этой проблемы автор Seeder взял Linux-демон rngd, скомпилировал его для Android и настроил так, чтобы он брал случайные данные из гораздо более быстрого (но и намного более предсказуемого) /dev/urandom и каждую секунду сливал их в /dev/random, не позволяя последнему истощиться. Как результат — система никогда не испытывала недостатка в энтропии и спокойно работала.
Данный баг был закрыт Google еще в Android 3.0, и, казалось бы, нам незачем вспоминать о Seeder. Но дело в том, что приложение с тех пор активно развивалось и даже сегодня рекомендуется многими «экспертами» для применения. Более того, у приложения появилось несколько аналогов (например, sEFix), а многие создатели скриптов/инструментов для ускорения до сих пор включают подобную функциональность в свои творения. Иногда это тот же самый rngd, иногда — демон haveged, иногда просто симлинк /dev/urandom на /dev/random.
Все, кто пробовал, наперебой кричат об эффективности решения, однако, если верить Рикарду Серкейре (Ricardo Cerqueira) из компании Cyanogen, в современных версиях Android /dev/random используется всего тремя компонентами: libcrypto (для шифрования SSL-соединений, генерации ключей SSH и так далее), wpa_supplicant/hostapd (для генерации WEP/WPA-ключей) и несколькими библиотеками для генерации случайных ID при создании файловых систем ext2/3/4.
Эффективность приложения в современном Android, по его мнению, связана вовсе не с пополнением пула /dev/random, а с тем, что rngd постоянно пробуждает устройство и заставляет его повышать частоту процессора, что позитивно сказывается на производительности и негативно на батарее.
Вердикт: плацебо.
Odex
Стоковые прошивки смартфонов всегда одексированы. Это значит, что наряду со стандартными для Android пакетами приложений в формате APK в каталогах /system/app/ и /system/priv-app/ (начиная с KitKat) также находятся одноименные файлы с расширением odex. Они содержат так называемый оптимизированный байт‑код приложения, уже прошедший через верификатор и оптимизатор виртуальной машины и записанный в обособленный файл (это делается с помощью утилиты dexopt).
Смысл существования файлов odex в том, чтобы разгрузить виртуальную машину и таким образом ускорить запуск приложений (стоковых). С другой стороны, файлы odex мешают вносить в прошивку модификации, создают проблемы с обновлением, и по этой причине многие кастомные ROM’ы (включая CyanogenMod) распространяются без них. Вернуть (точнее, сгенерировать) файлы odex можно разными способами, в том числе с помощью простых утилит/скриптов вроде Odexer Tool. Пользоваться ими легко, и многие «эксперты» советуют это делать.
Проблема только в том, что это чистейшее плацебо. Не обнаружив odex-файлов в каталоге /system, система сама создаст их при следующей загрузке и поместит в каталог /system/dalvik-cache/. Именно этим она занимается, когда при загрузке новой прошивки на экране появляется сообщение «Идет оптимизация приложений...». В отношении приложений из маркета это тоже, кстати, работает. Но на этапе установки софта.
Вердикт: плацебо.
Твики lowmemorykiller
Реализация многозадачности в Android сильно отличается от других мобильных ОС и основана на классической модели. Приложения могут спокойно работать в фоне, в системе нет никаких ограничений на их количество, функциональность при переходе к фоновому исполнению не урезается. Все, как на десктопе, за исключением одной детали: система имеет полное право убить любое фоновое приложение в случае недостатка оперативной памяти или (начиная с KitKat) излишней жадности приложения к ресурсам.
Этот механизм, названный lowmemorykiller, был придуман для того, чтобы, сохраняя черты полноценной многозадачной ОС, Android мог нормально жить в условиях ограниченного объема памяти и отсутствующего swap-раздела. Пользователь может спокойно запускать любые приложения и быстро переключаться между ними, а система сама позаботится о завершении давно не используемых приложений и о том, чтобы в устройстве всегда оставалась свободная память.
В первые годы существования Android назначение данного механизма для многих пользователей было непонятным, поэтому стали популярными так называемые таск‑киллеры — приложения, которые время от времени просыпались и завершали все фоновые приложения. Профитом в данном случае считалось большое количество свободной оперативки, что воспринималось как плюс, хотя никаких плюсов в этом, конечно же, не было. Зато было много минусов в виде более долгого переключения между приложениями, повышенного расхода заряда батареи и проблем c пробуждением владельца по утрам (будильник тоже убивался).
Со временем понимание принципов многозадачности пришло, и от таск‑киллеров постепенно отказались. Однако их быстро сменил другой тренд — тюнинг самого механизма lowmemorykiller (например, с помощью приложения MinFreeManager). Основная идея метода в том, чтобы приподнять границы заполнения оперативной памяти, при достижении которых система начнет убивать фоновые приложения. Этакий способ «и нам и вам», который позволяет освободить немного памяти штатными средствами, не нарушая идей многозадачности Android.
Но к чему это в итоге приводит? Допустим, стандартные значения границ заполнения памяти — это 4, 8, 12, 24, 32 и 40 Мб, то есть при достижении свободного объема памяти 40 Мб будет убито одно из кешированных приложений (загружено в памяти, но не запущено, это такая оптимизация Android), при 32 — Content Provider, не имеющий клиентов, 24 — одно из редко используемых фоновых приложений, затем в расход идут сервисные процессы приложений (например, сервис музыкального проигрывателя), видимые на экране приложения и текущее запущенное приложение. Разница между последними двумя в том, что «текущее» — это приложение, с которым в данный момент имеет дело юзер, а «видимое» — это то, что, например, имеет уведомление в строке состояния или отображает поверх экрана какую‑либо инфу.
В целом все это значит, что в смартфоне всегда будет свободно 40 Мб памяти, которых вполне достаточно для того, чтобы вместить еще одно приложение, после чего проснется поток LKM и начнет очистку памяти. Все ОК, все довольны. Система по максимуму использует память. А теперь представим, что будет, если юзер воспользуется советом доморощенного «эксперта» и поднимет эти значения так, что последнее будет составлять, ну, допустим, 100 Мб (обычно повышаются только три последних значения). В этом случае произойдет одна простая вещь: юзер потеряет 100 – 40 = 60 Мб памяти устройства. Вместо того чтобы использовать это пространство для хранения фоновых приложений, что полезно, так как сокращает время переключения на них и заряд батареи, система будет оставлять его свободным непонятно для чего.
Справедливости ради стоит сказать, что тюнинг LKM может быть полезен для девайсов с совсем уж небольшим объемом памяти (меньше 512) и Android 4.X на борту или для временного увеличения порогов. Некоторые разработчики твиков прямо рекомендуют использовать «агрессивные» настройки только в случае запуска тяжелого софта вроде hi-end игр, а все остальное время оставаться на стандартных. В этом действительно есть смысл.
Вердикт: лучше не трогать.
Твики I/O
В скриптах, публикуемых на форумах, можно часто встретить твики подсистемы ввода‑вывода. Например, в том же скрипте ThunderBolt! есть следующие строки:
echo 0 > $i/queue/rotational;echo 1024 > $i/queue/nr_requests;
Первая дает планировщику ввода‑вывода понять, что он имеет дело с твердотельным диском, вторая увеличивает максимальный размер очереди ввода‑вывода с 128 до 1024 (переменная $i в командах содержит путь к дереву блочного устройства в /sys, например /sys/block/mmcblk0/, скрипт проходит по ним в цикле). Далее по тексту можно встретить следующие строки, относящиеся к планировщику CFQ:
echo 1 > $i/queue/iosched/back_seek_penalty;echo 1 > $i/queue/iosched/low_latency;echo 1 > $i/queue/iosched/slice_idle;
Далее следует еще несколько строк, относящихся к другим планировщикам (кстати, обрати внимание на совершенно лишние точки с запятой в конце команд). Что во всех этих строках не так? Первые две команды бессмысленны по двум причинам:
- Планировщики I/O в современном ядре Linux сами способны понять, с каким типом носителя информации они имеют дело.
- Такая длинная очередь ввода‑вывода (1024) совершенно бессмысленна на смартфоне. Более того, она бессмысленна даже на десктопе и применяется на высоконагруженных серверах (из рекомендаций по настройке которых она, видимо, и попала в данный скрипт).
Последние три бессмысленны по той простой причине, что для смартфона, где фактически нет разделения приложений по приоритетам на ввод‑вывод и нет механических накопителей, лучший планировщик — это noop, то есть простая FIFO-очередь — кто первый обратился к памяти, тот и получил доступ. И у данного планировщика нет каких‑то особенных настроек. Поэтому все эти многоэкранные списки команд лучше заменить на один простой цикл:
for i in /sys/block/mmc*; doecho noop > $i/queue/scheduler
echo 0 > $i/queue/iostats
done
Кроме включения планировщика noop, для всех накопителей он отключает накопление статистики I/O, что также должно позитивно сказаться на производительности (хотя это всего лишь капля в море, которая будет совершенно незаметна).
Еще один твик, который часто можно найти в скриптах тюнинга производительности, — это увеличение значения readahead для карты памяти до 2 Мб. Механизм readahead предназначен для заблаговременного чтения данных с носителя еще до того, как приложение запросит доступ к этим данным. Если ядро видит, что кто‑то достаточно долго читает данные с носителя, оно пытается вычислить, какие данные понадобятся приложению в дальнейшем, и заранее загрузит их в оперативку, позволяя таким образом сократить время их отдачи.
Звучит круто, но, как показывает практика, алгоритм readahead очень часто ошибается, что приводит к лишним операциям ввода‑вывода и расходу оперативной памяти. Высокие значения readahead (1–8 Мб) рекомендуются к применению на RAID-массивах, тогда как на десктопе или смартфоне лучше все оставить как есть, то есть 128 Кб.
Вердикт: кроме noop, не нужно ничего.
Оптимизация баз данных
Скрипт для оптимизации баз данных настроек системы и приложений. Для работы, естественно, требуется root и BusyBox.
#!/system/bin/shfor i in \`busybox find /data -iname "*.db"`;do \ /system/xbin/sqlite3 $i 'VACUUM;'; /system/xbin/sqlite3 $i 'REINDEX;';done;
Твики системы управления виртуальной памятью
Кроме подсистемы I/O, принято также тюнинговать подсистему управления виртуальной памятью. Зачастую изменению подвергаются только две переменные ядра: vm.dirty_background_ratio и vm.dirty_ratio, которые позволяют регулировать размер буферов для хранения так называемых грязных данных, то есть тех данных, которые были записаны на диск приложением, но еще до сих пор находятся в оперативной памяти и ждут, пока они будут записаны на диск.
Стандартные значения этих переменных в десктопных Linux-дистрибутивах и Android примерно следующие:
vm.dirty_background_ratio = 10
vm.dirty_ratio = 20
Это значит, что при достижении размера буфера «грязных» данных в 10% от всего объема оперативки проснется ядерный поток pdflush и начнет записывать данные на диск. Если же операции записи данных на диск будут слишком интенсивными и, даже несмотря на работу pdflush, буфер будет продолжать расти, то при достижении 20% от объема оперативки система переключит все последующие операции записи в синхронный режим (без предварительной буферизации) и работа пишущих на диск приложений будет заблокирована до того момента, пока данные не будут записаны на диск (в терминологии Android это принято называть лагом).
При этом важно понимать, что, даже если размер буфера не достиг 10%, система так или иначе запустит поток pdflush через 30 с. Что нам дают эти знания? Фактически ничего, что мы могли бы использовать в своих целях. Комбинация 10/20% вполне разумна и, например, на смартфоне с 1 Гб памяти составляет примерно 100/200 Мб памяти, чего более чем достаточно в условиях редких всплесков записи, скорость которых зачастую ниже скорости записи в системную NAND-память или SD-карту (при установке софта или копировании файлов с компа). Но создатели скриптов оптимизации с этим, конечно же, не согласны.
Например, в скрипте Xplix можно найти примерно такие строки (в оригинале они намного длиннее из‑за проверок на количество оперативной памяти и использования BusyBox):
sysctl -w vm.dirty_background_ratio=50
sysctl -w vm.dirty_ratio=90
Данные команды применяются к устройствам с 1 Гб памяти, то есть устанавливают лимиты «грязного» буфера, равные (примерно) 500/900 Мб. Такие высокие значения абсолютно бессмысленны для смартфона, так как работают только в условиях постоянной интенсивной записи на диск, то есть опять же для высоконагруженного сервера. В ситуации со смартфоном они будут ничем не лучше стандартных. Кстати, в скрипте ThunderBolt! применяются гораздо более разумные (и близкие к стандартным) значения, но я сомневаюсь, что от их применения пользователь заметит хоть какую‑то разницу:
if [ "$mem" -lt 524288 ];thensysctl -w vm.dirty_background_ratio=15; sysctl -w vm.dirty_ratio=30;elif [ "$mem" -lt 1049776 ];then sysctl -w vm.dirty_background_ratio=10; sysctl -w vm.dirty_ratio=20;else sysctl -w vm.dirty_background_ratio=5; sysctl -w vm.dirty_ratio=10;fi;
Первые две команды выполняются на смартфонах с 512 Мб оперативки, вторые — с 1 Гб, третьи — с более чем 1 Гб. Но на самом деле есть только одна причина изменять стандартные значения — девайс с очень медленной внутренней памятью и/или картой памяти (привет китайцам). В этом случае разумно разнести значения переменных, то есть сделать примерно так:
sysctl -w vm.dirty_background_ratio=10
sysctl -w vm.dirty_ratio=60
Тогда при резких всплесках операций записи система, не успевая записывать данные на диск, до последнего не будет переключаться на синхронный режим, что позволит уменьшить лаги приложений при выполнении записи.
Вердикт: лучше не трогать.
Бесполезные настройки build.prop
LaraCraft304 с форумов XDA Developers провела исследование и выяснила, что внушительное количество настроек /system/build.prop, которые рекомендуют к применению «эксперты», вообще не существуют в исходном тексте AOSP и CyanogenMod. Вот их список:
- ro.ril.disable.power.collapse
- ro.mot.eri.losalert.delay
- ro.config.hw_fast_dormancy
- ro.config.hw_power_saving
- windowsmgr.max_events_per_sec
- persist.cust.tel.eons
- ro.max.fling_velocity
- ro.min.fling_velocity
- ro.kernel.checkjni
- dalvik.vm.verify-bytecode
- debug.performance.tuning
- video.accelerate.hw
- ro.media.dec.jpeg.memcap
- ro.config.nocheckin
- profiler.force_disable_ulog
- profiler.force_disable_err_rpt
- ersist.sys.shutdown.mode
- ro.HOME_APP_ADJ
Выводы
Существует огромное количество и более мелких оптимизаций, включая «тюнинг» сетевого стека, изменение переменных ядра Linux и Android (build.prop), но 90% из них не оказывают никакого влияния на реальную производительность устройства, а остальные 10% либо улучшают одни аспекты поведения устройства в ущерб других, либо настолько незначительно повышают производительность, что ты этого даже не заметишь. Из того, что реально действует, можно отметить следующее:
- Разгон. Небольшой разгон позволяет повысить производительность, а андервольтинг — сохранить немного батарейки.
- Оптимизация баз данных. Сильно сомневаюсь, что это даст заметный прирост скорости работы, но теория говорит нам, что работать должно.
- Zipalign. Забавно, но, несмотря на встроенную в Android SDK функцию выравнивания контента внутри APK-файлов, в маркете можно найти большое количество софта, не прошедшего через zipalign.
- Отключение ненужных системных сервисов, удаление неиспользуемых системных и редко используемых сторонних приложений (об этом я уже писал в одной из прошлых статей).
- Кастомное ядро с оптимизациями под конкретный девайс (опять же не все ядра одинаково хороши).
- Уже описанный планировщик ввода‑вывода noop.
- Алгоритм насыщения TCP westwood+. Есть доказательства, что в беспроводных сетях он намного эффективнее применяемого в Android по умолчанию Cubic. Доступен в кастомных ядрах.
А остальное лучше не трогать.