Comments 58
dex, я правда не копал именно этот баг сам, за объяснения почему так спасибо Максиму из российского офиса Google
UFO landed and left these words here
Учитывая что множество моделей никогда не будет обновлено на 2.2, то даже через год, скорее всего, модели с прошивкой < 2.2 будут составлять не менее 30% рынка.

Т.е. это безусловно приятно, но в ближайшее время не имеет коммерческой ценности. Все равно нужно реализовывать докачку самостоятельно.
Спасибо, Андрей. Хорошая подборка. По поводу «магии» не совсем понял

В результате вы имеет два экземпляра Task в памяти


В яве — не экземпляры, а ссылки. Видимо, имеешь ввиду, что GC сразу его не убивает? Так это правильно — даже если сделаешь task = null, GC не сразу будет вызван, как и в случае out of scope. Так что это не баг, а скорее особенность явы
Ну одно другому не противоречит, ссылки на экземпляры :)

В первом случае, у нас первый экземпляр Task будет удален GC только после создания второго экземпляра Task. В магическом случае, мы даем GC шанс его удалить в процессе создания нового объекта. Если у нас объекты занимают приличное количество памяти это очень критично.
Я думаю будет тоже самое. Ява все равно создаст байт код соответствующий

Task tmp = queue.getTaskBlocking()
tmp.execute()


А dex опять будет переменная вынесена за пределы цикла.

Не совсем понимаю работу магического класса. Видимо, где-то ошибка. Почему не используется метод getMagic()? В метод Magic.doMagic(task) передается null — это факт. Тогда к чему это условие? if (obj != null) n++;
На оба вопроса — чтобы обмануть оптимизатор, чтобы тот не повыкидывал код.
Минутку,
Task task;

не создаёт экземпляра, а просто объявляет переменную под указатель на этот экземпляр.

Тут компилятор всё делает правильно — выделение памяти под указатель внутри цикла множество раз менее эффективно, чем выделение памяти под указатель один раз, до цикла. Кроме того, такая оптимизация упрощает жизнь для GC — он будет срабатывать на объект-без-ссылок, а не на объект-который-больше-не-понадобится
Не в этом дело.
Смотрите, прошли по циклу один раз, создали таск. закончили цикл.
Пошли по циклу второй раз: переменная таск указывает на экземпляр таска и поэтому таск не может быть уничтожен. До тех пор, пока не выполнится конструктор второго таска и переменная не будет переписана. То есть во время выполнения второго (и каждого последующего) прохода по циклу есть момент, когда в памяти гарантировано два экземпляра объекта таск.
Если же удалось бы обнулить переменную в конце цикла (или как в неоптимизированной версии — выход за границу видимости тоже равен освобождению указателя), то у сборщика был бы шанс утилизировать память первого таска либо до конструкции второго, либо во время.
Ваша правда.

Действительно, момент не очевидный.
В том плане, обычному, не андроидному, джависту подобное и в голову не придёт. Потому оно и работает подобным образом, подозреваю.
Собственно, сам объект может создаться только из конструктора.
Единственное место, где может быть конструктор — где-то в недрах queue.getTaskBlocking().
Или этот вызов возвращает полную копию внутреннего объекта Task?
Я в яве вообще ничего не понимаю, но вот такое решение не должно убрать проблему с out of scope?

Task task;
while(true) {
task = queue.getTaskBlocking();
task.execute();
task = null;
}
Разве в прошивке 2.2 не поправили проблемы с мултитачем? Я сейчас посмотрел на своём N1 с 2.2, кажется, проблемы больше нет или я что-то неправильно сделал.
небольшой оффтоп:
что означает «оверхэд»? гугль молчит, а слышу термин часто…
В данном контексте, это некоторое время которое требуется операционной системе чтобы «подготовиться» к считыванию данных.

Если на пальцах, допустим физически считать данные с флэшки у нас занимает по времени X мс, то для того чтобы это сделать в программе нам нужно X+Y мс, где Y и есть «оверхэд». Причем, часто данный оверхед не завистит от размера данных.

overhead overheads 1) непроизводительные издержки; накладные расходы
Это еще и автору по башке на до дать. ;) Все-таки тянуть англизмы без особой нужды — портить родную речь.
Это дело привычки, особенно если часто читаешь английскую документацию :) Тот же mutex например: неужели нужно переводить как «взаимное исключение»?
«Издержки». Например, если вы шлёте данные в IP-пакете то с точки зрения вашего приложения оверхэд пересылки равен размеру заголовка IP-пакета. Оверхэд — это обычно объём памяти или трафика. Термин очень относительный и субъективный для приложения :)
Также есть «алгоритмический» (вольное название) оверхэд: это издержки на выполнение действия. Здесь оверхедом можно считать все операции которые напрямую не приводят к результату но по каким-то причинам требуются.
>… добавили туда управление с использование акселерометра.…
>… значение поворота снятые со сенсоров меняются от -10 до +10 градусов.…

Там (допустим, в Desire и Nexus One) два разных датчика — трёхосевой акселерометр и трёхосевой магнетометр. Магнетометр в системе притворяется двумя разными устройствами — собственно магнетометром и датчиком ориентации. На самом деле это один и тот же датчик, просто данные в разном формате выдаются.
Соответственно, вы видимо имеете ввиду магнетометр, т.к. с акселерометра никакие градусы не снимешь.
Магнетометр оценивает положение телефона относительно магнитного поля (Земли). Соответственно, если рядом есть металлические предметы, это очень сильно влияет. Динамик телефона играющий, даже на максимальной громкости, никаких заметных искажений не вносит (только что проверил). А вот если, скажем, положить телефон рядом с ноутом — искажения будут очень большие (понятно, почему).
Вот данные на акселерометр и магнетометр в Desire/Nexus, если интересно:
www.bosch-sensortec.com/content/language1/downloads/BMA150_DataSheet_Rev.1.5_30May2008.pdf
www.ic-on-line.cn/iol/datasheet/ak8973_4138699.pdf
Попробовал в двух местах… Нет — разницы нет. Что неудивительно — есть же довольно жесткие требования относительно электромагнитных помех таких девайсов.
Так что изменения показаний исключительно за счёт металлических частей внутри.
Магнитометр срабатывает на постоянное магнитное поле, а требования — на переменное электромагнитное. Помеху будет создавать даже просто кусок металла.
Неприятность с датчиками я на себе испытал, благо для меня было не критично если пальцы вдруг меняются местами.
(странно — хабр съел сообщение. вторая попытка..)

"… добавили туда управление с использование акселерометра.…
… значение поворота снятые со сенсоров меняются от -10 до +10 градусов. ..."

Там (допустим, в Desire и Nexus One) два разных датчика — трёхосевой акселерометр и трёхосевой магнетометр. Магнетометр в системе притворяется двумя разными устройствами — собственно магнетометром и датчиком ориентации. На самом деле это один и тот же датчик, просто данные в разном формате выдаются.
Соответственно, вы видимо имеете ввиду магнетометр, т.к. с акселерометра никакие градусы не снимешь.
Магнетометр оценивает положение телефона относительно магнитного поля (Земли). Соответственно, если рядом есть металлические предметы, это очень сильно влияет. Динамик телефона играющий, даже на максимальной громкости, никаких заметных искажений не вносит (только что проверил). А вот если, скажем, положить телефон рядом с ноутом — искажения будут очень большие (понятно, почему).

Вот данные на акселерометр и магнетометр в Desire/Nexus, если интересно:
www.bosch-sensortec.com/content/language1/downloads/BMA150_DataSheet_Rev.1.5_30May2008.pdf
www.ic-on-line.cn/iol/datasheet/ak8973_4138699.pdf
Не понял почему вы думаете, что два task'a создалось.
Task task создает неинициализированную ссылку, никакого объекта в памяти не создается. task = queue.getTaskBlocking() создает (или просто возвращает) объект и теперь ссылка указывает на него. Компилятор просто убирает лишнее создание ссылок, т.е. даже экономит силы процессора.
По поводу flash, сколько было флешек которые очень активно использовались, ни одна так и померла от активного использования и на windows и на windows mobile.

На какую лучше сменить файловую систему на flash для android что бы всё быстро работало, а не заботилось о том, как бы флешку за 300р. прожила 10 лет?

Много раз видел как пользователи убивают флэш (на примере обычных USB).
Всякого рода бухгалтера и прочие любители работать с базами.
За три месяца — пол года угробить новую флэшку — раз плюнуть.
16 МБ на процесс и это при том что на устройства в среднем ставят 256МБ памяти…
В WM и то в два раза больше можно…
из 256 половину съедает радиомодуль, и ОС
по факту там для приложений около 100мб.
> Я понимаю что описание достаточно туманное
На самом деле, очень хорошее описание — я хоть теперь понял почему так происходит. Ну, можно и вправду разносить управление по противоположным углам экрана в таком случае.
Вы выкладываете свое приложение размером в 10 мегабайт в Android Market, и вам приходит гневное письмо от пользователя

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

Ну и дела…
«Багофича» специфичная для окружения. Запустив инсталлятор размером 10Мб на реальном девайсе с 40+ Мб свободного места ошибки не получим.
Кстати пересобирите iDracula под Android 2.X
ато он даже не находиться сейчас в маркете хотя я даже его честно покупал =)
Спасибо за статью. Очередной раз убедился, что если что-то и писать под Андроид — то это должно быть отдельное, оптимизированное под него приложение, а не просто порт с iOS.

Насчет оптимизации компилером — в яве по идее тоже можно выставлять барьеры различного типа, которые будут влиять на видимость переменных и порядок выполнения кода. Вероятно, при их использовании не нужно будет прибегать к магии :)

Сам за Android пока браться планирую в очень очень отдаленном будущем, но документацию переодически внимательно изучаю. Хотя у меня и есть что портировать туда с iPhone из популярных вещей (не игры), но пока к сожалению средства NDK не позволяют это сделать без титанического эффорта — на Java это вообще не реализуемо, к сожалению.
Если не секрет, что? Интересно было бы знать, что столь сложно реализуется.
Я думаю, что такие же или схожие проблемы будут и у разработчиков под Windows Phone 7 и других мобильных платформ. Обязательно берём на заметку.
>Windows Phone 7 и других мобильных платформ.
Половины того что описано не будет.
Внимание фокус — простое решение магии:

Task task=null;
while(task==null) {
task = queue.getTaskBlocking();
task.execute();
task=null;
}

Всё. Ни один оптимизатор ничего не сделает.
Где выдают призы?
>Причина: 16Мб памяти на процесс (24Мб на N1/Desire), это включает себя «нативную кучу», где хранятся Bitmap (и OpenGL текстуры)

насколько я понмю, панять под «честный native»(c/c++) сильно больше, т.е. мегабайт 50 можно занять
«И будем надеяться что никто не будет использовать ваше приложение месяцами и не убьет свою флэшку.»

Учитывая, что файловая система оперирует кластерами, вряд ли это убьет флэшку, т.к. кластеры всеравно будут браться из разных мест.
Но учитывая, опять же, что работа с кластерами, даст ли данный метод решение проблемы? Разве что будет ниже оверхед на открытие файлов.
Вопрос автору — откуда информация про магические грабли?
Либо вы что-то не доперевели, либо неправильно поняли.

Task task;
while(true) {
task = queue.getTaskBlocking();
task.execute();
}

В каком именно месте после выполнения этого кода появится второй таск?

Единственное, что смущает, так это бесконечный цикл — зачем он тут? Если предполагается, что он выполнится только один раз, то и таск будет всего один, а если несколько раз, то откуда конкретная цифра именно про два таска?

Имхо, единственная разница между оптимизированным и изначальным кодом лишь в том, что после выхода из цикла останется один (а не два, как утверждается) сильнодостижимый таск. Решается эта проблема без всякой магии — выносим объявление переменной в место перед циклом, а после цикла её обнуляем.
Данный пункт вызвал много споров. Я сделаю тут одну попытку, объяснить что я имею ввиду. Если не получиться, то наверно продолжать не буду — подожду время для отдельного поста с тестами.

Смотрите, для кода который вы процитировали, что происходит.

Первая итерация цикла, внутри очереди создается экземпляр класса Task указатель на который записывается в переменную task.

Вторая итерация цикла — внутри очереди начинает создаваться второй экземпляр класса Тask, НО по прежнему в переменной task у нас храниться ссылка на экземпляр созданный в первой итерации.

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

И только после того как создание второго экземпляра объекта будет закончено, и переменная task будет перезаписана GC сможет наконец удалить экземпляр объекта созданного на первой итерации.

Для того, чтобы было понятно, попробуйте пройтись данный код поэтапно представляю что таск занимает 10 мегабайт из 16 возможных.
А, вот теперь понял, что вы имели в виду — в оптимизированном варианте, действительно, будет _момент_, когда в памяти будут одновременно два таска.

таск занимает 10 мегабайт из 16 возможных.

Абсолютно согласен, что в такой ситуации разница между изначальным кодом и оптимизированным критична.

Может, добавите это в пост, а то, боюсь, ещё человек десять зададут схожий вопрос? :)
Only those users with full accounts are able to leave comments. Log in, please.