Сравнение производительности Xamarin (monodroid) и Java (DalvikVM) на Android устройствах

    image
    Добрый день. Многие интересуются насколько сильно отличается производительность Xamarin на Android или iOS. Вопрос с iOS я пока оставлю открытым, а вот все вопросы по производительности monodroid предлагаю закрыть раз и навсегда.

    Зачастую эти вопросы вызваны из-за неправильного понимания как устроен monodroid, мне например задавали вопросы типа «А Xamarin потом пересобирается под JVM?». Это конечно же не так. Важно понимать, что Xamarin выполняется на том же уровне Android где работает виртуальная машина Android Dalvik. Поэтому при сравнении производительности мы на деле имеем сравнение эффективности работы двух виртуальных машин: Mono VM и Dalvik VM.


    Методика проверки


    Для проверки производительности необходим реализованный на Java и общепризнанный метод, который необходимо будет реализовать на C#. Я решил использовать известный тест производительности LINPACK в первую очередь потому что его исходный код открыт и легко будет реализовать C# версию, а во-вторую — есть уже готовая Android версия LINPACK for Android написанная на Java.

    Тест производительности LINPACK это метод оценки производительности путем оценки скорости выполнения операций с плавающей точкой вычислительной системы. Бенчмарк создан Джеком Донгарра (Jack Dongarra) и измеряет как быстро компьютер ищет решение плотной СЛАУ Ax = B размерностью N x N методом LU-декомпозиции. Решение получено методом Гаусса с выделением главного элемента (описание), при котором выполняется 2/3*N3 + 2*N2 операций с плавающей точкой. Результат определяется в Floating-point Operations Per Second(MFLOP / s, чаще просто FLOPS). Сам тест производительности был описан в документации к фортрановской библиотеке линейных вычислений LINPACK и с тех пор его вариации используются, например, для составления рейтинга TOP500 суперкомпьютеров.

    Реализация


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

    На первом шаге строим кроссплатформенное решение:

    Потом согласно шаблону проектирования MVP создаем Presenter и View
    image
    Пишем тесты и реализуем Presenter через тест. Для этого я использую NUnit и NSubstitute. Описывать NUnit особого смысла нету, на мой взгляд более удобного фреймворка для тестирования просто нету. NSubstitute это крайне удобный фреймворк для создания заглушек на основании интерфейсов. Фреймворки устанавливаются через NuGet.
    Небольшой пример использования NSubstitute из доков
    // Let's say we have a basic calculator interface:
        public interface ICalculator
        {
            int Add(int a, int b);
            string Mode { get; set; }
            event Action PoweringUp;
        }
    // We can ask NSubstitute to create a substitute instance for this type. We could ask for a stub, mock, fake, spy, test double etc., but why bother when we just want to substitute an instance we have some control over?
        _calculator = Substitute.For<ICalculator>();
    // Now we can tell our substitute to return a value for a call:
        _calculator.Add(1, 2).Returns(3);
        Assert.That(_calculator.Add(1, 2), Is.EqualTo(3));
    // We can check that our substitute received a call, and did not receive others:
        _calculator.Add(1, 2);
        _calculator.Received().Add(1, 2);
        _calculator.DidNotReceive().Add(5, 7);
    // If our Received() assertion fails, NSubstitute tries to give us some help as to what the problem might be:
        NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
            Add(1, 2)
        Actually received no matching calls.
        Received 2 non-matching calls (non-matching arguments indicated with '*' characters):
            Add(1, *5*)
            Add(*4*, *7*)
    
    // We can also work with properties using the Returns syntax we use for methods, or just stick with plain old property setters (for read/write properties):
        _calculator.Mode.Returns("DEC");
        Assert.That(_calculator.Mode, Is.EqualTo("DEC"));
        _calculator.Mode = "HEX";
        Assert.That(_calculator.Mode, Is.EqualTo("HEX"));
    


    В нашем случае сначала создаем Setup метод
    [TestFixtureSetUp]
    public void Setup()
    {
          view = Substitute.For<ILinpackView>();
          presenter = new LinpackPresenter(view);
    }
    

    И выполняем простой тест, естественно асинхронно
     [Test]
     public void CalculateAsyncTest()
     {
           presenter.CalculateAsync().Wait();
           Assert.That(view.Mflops, Is.EqualTo(130.0).Within(5).Percent);            
     }
    

    Сторонним ПО я выяснил что производительность моего ПК — около 130 MFLOPS/s, так что его и впишем в ожидаемые значения, добавив погрешность.

    Внутри метода у меня создание асинхронной Task и заполнение View. Все просто и понятно
    public Task CalculateAsync()
    {
        Linpack l = new Linpack();
        return Task.Factory.StartNew(() => l.RunBenchmark())
            .ContinueWith(t =>
            {                    
                _view.Mflops = l.MFlops;
                _view.NormRes = l.ResIDN;
                _view.Precision = l.Eps;
                _view.Time = l.Time.ToString();
            }
            );
    }
    


    Программа фактически реализована, ни строчки платформозависимого кода пока написано не было. Теперь создаем Android проект:

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

    Теперь остается только скомпилировать релиз, подписать его приложенным к студии мастером и установить на устройства с помощью консольной утилиты adb.exe


    Недавно я приметил, что скорость выполнения кода зависит не только от Debug/Release но и от подключения устройства по USB. Происходит это из-за того что при подключенном телефоне в режиме разработки Android гонит огромные объемы отладочной информации на компьютер, что может повлиять на скорость выполнения операций (а может и не повлиять), особенно если есть конструкции логирования типа Trace. Поэтому отключаем устройства и запускаем тесты.

    Готово!

    Результат


    У меня два тестовых устройства, HTC Desire с Android 2.3 (Qualcomm QSD8250, 1000 МГц, 512 ОЗУ) и Fly IQ443 с Android 4.0.4 (MediaTek MT6577, 1000 МГц, 512 ОЗУ)

    Вот их результаты:
    HTC Desire:


    Fly IQ443:


    И красивые графики


    Вывод


    Результаты теста показывают что работа Mono на Android как минимум сравнима, а иногда и превосходит производительность Dalvik, но в целом они примерно равны. Второй столбец некорректен, т.к. Mono самостоятельно распараллелил тест на два ядра без каких либо действий с моей стороны, полагаю где-то на уровне Task'ов, в то время как для Linpack for Android необходимо явно выбрать мультипоточный тест.

    Кстати говоря, этот проект показывает различия в размерах для релизовых сборок. Java версия теста весит всего лишь 280Кб, когда Monodroid версия весит почти 2.3 МБ на один тип процессора (armeabi и armeabi-v7), т.е. в сумме 4.6 МБ, что впрочем в условиях современных сетей не кажется мне особо критичным. Если такой размер apk кажется вам неприемлемым можно отдельно собрать и распространять пакеты для armeabi и armeabi-v7, благо Google Play позволяет загружать разные apk для разных платфом.

    Исходный код приложения находится здесь

    P.S. Мне бы хотелось собрать статистику по устройствам. Так что если у кого есть Android и 10 минут свободного времени буду очень благодарен если вы измерите производительность вашего устройства с помощью LINPACK for Android и вновь созданной MonoLINPACK и запишите результат сюда (если у вас многоядерный процессор выбирайте сразу Run Multiple Thread в LINPACK for Android)

    P.P.S. Аналогичный тест для iOS будет

    UPD1 По текущим тестам видно что Mono и Task по умолчанию не использует все ядра на 4-ядерных процессорах. Поэтому результаты между Linpack for Android и MonoLinpack на таких устройствах сильно различаются. В ближайшее время MonoLINPACK будет модифицирован с использование TPL.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 36
    • +18
      На андроиде очень сильно процессорное время жрёт вон тот баннер. Надо проверить на рутованом девайсе с фаерволлом.
      • 0
        Да можно просто интернет отключить.
        • +2
          P.S. Нет смысла делать аналогичный тест для iOs, так как код компилируется АОТ
          • +2
            Проверил на Nexus 4 (Qualcomm Snapdragon S4 Pro APQ8064, 4 ядра, 1500 МГц) и Samsung Galaxy Note 8.0 (Exynos 4412, 4 ядра, 1600 МГц) — Mono проигрывает в обоих случаях в 1,5-2 раза.
            Результаты тестов LINPACK for Android нестабильны на обоих девайсах, получается разброс примерно 150-220, при этом указывается точность 2.22E-16
            • 0
              Интересно. Могу предположить что в Mono по умолчанию не используются все 4 ядра. Никакого принудительного распараллеливания я не писал, а как ведет себя Task внутри предсказать сложно. Думаю, использовав возможности TPL и Parallel.For можно добиться более лучшего результата.
            • +3
              8-ми мегапиксельная картинка на 3 Мб в начале статьи — просто огонь :)
              Пользователи GPRS интернета тихо плачут в сторонке…
              • +2
                я очевидно зря понадеялся на
                сервис Habrastorage.org — он сразу уменьшает большие картинки до нужного размера и позволяет выдерживать высокие нагрузки.

                Пережал и перезалил картинку.
              • 0
                Samsung Galaxy S3
                Mono — 65 (стабильный результат)
                Java Single Thread — 41 (стабильный результат)
                Java Multi Thread — максимум 138 (скачет от 90 до 138)
                • +3
                  Объясните пожалуйста каким образом Mono под Android может работать на том же уровне что и Dalvik виртуальная машина. Насколько я знаю есть только два способа написать что-то под андроид: на java или native(*.so) и тоже вызвать из java. Как же тогда автор статьи может говорить что Mono и Dalvik на одном уровне? Значит есть третий способ писать под андроид?
                  • +6
                    Есть. В основе android лежит linux и Mono как и Dalvik работает поверх ядра линукса.
                    image
                    подробнее можно почитать тут
                  • 0
                    LG Optimus G Pro (E988)
                    CPU: 4-ядерный, 1.7 ГГц (Qualcomm Snapdragon™ 600)

                    Mono: 80.6
                    Android ST: 286
                    Android MT: 661

                    Как-то у Mono плоховато с ядроюзабельностью. Попробуйте собрать с TPL.
                    • 0
                      Хм на ноут3 не ставится моно версия.
                      В обычном линпаке — 750-860 в мультитреде
                      Точность — 2,22 показывает
                      • 0
                        update:
                        Поставился
                        MonoLP — 200-240
                        Precision 2.22
                      • 0
                        Acer Iconia Tab A501

                        dalvik single: 37.18
                        dalvik multi: 62.1
                        mono: 52.17 — 56.57 (скачет)
                        • 0
                          Lenovo P780
                          CPU: MT6589, 4 ядра х 1,2 ГГц

                          Linpack for Android Single Thread: 62.844
                          Linpack for Android Multi-Thread: 190.584
                          Mono Linpack: 49.8120
                          • 0
                            Mono боится Lenovo. Даже одно ядро не смогло использовать по полной :)
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • +1
                                Код который я использую есть прямой порт Linpack.java, используемой в Linpack for Android. Это необходимо для сравнения производительности двух VM. На мой взгляд чем меньше различий, тем точнее тест (хотя я например не знаю каким образом там реализован многопоточный тест), однако ваша оптимизация сама по себе интересна.
                              • +4
                                извените за глупый вопрос, сама «моно машина» (ну чтото вроде JVM) встроенна в андроид? или идет внутри аппликейшена который написан на mono с#? где можно про это почитать.
                                • НЛО прилетело и опубликовало эту надпись здесь
                                  • 0
                                    Пушните все изменения?
                                    И какая у вас спецификация, что дает 1.6 GFLOPS? У меня на Phenom II 945 X4 3Ghz больше 0.4 GFLOPS не выдает.
                                    • НЛО прилетело и опубликовало эту надпись здесь
                                  • +1
                                    >Тест производительности LINPACK это метод оценки производительности путем оценки скорости выполнения операций с плавающей точкой вычислительной системы.
                                    и что вы тестировали? Работу JIT компилятора в области простой арифметики и математику процессора? Практического толку от такого теста 0, видно что вы маловато знаете о виртуальных машинах и что вообще нужно тестировать.
                                    • –1
                                      Тестировал я, как очевидно, разность в производительности разных VM. Естественно для этого нужно какое-то мерило, и LINPACK тут вполне подходит.

                                      Кстати, а какой тест предложите вы? Сначала я хотел добавить whetstone, а теперь подумываю об одном из олденовских тестов, например bisort или health. Хорошие, мощные тесты.
                                      • +1
                                        Протестировали бы лучше плавность скрола таблиц со сложными ячейками — вот что действительно волнует пользователей.
                                        • 0
                                          скроллинг и там и там одинаковый, так как моно пользуется тем же нативным контролем и просто вызывает его через обертку
                                          • 0
                                            самый главный тормоз в прокрутке в Android — метод viewForItem, который возвращает следующую ячейку. Т.е. все упирается в его быстродействие — количество оберток вокруг него.
                                        • +3
                                          вы по сути тестируете производительность железа а не скорость работы виртуальной машины.
                                          нужно тестировать и как работает сборщик мусора, перегрузка методов, всё типичное что делает виртуальная машина выполняя реальную программу (умеет ли она делать агрессивные оптимизации?). Скомпилировать 3+2 может самый примитивный JIT компилятор. А вот по остальным оптимизациям и определяется лучшая VM.
                                          написание хорошего теста долгая и сложная работа, большинство людей не могут даже правильно измерить работу разных кусков кода под одной и той же JVM.
                                          Если бы мне нужно было делать подобное тестирование, то я бы
                                          1) нашёл типичное приложение, разобрался бы что от него требуется (как минимум пиковая производительность или низкая латентность), изучил бы структуру и архитектуру, накатал бы тест приложение похожее на образцовое.
                                          LINPACK — почему по вашему такой какой есть, он измеряет то что обычно делают на суперкомпьютерах — задачи численной математики на _кластерах_.
                                          2) нагуглил бы какие оптимизации делают разные виртуальные машины, что тоже повлияло бы на код теста.
                                          3) нагуглил бы как правильно проводить тестирование: сколько раз запускать, как долго должен работать тест, следил бы как работает сборщик мусора, когда происходит JIT компиляция, какой разброс результатов.
                                          В общем это не задача на один вечер.
                                        • +1
                                          Согласен с вашим комментарием. Самое важное для разработчиков — будут ли Xamarin-приложения столь же отзывчивыми, как нативные. Флопсы здесь ни при чем.
                                          • 0
                                            Естественно. Видео

                                            Единственное место где заметна разница в производительности — холодный старт приложения. C# сборки надо еще загрузить…
                                            • 0
                                              С этого места поподробней, всё-таки выгружать фоновые приложения из памяти Android любит.
                                              • +1
                                                Тот же тест, на который я дал линк в начале, замерил задержку старта, получилось около пол секунды разницы.
                                                forums.xamarin.com/discussion/5314/xamarin-load-time
                                                • 0
                                                  А. Ну пол секунды — это не критично…
                                                  • +1
                                                    Когда я писал IM на конкурс Дурова я был вынужден добавить splash-screen перед стартом программы, т.к. грузилась она хоть и быстро (0.7-1.4 секунды) но и визуально заметно.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.