dr_klm: (Default)
Dr. K. L. Metlov ([personal profile] dr_klm) wrote2010-12-15 02:49 am
Entry tags:

динамическая загрузка Dex файла в Dalvik

В общем и с явовской виртуальной машиной вышло по сценарию -- "хотели как лучше, а получилось -- как всегда." ;-) Вроде бы делали универсальный байткод, который write once, run anywhere... Сделали. Но теперь неймется компании Google, которая мало того, что написала для Явы новую виртуальную машину, да еще к тому-же и совершенно другой архитектуры.

Большинству, конечно, все равно, они свои программы перекомпилируют специальным гуглевским компилятором (который из обычных Java class files делает Dalvik-овские dex файлы). А что делать нам, несчастным писателям компиляторов ? ;-)

Но мало того, то байткод другой архитектуры. Еще и загружать динамически его приходится по-новому. И не просто по-новому, оказывается сам этот байткод Dalvik загрузить непосредственно не может, только из zip архива... В общем, грабли на граблях.

Ну вот я вроде разобрался. Пишу, чтобы зафиксировать. Вам это, скорее всего, будет не интересно. ;-)


Итак, возьмем программу Hello.java с одним единственным методом hello, который очевидно что делает. Задача в том, чтобы загрузить этот файл в Андроид динамически, непосредственно в двоичном виде.

Тоесть загрузить нужно Вот этот zip файл, созданный командой:
javac Hello.java && \
dx --dex --output=Hello.zip Hello.class && \ 
adb -e push Hello.zip /data/local
Чтобы это сделать, нужна вот такая программа SayHello.java, которая компилируется и работает вот так:
javac SayHello.java && \ 
dx --dex --output=SayHello.zip SayHello.class && \ 
adb -e push SayHello.zip /data/local \ 
adb -e shell dalvikvm -classpath /data/local/SayHello.zip SayHello
Note: SayHello.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
43 KB/s (2034 bytes in 0.045s)
Hi ! Im' your "Hello World" loader !
First, I'm hooking the DexFile class...Good.
Second, I'm getting hold of its constructor...Good.
Third, I'm instantiating the DexFile object on "/data/local/Hello.zip"...Good.
Fourth, I'm getting to its loadClass method...Good.
Fifth, loading Hello class from the loaded dex...Good.
Sixth, getting hello method of Hello class...Good.
Seventh, invoking the hello method:
Hello WORLD !
Done. Easy, isn't it ? ;-)

Ну не жуть ли ? ;-)


Так что, наступает эра многоплатформенных компиляторов в явовские байткоды со всеми вытекающими "прелестями". Да, явовский байткод не очень оптимален в плане используемого пространства, но неужели такая оптимизация, всего-лишь в несколько раз, стоила того ? И все равно-ж, гады, в процессе загрузки этого .zip с файлом .dex (вызов конструктора DexFile), они докомпилируют его в "оптимизированный" байткод в формате .odex, который еще хранят в отдельном файле.

Вот такая вот бессмысленная политика (ибо по-другому мне это сложно объяснить). Нет, для меня это, конечно, выгодно. Я то думал проект уже закончен. А тут опа, появляется возможность переписать все заново (с сохранением старого, проверенного и удобного API). Типа раз, и снова в девках. ;-) Хорошо, но бессмысленно.

По-хорошему нужно было спрятать свой dalvik подальше (как implementation detail, раз уж они верят, что именно такой подход даст им performance edge), включить в стандартную поставку перекомпилятор dx в прозрачном режиме и пройти явовский testsuite.

[identity profile] zeux.livejournal.com 2010-12-15 09:09 pm (UTC)(link)
Прошу прощения, забыл. Если стек это не выделенная область памяти, то вообще все достаточно жестоко - надо поддерживать многопоточность, т.е. иметь всю ту механику которая есть сейчас в стиле store queues, load queues, решать load/store конфликты и т.п. - с тем исключением что собственно hazards будут на каждом шагу - записали на стек, прочитали со стека, записали, прочитали, и т.п. - т.е. если сейчас некоторые архитектуры могут себе позволить пенальти в 60 тактов за Load Hit Store (read after write hazard), например, то в стековых пощады не будет.

А, и еще надо уметь все кеши которые есть на верхушку стека (состояния "регистров" т.е. - в стиле "значение есть на int конвеере, можно забирать") уметь сбрасывать по любой записи на стек другой инструкцией этого потока, ну или при загрузке cache line.

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

[identity profile] dr-klm.livejournal.com 2010-12-19 04:40 pm (UTC)(link)
Стэк в памяти, вместо регистров произвольного размера кэш, суперскалярная архитектура с register renaming (sort of). Это то, что я имею в виду. Переключение задач запоминает instruction pointer и stack pointer, два машинных слова (за одну транзакцию на шине удвоенной ширины).

Мне кажется, это должно быть просто и эффективно. По крайней мере, в сравнении с монстрозной архитектурой x86/x87 -- гораздо проще и гораздо эффективнее.

К.Л.М.

[identity profile] dr-klm.livejournal.com 2010-12-20 12:37 am (UTC)(link)
произвольного размера кэш -> кэш произвольного размера (в зависимости от модели процессора)

[identity profile] dr-klm.livejournal.com 2010-12-21 12:03 am (UTC)(link)
Казалось-бы, а почему не пойти дальше, отказавшись (в пользу кэша) не только от регистров, но и от стека тоже, сделав все операции работающими непосредственно с операндами в оперативной памяти*?

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

Стек неявно подразумевает операцию стирания. При правильной реализации кэша в описанной мной стековой машине промежуточные значения (вставленные в стек, а затем убранные из него в процессе расчета) в оперативную память никогда записаны не будут.

К.Л.М.

*При небольших ухищрениях (добавив несколько инструкций), на самом деле, можно сделать и так. Но это будет некрасиво.

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