Pull to refresh

Comments 106

PinnedPinned comments

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

Как раз в этом и интерес. Тестов алгоритмов JVM против Native в интернете как грязи. Они все показывают, что JVM в целом не хуже прямого исполнения.

В этом же тесте автор осветил цену управления памятью. Теперь можно смело писать: "производительность реализации с автоматическим управлением памяти до 10-и раз хуже" и ссылаться на эту статью

Выложил оптимизированный код на Котлине, который работает быстрее чем вызов кода на C++
Главное изменение это вынос объекта Complex за тело цикла и его повторное использование.
https://github.com/igormich/JNIvsKotlin/blob//app/src/main/java/ru/tusur/nativevskotlin1/model2/MandelbrotOptimized.kt
Результаты на POCO M3 Pro (без прогрева, значения плавают, но порядок величин сохраняется)

  • Kotlin - 17681ms

  • Оптимизированный Kotlin - 1085ms

  • C++ - 1708ms

PS: PullRequest сделал, интересно как код поведёт себя на других конфигурациях.

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

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

вынос объекта Complex за тело цикла и его повторное использование.

а если на С++ тоже сделать "переиспользуемый" внешний объект ?

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

ЭТО ПРОСТО КРУТО!!! Спасибо, а то напишут статью надергав переводов из документации и управились. Давно хотел попробовать нативный код написать для андроид, но ни разу случай не представлялся, вот воспользуюсь примером :)

Спасибо, я рад, что кому-то это было интересно!

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

з.ы. а как AAB установить ?

з.ы.ы. App Bundle Installer не справился с установкой этого .aab-файла
з.ы.ы.ы Split APKs Installer (SAI) тоже не справился

Просьба скомпилять .apk

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

Поймал. Результаты для Redmi Note 10 Pro (Qualcomm Snapdragon SDM732G, 2300 МГц):
Kotlin 16026 ms
C++ 1717 ms

В эмуляторе MEMU под Win10 @ Ryzen5 какой-то: 49.026 секунд и 4.665.

Xperia 10 iii
kotlin: 14916
++: 1983

GeekBench 1082 ! Очень мощный телефон, спасибо, у меня с таким бенчмарком ничего нет)

Realme gt neo 2 5g.

Qualcomm Snapdragon 870

Было бы ещё интересно увидеть такой же кусок кода на Java, чтобы увидеть разницу Kotlin-Java-C++.

А в целом, что это особо даёт? Главное же, работает программа или нет. Так что всё равно надо будет иметь дело с конкретной реализацией какого-то алгоритма. Можно было бы сравнить скорость алгоритмов с плавающей точкой на float и double, и потом на fixed point. Можно было бы провести более реалистичный тест, сравнив, например, скорости кодирования/деодирования звука кодеками на Java и на C++.

Ещё размер занимаемой памяти интересен. Андроид ограничивает ёё размер на приложение, и даже флаг увеличение размера кучи в манифесте не всегда работает.

важно, не только, что программа работает, но и насколько быстро она работает и сколько ресурсов протребляет при этом

Используемый алгоритм, способ его реализации, тип испольуемых данных играют в этом не последнюю роль.

Kotlin в пике 97,3 МБ в эмуляторе

Похоже, мы можем наблюдать сборку мусора в прямом эфире в виде этой пилы.

C++ 65.8 МБ в эмуляторе

Не такая и большая разница, получается.

Полностью согласен, можно придумать много интересных тестов, возможно займусь этим вопросом)

Спасибо за сравнение, но мне кажется оно не очень корректное в том плане, что код на Котлине изначально не производителен из-за необходимости выделать память в куче на каждой итерации. В то время как вариант на Си работает исключительно на стеке. Даже Android Studio ругается, когда мы в View.onDraw создаем объекты, а тут количество итераций еще больше.

Было бы интересно посмотреть на отличия в следующих случаях:

  1. Использовать пул объектов Complex и переиспользовать их насколько возможно.

  2. Не выделять память в куче на Котлине вообще, использовать value class Complex и упаковывать обе координаты в один long как например у класса Offset в компоузе. Только придется обновить алгоритм, чтобы использовать float.

  3. Сделать из Котлин кода Котлин Нейтив бинарник под андроид androidNativeArm64.

По поводу работы С++ на стэке - насколько мне известно, аллокатор памяти std::vector выделяет память в куче, что касается временных переменных Complex, тестировал вариант с new + delete - не особо влияет на результат.

Эксперимент как раз и был задуман как сравнение работы алгоритма с кучей мелких объектов, которые нужно постоянно создавать на куче и упаковывать в массив. Я осознаю, что если очень постараться, можно оптимизировать код Kotlin, но задумка была сравнить производительность "в лоб".

По поводу Kotlin Native - согласен полностью, очень интересно, были такие мысли, но пока не дошло до дела.

аллокатор памяти std::vector выделяет память в куче

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

Так что если знаете кол-во элементов заранее, то перед заполнением советую сделать что-то вроде .reserve(MAX_SIZE), это избавит от копирований и реаллоакаций, прирост производительности может быть весьма неплохой.

Да, в этом изолированном случае, я точно знал сколько будет элементов в массиве, и понимал что точно не хватит тех 300 тысяч double, которые резервируются в конструкторе, но ведь в те же условия был поставлен алгоритм на JVM, намеренно не подгонял размер вектора под количество, чтобы вектор справлялся с этой проблемой своими силами.

Не эксперт Kotlin, но похоже что там получается массив объектов, а не примитивов, и поэтому тормозит. Если это так, то смысл статьи теряется.

проверить, как реально поведет себя Java машина с объектами

Все верно, так и было задумано, проверить с объектами, а не с примитивами, на примитивах таких тестов хватает на просторах сети.

В таком случае, вывод в статье не имеет смысла:

  1. JVM здесь не при чём, т.к. она просто выполняет код. Если код не оптимальный, то и скорость работы тоже. Garbage in - garbage out.

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

Если делать упор на оптимизацию, то для данного алгоритма есть чисто математические способы оптимизации, которые превзойдут любые "фокусы" в коде. Идея была реализовать простой алгоритм который будет работать именно с объектами - с большим их количеством, большим количеством итераций и сравнить как поведет себя JVM и Garbage Collector в сравнении с Native. Готов согласиться со всем, кроме

она просто выполняет код

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

А что, простите, ещё она делает? Упрощённо, конечно.

Вы говорите: JVM, создай-ка мне мульон объектов в куче! Она создаёт. Вы могли бы сказать: создай мне массив double. И она бы создала, но вы ведь не попросили.

В вашей статье было заявлено сравнение Kotlin и C++. В выводе, почему-то, говорится уже о JVM. Повторюсь, я не эксперт Kotlin, но вывод ваш, по моему мнению, ложен. Если бы там было написано: язык Kotlin плохо подходит для написания числодробилок из-за своих ограничений, я бы с таким выводом согласился.

В начале поста - я осознанно сделал пояснения к выбору алгоритма, пункту номер 4, что здесь будет рассмотрена именно работа с кучей, так как JVM не дает большого выбора как создавать объекты,
замечу что С++ делает все тоже самое, выделяет память в куче и занимается "дробилками".
А в целом да

Kotlin плохо подходит для написания числодробилок из-за своих ограничений,

Отлично сказано, только я бы добавил Kotlin на JVM ART плохо подходит для "зубодробилок", потому как не удивлюсь, если на ПК или Kotlin Native результат будет противоположный

Замечу, что не ставил перед собой цель начать холивар, я просто искал ответ на вопрос на который ответа не было и делюсь своими скромными результатами. Многие задачи и несут в себе "перетусовки" объектов в памяти. Данный алгоритм может и перегибает палку, но зато наглядно.

JVM не дает большого выбора как создавать объекты

Что значит не даёт? Вы про примитивы что-нибудь слышали?

Да про примитивы слышал, но мы говорим про объекты, как я создам примитивный объект пользовательского класса, тут моих знаний не хватает. Я понимаю, что в случае с примитивами картина будет другой, спасибо.

ps. Этот эксперимент - альтернатива тысяче и одному эксперименту с кучкой примитивов, которых и так достаточно.

Да и в котлине нет примитивов)

Это ограничение языка, а не виртуальной машины. Согласны?

Согласен конечно. Если обратите внимание , есть уже вариант где все double хранятся тоже как объекты в векторе std::vector<unique_ptr<double>> вроде близко к тому что происходит в котлин, то есть это уже не примитивы но пока это ничего не дало в плане времени выполнения.

Я не понимаю в чём проблема оптимизировать код под платформу, на которой пишешь.

Почему тогда не сравнили аналогичный код и на Java и на Kotlin Native.

Что-то хабр уже не торт, раньше на хабре были огромные крутые статьи, как создать убер приложение и на какие грабли можно наступить.

Кому надо проверить буквально 1 тест напишут его за 10 минут (+10 минут на каждый возможный язык).

(при этом понимая, что тест не объективный, объяснять почему надеюсь не надо? Если надо сравните результаты бенчмарови для видеокарт с реальным fps в игре)

Вообще не вижу смысла в статье, тем более, что в интернете уже есть все подобные сравнения.

Хотелось бы видеть расширенное сравнение, нет даже, исследование на эту тему. У вас в статье даже профайлер не включён. Не понятно на что тратится и где время.

Сейчас самыми быстрыми считаются C, Rust, C++ и каждый использует очень много оптимизаций, чтобы этого достичь.

Всё начинается ещё с компилятора ещё до написания кода, у jvm в этом есть некое приемущество, но компилятор, каким бы он крутым не был в конце концов выполняет тот, код, который вы написали.

Хотя не мне вас судить, я понимаю всю ответственность большого исследования не делаю его просто потому что слишком многое нужно протестировать бесплатно.

А вот ментейнеры в openjdk так делают:

https://habr.com/ru/articles/348018/

(проблему кстати пофиксили даже в jdk 8)

Хотелось бы видеть расширенное сравнение, нет даже, исследование на эту тему. У вас в статье даже профайлер не включён. Не понятно на что тратится и где время.

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

На счет Kotlin Native - самому очень интересно. Есть более серьезные исследования, ссылки на них прикреплены к работе. Я же старался найти ответы на те вопросы на которые не нашел ответы в сети.

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

Как раз в этом и интерес. Тестов алгоритмов JVM против Native в интернете как грязи. Они все показывают, что JVM в целом не хуже прямого исполнения.

В этом же тесте автор осветил цену управления памятью. Теперь можно смело писать: "производительность реализации с автоматическим управлением памяти до 10-и раз хуже" и ссылаться на эту статью

Так все давно знают, что в Android Java - это быстрый код, а Kotlin это медленный.

Kotlin - это вообще не про скорость, а про архитектуры, сахар в коде, бизнес, Гугл мечты и много кода.

Вот почитайте, что бы понять, как вы ошибаетесь https://t.me/c/1493249585/61650

Котлин прекрасен, тут не о чем говорить, просто такие понятия, как "быстрый" или "медленный" не очень раскрывают тему, интересно было получить хоть какой-то измеримый результат, чтобы можно было принимать решения о необходимости подключения native в некоторых случаях.

Сравнивал скорость компиляции под Андроид. Для этого написанное на Java небольшое приложение сконвертировал на Kotlin. Код на Java собирался в 2 раза быстрее, но что удивило, размер финального файла на Kotlin оказался меньше примерно на 10% при том, что в собранном файле присутствовал котлиновский мусор с лишними проверками.

Примерно такие же интересности можно иногда и на C++ видеть. Собираю с -O0 - 3.5мб после strip, а с -O3 дал 3мб, хотя явно во многих местах циклы "размотало" и функции поинлайнило.

O3 же вроде считается оптимизацией по размеру, по скорости O2.

UFO just landed and posted this here

Да, точно, перепутал. O3 всё равно часто даёт меньший размер, чем O2.

Линк идёт на какую-то закрытую группу.

Не знаете, что уже 3 года существует официальный Хабр чат. Вы явно не в курсе всех новостей...

Не в курсе, тем более всех... Как ссылку-то открыть?

Надо кликнуть на эту чудесную кнопочку и чат откроется!
Ваш ангел хранитель :D
группа тут https://t.me/habr_com

В статье отсутствует очень важная информация про то, сколько запусков алгоритма было сделано для его прогрева. В этом же и есть вся сила JIT компилятора (в прочем, возможно, что в ART сделали что-то такое, чтобы не требовалась куча прогонов кода для разогрева, тут я плаваю)

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

Ну и еще вопрос, подскажите, где у вас такая интересная учеба, а то и OpenGL, и какие-то исследования подобные на фоне лаб моего вуза выглядят довольно интересными

Если честно, по количеству "прогревочных" запусков - не указал количество, по той причине что разложил все телефоны и запускал тест столько раз, насколько хватило терпения - думаю, не больше 50. Интересно что время выполнения в JVW продолжает плавать, в то время как С++ стабилен, только в режиме полета работает быстрее. Допускаю, что dex2aot, со временем, оптимизирует все до "небывалых" высот, но пока это только предположение.

Обязательно почитаю про бенчмарки, спасибо, пробелов в знаниях тем больше, чем больше я знаю)

По поводу учебы - секрета нет, ТУСУР программная инженерия,4 курс - удаленка.

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

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

не андроид разработчик, но когда разница между jvm и с в неспецифическом софте (и не микро размера) больше чем 2 раза, то значит что-то сделано не так, или может быть улучшено

100% может быть улучшено, повторюсь, идея заключалась в сравнении "в лоб". Не исключаю также, что где-то закралась критическая ошибка, если найду, обязательно дополню.

Быструю простую математику на Котлине лучше писать без Complex. Будет быстрее и меньше выделений памяти.

UFO just landed and posted this here

Если честно я ожидал совсем другого результата, уже была заготовлена фраза - что-то вроде "использование С++ усложняет проект, а разница во времени выполнения незначительна", поэтому, собственно и написал пост, интересно что скажут профи или просто умные люди)

UFO just landed and posted this here

Про Питон: если делать это на NumPy обрабатывая картинку целиком или сегментами, может получится быстрее чем на C++. В NumPy не будет цикла по пикселам, а будет векторизация.

UFO just landed and posted this here

Тут появилось предложение сложить в котлине все в массив DoubleArray, попробую проверить

Пока я пришел к мнению что все что можно cделать на Kotlin, нужно делать на котлин, а если придется упереться в какие-то супер вычисления думать о native или GLSL

У вас некорректное сравнение!

В коде на с++ в массив кладутся double по значению, а в kotlin используется массив объектов Double. Из-за этого jvm создаёт и удаляет кучу ненужных объектов.

Вместо этого в kotlin надо использовать DoubleArray, которых хранит данные как массив значений.

Либо, как вариант, надо в С++ сделать не vector double, а vector<unuqie_ptr<double>> и тоже создавать и складывать в массив объекты. Или даже shared_ptr, но тогда производительность может стать совсем плохой.

P.s. Пример с умными указателями - для иллюстрации, что сейчас происходит в котлине и почему это тормозит. С DoubleArray должно быть лучше.

Да, вы правы. Я просто просто беру коллекцию куда я могу складывать double и при этом не знаю заранее ее размер,то есть применена стандартная коллекция языка - контейнер с автоматическим расширением размера - можно сказать динамический массив. А так можно и std::vector реализовать на kotlin.
Вы абсолютно правы,можно использовать массив DoubleArray и управлять его размером, это поле для экспериментов, но есть сомнения что даже в таком случае Kotlin отработает быстрее native.

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

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

В JVM есть escape analysis. Если внутри функции создаётся временный объект и он не уходит наружу, то он вполне может создаваться на стеке - прям как в С++. В андроиде не JVM, но скорее всего на современных телефонах эта оптимизация тоже будет.

В таком случае узким местом будет boxing из double в Double

Создаем массив DoubleArray размером 3млн Double складываем туда координаты точек, теперь мы знаем точно количество точек, оборачиваем в asList, применяем границы с помощью subList

private var points=DoubleArray(3000000)
.......
.......
   points[index]=x;
   ++index;
   points[index]=y;
   ++index;
.......
return points.asList().subList(0,index+1)

Да, это быстрее ,идея хорошая, но тут мы пошли на хитрость - используем массив фикс. размера, добавил Apk в ветку DoublуАrray, вроде особо быстрее не стало, или я неправильно понял вашу идею?

Я бы предложил вообще нигде не использовать Array<Double> и просто сделать копию нужного размера:

Arrays.copyOf(points, index + 1)


В коде на С++, кстати, примерно то же самое получается - там же из С++ в java приезжает DoubleArray.

Сделал

 return  points.copyOf(index + 1).asList()

Прилетает DoubleArray, заворачивается в List, прямо как в С++

сделать не vector double, а vector<unuqie_ptr<double>> и тоже создавать и складывать в массив объекты

Попробовал

 std::vector<std::unique_ptr<double>> points;
//в цикле складываем умные указатели на double в массив
points.emplace_back(std::make_unique<double>(x)); 
points.emplace_back(std::make_unique<double>(y));
// по итогу вычислений пробегаемся по массиву c указателями,
// разименовываем их и складываем в обычный вектор double
//  std::vector<double> resVector;
 resVector.reserve(points.size());
for (auto it=points.begin();it!=points.end();++it){
        resVector.push_back(**it);
}

сейчас залью в отдельную ветку ,особо так медленнее но все равно не сильно влияет на результат)

ветка arrayOfSmartPointers

Странно что вы в другую сторону воюете - замедляете плюсы, а не ускоряете котлин.

Да, это война в другую сторону, с ускорением Котлин уже справились - нужно просто перестать создавать горы объектов в Complex в куче , тогда все работает быстро, иначе в С++ все уходит вперед с большим отрывом, как бы мы не старались его замедлить)

Интересно, почему в списке побуждающих к исследованию факторов не было просто факта выпуска Гуглом AGDK. Что и как работает, сколько Жабу ни чини, после этого стало понятно, но убедиться - всегда хорошо.

Да, это факт, с которым спорить трудно, но сколько я не перекапывал docs, я не смог найти никакого сравнения,если бы хотя бы написали работать будет на 2% быстрее, а так просто используйте NDK, если нужно выжать производительность.... Сколько производительности ? Это же не лимон, который можно выжать)

Ох уж эти сравнения Javs/C#/JavaScript/*чего угодно* с С++ в плане производительности... Всегда это прикрыто какой-то магией, мол компилируется так же в машинный код, да и оптимизаций в рантайме побольше можно сделать, чем при предварительной компиляции.

Но зачастую результат теста определяет сама парадигма сравниваемого с С++ языка. С++ эффективно расходует ресурсы, ты действительно почти не платишь за то чего не используешь. Но и колено отстрелить себе легко можешь!

А в другом языке - безопасность, синтаксический сахар. Это все накладные расходы, происходящие из самой архитектуры языка. И как ты его не компилируй, он, защищая твои колени, чуток но тратит на сторону.

Главное не скатываться в тесты про числомолотилки. Когда все языки, так или иначе компилирующиеся в байт-код, начинают показывать одинаковые результаты. И пот этим соусом подсовывают идею "а вот видите, С++ то не нужен, JavaScript дает те же результаты!".

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

Если честно, тест был задуман только в контексте ART vs NDK, не ставил себе цели определить какой язык лучше, цель стояла простая - выяснить бывают ли случаи, когда есть смысл использовать NDK. На мой взгляд все понятно

В целом можно с уверенностью сказать, что существуют случаи, когда
производительность нативного кода колоссально превосходит
производительность JVM и, несмотря на некоторое усложнение проекта,
имеет смысл реализовать с помощью NDK

Потому как на момент задумки, были сомнения, все таки AOT компиляция, профилирование выглядят как серьезные аргументы.

В критичных для производительности местах в kotlin нужно использовать массив примитивов: DoubleArray. Иначе получается что хаешь что-то чем не умеешь пользоваться.

Спасибо за комментарий, вариант с DoubleArray уже посоветовали, пробую - есть отдельная ветка где все складывается в DoubleArray, пока ничего это не дало) А так я вроде ничего не "хаял")

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

Код лежит на GitHub, ветки по советам из комментариев созданы, apk лежат в ветках в папках apk. Не соглашусь по поводу негатива, не было его, мне нравится Котлин, если есть критическая ошибка в коде на Котлин, буду рад испытать после её исправления, пока два дельных предложения было, оба пробую

Хорошо, пусть будут просто некорректные выводы, которые в негативном свете представили kotlin. Давайте доведём код до состояния, когда его можно будет сравнивать и опубликуем новые цифры. В любом случае спасибо за статью!

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

Дельный совет уже был. И без него статья и выводы обесцениваются. Опять же надо различать обычный код и критичный с точки зрения производительности. С подходом "не хочу идти на компромиссы" ни на каком языке производительности не добьешься. Да и на плюсах вы динамически коллекцию не расширяете, зачем-то сразу резервируете память. Это не компромисс?)

Резервирую только 300тыс, как и в Котлин, массив же почти в 8 раз больше, то есть будет несколько реалокаций с расширением контейнера.

В варианте с shared_ptr вообще не резервируется память, просто с нуля всё складывается в пуиой массив. Это не особо влияет на результат

Совет с DoubleArray был принят к исполнению, но пока что ничего этот не дало.

у автора код лежит на GitHub - сделайте туда PR с исправлением кода на Kotlin в правильный, на ваш взгляд, вид для данного теста

Выложил оптимизированный код на Котлине, который работает быстрее чем вызов кода на C++
Главное изменение это вынос объекта Complex за тело цикла и его повторное использование.
https://github.com/igormich/JNIvsKotlin/blob//app/src/main/java/ru/tusur/nativevskotlin1/model2/MandelbrotOptimized.kt
Результаты на POCO M3 Pro (без прогрева, значения плавают, но порядок величин сохраняется)

  • Kotlin - 17681ms

  • Оптимизированный Kotlin - 1085ms

  • C++ - 1708ms

PS: PullRequest сделал, интересно как код поведёт себя на других конфигурациях.

Спасибо, сейчас скачаю.

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

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

Google Pixel 7

  • Kotlin - 10942ms

  • Оптимизированный Kotlin - 787ms

  • C++ - 1402ms

вынос объекта Complex за тело цикла и его повторное использование.

а если на С++ тоже сделать "переиспользуемый" внешний объект ?

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

не люблю сравнения языков, потому что 90% авторов хорошо знают только один язык и в итоге сравнивают теплое с мягким. эта статья не исключение.

Самого бесит) Я бы сказал "одинаково плохо знают оба")

Вы не поняли главного - зачем собственно нам всё это нужно? Да банально время отклика железа укорачивается, причем значительно. Почитайте, например, про библиотеку Oboe - https://github.com/google/oboe, которая решила проблему со временем отклика, если программировать через Java или Kotlin.

Стал таковым. До AGDK Oboe был отдельным проектом.
До Oboe писать какое-нибудь музыкальное приложение было просто болью.

Спасибо, обязательно изучу.

Как-то пробывал написать караоке приложение, которое должно было транслировать звук с микрофона в наушники... Оказалось, что если делать это стандартно - через Java или Kotlin, то будет задержка в передаче данных, причем задержка была от 200 миллисекунды и больше... Тогда и познакомился с NDK и с Oboe (ещё не было AGDK). С использованием NDK удалось значительно уменьшить задержку, она была в районе 5 миллисекунд (5 миллисекунд человеческий мозг уже зачастую не распознает как задержку). Т.е. производительность увеличилась как минимум в 40 раз конкретно в примере моей задачи.

Дорогой Автор, а не составит ли Вам труда
сравнить Rust / C++ / Kotlin ?

Если честно, не ставил целью сравнение языков, основная идея была сравнить выполнение кода в контексте виртуальной машины и сборщика мусора против выполнения native C++, поэтому алгоритм был осознанно написан таким образом, чтобы поставить сборщик мусора в неудобную ситуацию. Оптимизацию с удалением объектов Complex я добавил по той причине, что в комментариях было много людей, которые считали что я хочу "очерить котлин". Лично мне неважно как называется язык, тут важен сам факт того, что использовать нативный код легко и работает он быстрее,для каких то задач это может быть полезно. Я думаю Rust будет +- даст те же результаты, что и С++, но постараюсь проверить.

Языки были выбраны просто по принципу - рекомендованные в developer.android.com

буду очень признателен,
меня мучает обыкновенное
любопытство по поводу Rust,
ведь я тоже ожидаю результат
близкий к С++.

Да, мне тоже люботно, думаю это будет битва компиляторов,я перекопал массу публикаций на тему "Android Java vs C++" и да, были случаи когда вычисления с плавающей точкой на Java работали быстрее, но во всех таких случаях использовались какие-то очень древние компиляторы С++. В случае с Rust , я ставлю на то что результаты тестов буду "плавать" с выходом новых версий компиляторов, но рассуждать бесполезно, нужно просто проверить) Так же остается вопрос еще как поведет себя Kotlin Native.

Sign up to leave a comment.

Articles