- Русские Блоги
- Используйте Android studio для просмотра байт-кода Kotlin
- Интеллектуальная рекомендация
- Многослойная презентацияViewController Jap
- Распечатать список с конца до головы
- Типы данных и переменные
- Python Daily Practice (4) -идиомы заполняют музыку
- Kotlin под капотом — смотрим декомпилированный байткод
- Как посмотреть декомпилированный байткод в Intellij Idea?
- object
- extensions
- inline
- infix
- tailrec
- reified
- data class
- destructuring declaration
- operator
- inline class
- Diving deep into Android Dex bytecode
- Analyzing memory and performance of our code at the low-level.
- Introduction
- Setup
- Kotlinc
- Working with the Command Line Compiler
- Every release ships with a standalone version of the compiler. We can download the latest version (…
- Commands
- Verify setup
- Example 1 (where no class is instantiated)
- Example 2 (where extension function is not called… almost)
- Example 3 (where we encounter overhead)
- Example 4 (where we fix overhead from Example 3)
- Conclusion
Русские Блоги
Используйте Android studio для просмотра байт-кода Kotlin
Kotlin — это язык JVM, который продвигал Google, а теперь он является официальным языком разработки, рекомендованным Android. Чтобы лучше изучить Kotlin, вы должны взглянуть на грамматические особенности с точки зрения байт-кода, чтобы вы могли лучше понять. Эта статья научит вас просматривать байт-код, скомпилированный Kotlin через Android Studio.
1. Сначала выберите файл, байт-код которого вы хотите отобразить, а затем щелкните на верхней панели инструментов.Tools -> ВыбратьKotlin -> Щелкните во всплывающем менюShow Kotlin Bytecode。 2. После нажатия появится увеличение панели инструментов в правом углу Android Studio.Kotlin Bytecode Кнопка, после нажатия на нее будет подробно отображаться байт-код файла Kotlin.
3. Информация о байт-коде подробно указана в новом окне, но вам необходимо иметь соответствующие резервы знаний, чтобы прочитать информацию о байт-коде. Для удобства чтения вы можете нажать на новый окно в это время посерединеDecompile Кнопка для преобразования байт-кода в более понятную структуру предложения.
Всего за несколько простых шагов мы можем превратить кажущиеся непонятными грамматические особенности Kotlin в простые для понимания предложения на Java, что может очень помочь нам в процессе изучения Kotlin.
Наконец, если вас интересует Kotlin и вы хотите узнать о Kotlin, вы можете взглянуть на то, что я написалКотлин обучение Серия блогов.Kotlin Learning — основы Kotlin
Интеллектуальная рекомендация
Многослойная презентацияViewController Jap
. Недавно, проект использует многоэтажные прыжки [A presentViewController: B animated: YES] [B presentViewController: C animated: YES] . Проблема в том, где: как это идет прямо к? Я не нашел ме.
Распечатать список с конца до головы
В случае, когда таблица цепи не может изменять дисплей, данные хранения стека могут рассматриваться с рекурсивным методом. Разрешить модификацию структуры ссылки.
Типы данных и переменные
тип данных Компьютерная программа может обрабатывать различные значения. Однако компьютеры могут обрабатывать гораздо больше, чем числовые значения. Они также могут обрабатывать различные данные, таки.
Python Daily Practice (4) -идиомы заполняют музыку
оглавление 1. Одно место 2. Случайное расположение 3. Добавьте баллы для оценки 4. Получение файла 5. Установите уровень сложности. 6. Срок завершения 7. Выберите заполнение пропусков. 1. Одно место Н.
Источник
Kotlin под капотом — смотрим декомпилированный байткод
Просмотр декомпилированного в Java байткода Kotlin едва ли не лучший способ понять как он все-таки работает и как некоторые конструкции языка влияют на перфоманс. Многие само собой уже давно это сделали, так что особенно актуальной данная статья будет для новичков и тех, кто уже давно осилил Java и решил использовать Kotlin недавно.
Я специально упущу довольно избитые и известные моменты так как, наверное, нет смысла в сотый раз писать о генерации геттеров/сеттеров для var и подобных вещах. Итак начнем.
Как посмотреть декомпилированный байткод в Intellij Idea?
Довольно просто — достаточно открыть нужный файл и выбрать в меню Tools -> Kotlin -> Show Kotlin Bytecode
Далее в появившемся окне просто нажимаем Decompile
Для просмотра будет использоваться версия Kotlin 1.3-RC.
Теперь, наконец-то, перейдем к основной части.
object
Я полагаю все, кто имеет дело с Kotlin знает, что object создает синглтон. Однако, далеко не всем очевидно какой именно синглтон создается и является ли он потокобезопасным.
По декомпилированному коду видно, что полученный синглтон похож на eager реализацию синглтона, он создается в тот момент, когда класслоудер загружает класс. C одной стороны static блок выполняется при загрузке класслоудером, что само по себе потокобезопасно. С другой стороны, если класслоудеров больше одного, то и одним экземпляром можно не отделаться.
extensions
Тут в общем все понятно — экстеншны являются просто синтаксическим сахарком и компилируются в обычный статический метод.
Если кого-то смутила строчка с Intrinsics.checkParameterIsNotNull, то и там все прозрачно — во всех функциях с не nullable аргументами Kotlin добавляет проверку на null и кидает исключение если вы подсунули свинью null, хотя в аргументах обещали этого не делать. Выглядит это так:
Что характерно, если написать не функцию, а extension property
То в результате мы получим ровно то же самое, что получили для метода String.getEmpty()
inline
С инлайном все довольно просто — функция, помеченная как inline просто целиком и полностью вставляется в то место, откуда ее вызвали. Что интересно — она также сама по себе компилится в статику, вероятно, для возможности interoperability с Java.
Вся мощь инлайна раскрывается в тот момент, когда в аргументах значится лямбда:
В нижней части опять видна статика, а в верхней видно, что лямбда в аргументе функции также инлайнится, а не создает дополнительный анонимный класс, как в случае с обычной лямбдой в Kotlin.
Примерно на этом познания inline в Kotlin у многих заканчиваются, но есть еще 2 интересных момента, а именно noinline и crossinline. Это ключевые слова, которые можно приставить к лямбде являющейся аргументом в инлайн функции.
При такой записи IDE начинает указывать, что такой инлайн бесполезен чуть менее чем полностью. А компилирует ровно в то же, что и Java — создает Function0. Почему декомпилировалось со странным (Function0)null.INSTANCE; — я без понятия, вероятнее всего это баг декомпилятора.
crossinline в свою очередь делает ровно то же, что и обычный inline (то есть если перед лямбдой в аргументе не писать вообще ничего), за небольшим исключением — в лямбде нельзя писать return, что необходимо для блокирования возможности внезапно завершить функцию, вызывающую inline. В смысле написать-то можно, но во-первых IDE будет ругаться, а во вторых при компиляции получим
Впрочем, байткод у crossinline не отличается от дефолтного инлайна — ключевое слово используется только компилятором.
infix
Инфиксные функции компилируются как и экстеншны в обычную статику
tailrec
tailrec является довольно занятной штукой. Как видно из кода рекурсия просто перегоняется в куда менее читаемый цикл, зато разработчик может спать спокойно, так как ничего не вылетит со Stackoverflow в самый неприятный момент. Другое дело в реальной жизни найти применение tailrec получится редко.
reified
Вообще про саму концепцию reified и для чего это надо можно написать целую статью. Если вкрадце, то доступ к самому типу в Java в compile time невозможен, т.к. до компиляции Java знать не знает что там будет вообще. Котлин — другое дело. Ключевое слово reified может быть использовано только в inline функциях, которые как уже отмечалось просто копируются и вставляются в нужные места, таким образом уже во время «вызова» функции компилятор уже в курсе что именно там за тип и может модифицировать байткод.
Следует обратить внимание на то, что в байткоде компилируется статичная функция с приватным уровнем доступа, а значит из Java такое дернуть не получится. К слову из-за reified в рекламе Kotlin «100% interoperable with Java and Android» получается как минимум неточность.
Может все-таки 99%?
В целом с init все просто — это обычная inline функция, которая отрабатывает до вызова кода самого конструктора.
data class
Честно говоря вообще не хотелось упоминать дата классы, о которых уже столько сказано, но тем не менее есть пара моментов заслуживающих внимания. Во-первых стоит заметить, что в equals/hashCode/copy/toString попадают только те переменные, которые были переданы в конструктор. На вопрос почему так — Андрей Бреслав ответил, что брать еще и поля не переданные в конструкторе сложно и запарно. К слову от дата класса нельзя наследоваться, правда только потому, что при наследовании нагенеренный код не был бы корректным. Во-вторых стоит отметить метод component1() для получения значения поля. Генерируется столько componentN() методов, сколько аргументов в конструкторе. Выглядит бесполезно, но на самом деле нужно это для destructuring declaration.
destructuring declaration
Для примера воспользуемся дата классом из предыдущего примера и добавим следующий код:
Обычно эта возможность пылится на полке, но иногда может быть полезной, например, при работе с содержимым мап.
operator
Ключевое слово operator нужно для того, чтобы переопределить какой-нибудь оператор языка для конкретного класса. Честно сказать я ни разу не видел чтоб это кто-нибудь использовал, но тем не менее такая возможность есть, а магии внутри нет. По сути компилятор просто подменяет оператор на нужную функцию, примерно также как typealias заменяется на конкретный тип.
И да, если вы прямо сейчас подумали о том, что будет если переопределить оператор идентичности ( === который), то спешу огорчить, это оператор, который переопределить нельзя.
inline class
Из ограничений — можно использовать только один аргумент в конструкторе, впрочем оно и понятно, учитывая что инлайн класс это в целом обертка над какой-то одной переменной. Инлайн класс может содержать в себе методы, но они представляют из себя обычную статику. Также очевидно, что для поддержки интеропа с Java добавлены все необходимые методы.
Источник
Diving deep into Android Dex bytecode
Analyzing memory and performance of our code at the low-level.
Introduction
Modern Android development is based on Kotlin, which is interoperable with Java. Whenever we use some cool feature from Kotlin (say High-order functions) under the hood (when running on JVM) the feature is implemented in terms of Java bytecode. This might lead to some overheads in memory and performance if used without caution (for example excessive usage of lambdas with parameters without inlining might produce a lot of anonymous classes and put additional pressure on GC).
In order to get some insight on the performance and memory we can look at Java bytecode (or decompile from it to .java files). This way we might see some additional classes instantiated, or variables etc.
But one thing about analyzing such bytecode is that actually it won’t be run on the device. Before running on Android all the compiled code (.class files) are compiled into .dex files. This is so called Dalvik bytecode.
Dalvik Virtual Machine is where Android apps were run prior to Lollipop. After that we have new Android Runtime (ART) with a lot of different optimizations. And that new runtime is compatible with dex.
But that’s not all — between compiling our code to .class and creating .dex files from it there are additional things to note:
- previously for creating .dex file dex tool was used, now it is replaced with d8 tool.
- also now we have r8 tool which adds additional optimizations to d8 .
So, as you can see from the compilation start till the time when we have our dex file there are quite a lot of tools which can work: kotlinc , javac , r8 .
In this article we’ll try to look at some examples on how to investigate what will be the result of compiling our code.
This article was inspired by series of posts about d8 and r8 by Jake Wharton. And I highly recommend anyone who is interested to read them. Also I encourage you to read content of the links in this article as they will be useful for broader understanding.
Here we’ll go through some practical guide on how to get used to new tools and see the resulting bytecode.
Setup
Kotlinc
First of all we’ll need to download kotlinc — Kotlin compiler for command line. And add it to your system paths.
Working with the Command Line Compiler
Every release ships with a standalone version of the compiler. We can download the latest version (…
In order to run our dex-tools we’ll need to have Java setup. Choose any you’ll get comfortable with and add it to your system paths.
Next we download D8/R8 sources from its repository (see instructions following the link) and build.
NOTE: do not forget that R8 depends on the depot_tools, which you should download and add to your system paths before proceed.
Also to build D8/R8 you’ll need then to execute: “tools/gradle.py d8 r8″.
Jar files which we’ll use will be under /build/libs folder.
Commands
These are the commands which we’ll use throughout the article.
Compile all the kotlin files in the current directory:
Package all the .class files into dex file (with r8 optimizations):
Provided —pg-conf is a proguard rules file, in which we’ll add to keep main function and skip obfuscation (for our readability):
Dump content of .dex file to see its contents.
Verify setup
In order to verify your setup works, you can go through this article and see whether you get similar results. Or try to follow examples below.
Example 1 (where no class is instantiated)
In first our example let’s look at following program:
Here we basically provide input and print incremented value of it.
NOTE: this is a template which we’ll use. We’ll have main function and some Runner class in which there will be some logic we’ll try to test.
We then run our kotlin compiler, run r8 over compiled .class files and then run dexdump and get following result:
It is not very long file (later we won’t paste whole listing as it might be too long).
Here we have two interesting things:
- there is no Runner class instantiated at all
- there is no increment operation in code
While first is pretty easy to see (we just don’t have Runner mentioned), second we’ll try to investigate deeper.
For this let’s look at the content of the main function:
Looking at bytecode one by one:
- [000114] — declaring our main function
- 0000 — loading integer constant 6 into v1 register (so here there is no increment at runtime, during compile time value was calculated and it loaded as constant)
- 0001 — we get Java PrintStream reference and store it in v0 reference
- 0003 — we invoke print method on the v0 (which is PrintStream) providing v1 as param (which is our constant 6)
- 0006 — return from function with void
Not that difficult right? Next let’s look at some other examples.
Example 2 (where extension function is not called… almost)
Next example looks the following way:
We have extension function on String, which we call on some “Hello World” and print result.
During r8 work we’ll see the warning:
And in resulting dex file we’ll see that our extension function is still in the bytecode:
First we see that there is our calculate method and inside main function that method is called with invoke-static .
That happens because kotlin adds Intrinsics checks (to verify that params are not null) and because implementation of Intrinsics wasn’t found by r8 it won’t be able to optimize this code.
If we provided Intrinsics implementation, then there will be some optimization. But instead of doing that (as it will require some additional mangling of our setup) we’ll ask kotlin to not generate Intrinsics code by running:
NOTE: think carefully before doing same in production
After that we’ll see the following bytecode:
Again we have everything optimized and final value is calculated at compile time.
Example 3 (where we encounter overhead)
Next program is the following:
Basically we’d like to calculate sum of arithmetic progression from 0 and step 1.
When we run R8 we’ll see the following warning:
This time as we’ve used sum function which is part of kotlin stdlib in order to have r8 work correctly we’ll need to add kotlin stdlib to classpath.
Download latest kotlin stdlib jar version from maven.
And add it to classpath when r8 works:
Then after compilation we’ll see no warnings. Let’s find our what will be inside our bytecode. Unfortunately the result won’t be that great:
We see that we still have our Runner instantiated, inside we create separate IntRange instance, on which we invoke static method CollectionsKt.sumOfInt .
So in this example we didn’t get precalculated result inlined.
If anyone knows why exactly that happened or what can be added during compilation or r8 to make it work feel free to leave a comment.
Example 4 (where we fix overhead from Example 3)
As we’ve analyzed particular use case in Example 3 and see that there is some room for improvements, let’s try to utilize this:
This is equivalent code to calculate arithmetic sum as in Example 3. Note, that here we also use range inside for-loop.
And here is the result:
What we see is that:
- there is no Runner class instantiated
- there is no IntRange instantiated
- instead we have general for-loop inside bytecode to calculate result
Let’s look one by one what is happening here (to check what particular bytecode operation means you can refer to docs):
- 0000 — we move constant value (0) into register v2 (our result)
- 0001 — same we move constant value (0) into register v0 (our current index)
- 0002 — performs sum of two variables (v2 and v0) storing result in first one (v2) — the result will be 0
- 0003 — move value of constant (5) into register v1 (this is our upper-bound)
- 0004 — we make a comparison of v0 and v1 and if they are equal then we exit loop. This time v0 == 0 and v1 == 5 therefore we still inside loop.
- 0006 — we add value of int 1 to our register v0 storing result in v0 (incrementing index)
- 0008 — we go to the next iteration of the loop (at index 0002) where we again will add current index to result, perform check to exit the loop and repeat.
- After all we print result
So, we did better that before and should be probably a bit happy 🙂
Conclusion
Here in this article we saw in practice how we can analyze dex bytecode and with help of r8 in some cases have it optimized (despite Java bytecode is not).
The key takeaways should be the following: even when you try to optimize your code written in Kotlin to make it faster or consume less memory, and even when you look at Java bytecode after your code is compiled, you should also pay attention to dex bytecode and look at the tools such as r8 to make them do their job while keeping your codebase clean.
This article is not about telling you that you should not optimize your code in the first place (but actually you shouldn’t, write correct first and then optimize where needed), it is mostly about knowledge that current development ecosystems are quite complex and it is difficult to know what is actually happening at the lower levels. Therefore it might be useful to know the tools which might help you to identify places for performance or memory improvements where you need them.
Think about optimizations, write fast code which consumes less memory but don’t forget to profile first.
Thanks for reading!
If you enjoyed this article you can like it by clicking on the👏 button (up to 50 times!), also you can share this article to help others.
Have you any feedback, feel free to reach me on twitter , facebook
Источник