У кого-нибудь есть тесты (код и результаты), сравнивающие производительность приложений Android, написанных на Xamarin C # и Java? [закрыто]

544

Я сталкивался с утверждениями Xamarin о том, что их реализация Mono на Android и их приложения, скомпилированные на C #, работают быстрее, чем код Java. Кто-нибудь выполнил реальные тесты на очень похожих кодах Java и C # на разных платформах Android, чтобы проверить такие утверждения, мог опубликовать код и результаты?

Добавлено 18 июня 2013 г.

Так как ответа не было и не удалось найти такие тесты, сделанные другими, я решил провести свои собственные тесты. К сожалению, мой вопрос остается «заблокированным», поэтому я не могу опубликовать его в качестве ответа, а только отредактировать вопрос. Пожалуйста, проголосуйте, чтобы снова открыть этот вопрос. Для C # я использовал Xamarin.Android Ver. 4.7.09001 (бета). Исходный код, все данные, которые я использовал для тестирования и скомпилированные пакеты APK, находятся на GitHub:

Java: https://github.com/gregko/TtsSetup_Java

C #: https://github.com/gregko/TtsSetup_C_sharp

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

Результаты моего тестирования

Я перенес свой класс экстрактора предложений в C # (из моего приложения @Voice Aloud Reader) и провел несколько тестов для 10 файлов HTML на английском, русском, французском, польском и чешском языках. Каждый запуск был выполнен 5 раз для всех 10 файлов, и общее время для 3 различных устройств и одного эмулятора опубликовано ниже. Я тестировал только сборки "Release", без включенной отладки.

HTC Nexus One Android 2.3.7 (API 10) - CyanogenMod ROM

Java: общее общее время (5 запусков): 12361 мс, общее время чтения файла: 13304 мс

C #: общее общее время (5 запусков): 17504 мс, общее чтение файла: 17956 мс

Samsung Galaxy S2 SGH-I777 (Android 4.0.4, API 15) - ПЗУ CyanogenMod

Java: общее общее время (5 запусков): 8947 мс, общее время чтения файла: 9186 мс

C #: общее общее время (5 запусков): 9884 мс, общее время чтения файла: 10247 мс

Samsung GT-N7100 (Android 4.1.1 JellyBean, API 16) - Samsung ROM

Java: общее общее время (5 запусков): 9742 мс, общее время чтения файла: 10111 мс

C #: общее общее время (5 запусков): 10459 мс, общее чтение файла: 10696 мс

Эмулятор - Intel (Android 4.2, API 17)

Java: общее общее время (5 запусков): 2699 мс, общее время чтения файла: 3127 мс

C #: общее общее время (5 запусков): 2049 мс, общее чтение файла: 2182 мс

Эмулятор - Intel (Android 2.3.7, API 10)

Java: общее общее время (5 запусков): 2992 мс, общее чтение файла: 3591 мс

C #: общее общее время (5 запусков): 2049 мс, общее чтение файла: 2257 мс

Эмулятор - Arm (Android 4.0.4, API 15)

Java: общее общее время (5 запусков): 41751 мс, общее время чтения файла: 43866 мс

C #: общее общее время (5 запусков): 44136 мс, общее чтение файла: 45109 мс

Краткое обсуждение

Мой тестовый код содержит в основном анализ текста, замену и поиск в Regex, возможно, для другого кода (например, больше числовых операций) результаты будут другими. На всех устройствах с процессорами ARM Java работала лучше, чем код Xamarin C #. Самая большая разница была под Android 2.3, где код C # работал на ок. 70% скорости Java.

В эмуляторе Intel (с технологией Intel HAX эмулятор работает в режиме быстрого виртуального выхода) код Xamarin C # выполняет мой пример кода намного быстрее, чем Java - примерно в 1,35 раза быстрее. Может быть, код и библиотеки виртуальной машины Mono намного лучше оптимизированы для Intel, чем для ARM?

Редактировать 8 июля 2013 г.

Я только что установил эмулятор Android Genymotion, который работает в Oracle VirtualBox, и снова этот использует собственный процессор Intel, а не процессор ARM. Как и в случае с эмулятором Intel HAX, снова C # работает здесь намного быстрее. Вот мои результаты:

Эмулятор Genymotion - Intel (Android 4.1.1, API 16)

Java: общее общее время (5 запусков): 2069 мс, общее время чтения файла: 2248 мс

C #: общее общее время (5 запусков): 1543 мс, общее чтение файла: 1642 мс

Затем я заметил, что появилось обновление для бета-версии Xamarin.Android, версия 4.7.11, с примечаниями к выпуску, в которых упоминались также некоторые изменения во время выполнения Mono. Решил быстро протестировать некоторые устройства ARM, и большой сюрприз - улучшилось число C #:

BN Nook XD +, ARM (Android 4.0)

Java: общее общее время (5 запусков): 8103 мс, общее время чтения файла: 8569 мс

C #: общее общее время (5 запусков): 7951 мс, общее чтение файла: 8161 мс

Вот Это Да! C # теперь лучше, чем Java? Решил повторить тест на моем Galaxy Note 2:

Samsung Galaxy Note 2 - ARM (Android 4.1.1)

Java: общее общее время (5 запусков): 9675 мс, общее чтение файла: 10028 мс

C #: общее общее время (5 запусков): 9911 мс, с общим чтением файла: 10104 мс

Здесь C # кажется немного медленнее, но эти цифры дали мне паузу: почему время длиннее, чем на Nook HD +, хотя Note 2 имеет более быстрый процессор? Ответ: режим энергосбережения. На Nook он был отключен, на Note 2 - включен. Решили протестировать с отключенным режимом энергосбережения (как и при включенном, он также ограничивает скорость процессора):

Samsung Galaxy Note 2 - ARM (Android 4.1.1), энергосбережение отключено

Java: общее общее время (5 запусков): 7153 мс, общее время чтения файла: 7459 мс

C #: общее общее время (5 запусков): 6906 мс, общее чтение файла: 7070 мс

Теперь, что удивительно, C # немного быстрее, чем Java на процессоре ARM. Большое улучшение!

Изменить 12 июля 2013 г.

Все мы знаем, что ничто не сравнится с быстродействием нативного кода, и я не был удовлетворен производительностью моего сплиттера предложений в Java или C #, особенно в том, что мне нужно его улучшить (и, следовательно, сделать его еще медленнее). Решил переписать это на C ++. Вот небольшое (т. Е. Меньший набор файлов, чем предыдущие тесты, по другим причинам) сравнение скорости нативной и Java на моей Galaxy Note 2 с отключенным режимом энергосбережения:

Java: общее общее время (5 запусков): 3292 мс, общее чтение файла: 3454 мс

Собственный большой палец: общее общее время (5 запусков): 537 мс, общее время чтения файла: 657 мс

Родная рука: Общее общее время (5 запусков): 458 мс, с общим чтением файла: 587 мс

Похоже, для моего конкретного теста нативный код работает в 6-7 раз быстрее, чем Java. Предостережение: не могу использовать класс std :: regex на Android, поэтому пришлось написать свои собственные специализированные процедуры для поиска разрывов абзацев или HTML-тегов. Мои начальные тесты того же кода на ПК с использованием регулярных выражений, были примерно в 4-5 раз быстрее, чем Java.

Уф! Пробуждая сырую память с помощью указателей char * или wchar *, я сразу почувствовал себя на 20 лет моложе! :)

Редактировать 15 июля 2013 г.

(Пожалуйста, смотрите ниже, с изменениями от 30.07.2013, для гораздо лучших результатов с Dot42)

С некоторым трудом мне удалось перенести свои тесты C # на Dot42 (версия 1.0.1.71 бета), еще одну платформу C # для Android. Предварительные результаты показывают, что код Dot42 примерно в 3 раза (в 3 раза) медленнее, чем Xamarin C # (v. 4.7.11), на эмуляторе Intel Android. Одна из проблем заключается в том, что класс System.Text.RegularExpressions в Dot42 не имеет функции Split (), которую я использовал в тестах Xamarin, поэтому вместо этого я использовал класс Java.Util.Regex и Java.Util.Regex.Pattern.Split (). Таким образом, в этом конкретном месте кода есть небольшая разница. Не должно быть большой проблемой, хотя. Dot42 компилируется в код Dalvik (DEX), поэтому он изначально взаимодействует с Java на Android, не требует дорогостоящего взаимодействия с C # до Java, такого как Xamarin.

Для сравнения, я также запускаю тест на устройствах ARM - здесь код Dot42 "всего лишь" в 2 раза медленнее, чем Xamarin C #. Вот мои результаты:

HTC Nexus One Android 2.3.7 (ARM)

Java: общее общее время (5 запусков): 12187 мс, общее время чтения файла: 13200 мс

Xamarin C #: общее общее время (5 прогонов): 13935 мс, с общим чтением файла: 14465 мс

Dot42 C #: общее общее время (5 запусков): 26000 мс, общее чтение файла: 27168 мс

Samsung Galaxy Note 2, Android 4.1.1 (ARM)

Java: общее время (5 запусков): 6895 мс, общее время чтения файла: 7275 мс

Xamarin C #: общее общее время (5 запусков): 6466 мс, общее время чтения файла: 6720 мс

Dot42 C #: общее общее время (5 запусков): 11185 мс, общее чтение файла: 11843 мс

Эмулятор Intel, Android 4.2 (x86)

Java: общее общее время (5 запусков): 2389 мс, общее время чтения файла: 2770 мс

Xamarin C #: общее общее время (5 запусков): 1748 мс, общее чтение файла: 1933 мс

Dot42 C #: общее общее время (5 запусков): 5150 мс, общее чтение файла: 5459 мс

Мне было также интересно отметить, что Xamarin C # немного быстрее Java на более новом устройстве ARM и немного медленнее на старом Nexus One. Если кто-то также хотел бы запустить эти тесты, пожалуйста, дайте мне знать, и я обновлю исходники на GitHub. Было бы особенно интересно увидеть результаты реального устройства Android с процессором Intel.

Обновление 26.07.2013

Просто быстрое обновление, перекомпилированное эталонными приложениями с последним Xamarin.Android 4.8, а также с вышедшим сегодня обновлением dot42 1.0.1.72 - никаких существенных изменений по сравнению с результатами, о которых сообщалось ранее.

Обновление 30.07.2013 - лучшие результаты для dot42

Перепроверено Dot42 с портом Роберта (от создателей dot42) моего Java-кода на C #. В моем C # -порте, изначально созданном для Xamarin, я заменил некоторые собственные классы Java, такие как ListArray, на класс List, родной для C # и т. Д. У Роберта не было моего исходного кода Dot42, поэтому он снова перенес его из Java и использовал оригинальные классы Java в такие места, которые приносят пользу Dot42, я думаю, потому что он работает в Dalvik VM, как Java, а не в Mono, как Xamarin. Теперь результаты Dot42 намного лучше. Вот журнал из моего тестирования:

30.07.2013 - Тесты Dot42 с большим количеством классов Java в Dot42 C #

Эмулятор Intel, Android 4.2

Dot42, код Грега с использованием StringBuilder.Replace () (как в Xamarin):
общее общее время (5 запусков): 3646 мс, с общим чтением файла: 3830 мс

Dot42, код Грега с использованием String.Replace () (как в коде Java и Роберта):
общее общее время (5 запусков): 3027 мс, с общим чтением файла: 3206 мс

Dot42, код Роберта:
общее время (5 прогонов): 1781 мс, общее чтение файла: 1999 мс

Xamarin:
общее общее время (5 запусков): 1373 мс, общее время чтения файла: 1505 мс

Java:
общее общее время (5 запусков): 1841 мс, общее время чтения файла: 2044 мс

ARM, Samsung Galaxy Note 2, энергосбережение выключено, Android 4.1.1

Dot42, код Грега с использованием StringBuilder.Replace () (как в Xamarin):
общее общее время (5 прогонов): 10875 мс, с общим чтением файла: 11280 мс

Dot42, код Грега с использованием String.Replace () (как в коде Java и Роберта):
общее общее время (5 запусков): 9710 мс, с общим чтением файла: 10097 мс

Dot42, код Роберта:
общее время (5 прогонов): 6279 мс, общее чтение файла: 6622 мс

Xamarin:
общее общее время (5 запусков): 6201 мс, общее время чтения файла: 6476 мс

Java:
общее общее время (5 запусков): 7141 мс, общее чтение файла: 7479 мс

Я все еще думаю, что Dot42 еще далеко. Наличие Java-подобных классов (например, ArrayList) и хорошая производительность с ними сделают перенос кода с Java на C # немного проще. Тем не менее, это то, что я вряд ли буду делать много. Я бы предпочел использовать существующий код C # (библиотеки и т. Д.), Который будет использовать собственные классы C # (например, List), и который будет работать медленно с текущим кодом dot42 и очень хорошо с Xamarin.

Greg

gregko
источник
5
Режим отладки на Nexus 7 4.2.2 с некоторыми оптимизациями для строк и xamarin alpha 9: общее время: 3907 мс, с общим объемом чтения файла: 4016. Что означает «5 прогонов»?
Softlion
1
«этот вопрос, скорее всего, вызовет дебаты, аргументы, опрос или расширенное обсуждение» <- см. выше;)
LearnCocos2D
2
@ LearnCocos2D - я сообщаю только конкретные результаты и цифры, то есть факты. Господа не оспаривают факты :)
gregko
2
хорошо, ученые делают;) есть разница между наблюдаемым поведением и фактом. Это намного больше, чтобы стать фактом, и даже тогда применимость к другим пользователям / ситуациям остается под вопросом. Это суть тестов, они только представляют факты на поверхности - пока вы не узнаете, что поставщик x оптимизировал свой драйвер для конкретного приложения тестов. В связи с этим было когда-то доказано, что вода обладает памятью (т. Е. Тестом гомеопатии), что было опровергнуто после того, как смещение тестера было рассмотрено и исключено, а затем не продемонстрировало статистической значимости.
LearnCocos2D
3
Кроме того, со следующей версией +0.1 эти характеристики производительности могут существенно измениться - тогда все ваши усилия, представленные здесь, изменятся с «факта» на «спорный». Однако любой, кто приходит сюда, может воспринимать это как факт и сделать неправильный вывод. Еще одна суть тестов: они являются репрезентативными только на данный момент времени и версии используемого программного обеспечения. На следующий день они могут больше не отражать реальность. Вы должны продолжать тестирование результатов. Вот почему результаты здесь можно считать субъективными и не имеют никакого значения.
LearnCocos2D

Ответы:

62

Да, виртуальная машина Xamarin Mono более впечатляет, чем Google Dalvik, используемый в Android. Я протестировал его на планшетах HTC Flyer и Acer Iconia Tab, чтобы сравнить порт C # Android через Mono с Java Dalvik, с реализацией C # в Android и поистине беспокоящей Dalvik на основе Java.

klvtsov
источник
4
@PeterLawrey, пожалуйста, смотрите мое обновление вопроса. Я намерен перенести некоторую часть моего реального Java-кода на C # и запустить тесты, а затем опубликовать их здесь - если они вновь откроют мой вопрос, так как Бдительные СО закрыли его почти сразу.
Грегко
1
@PeterLawrey - я теперь проводил свои тесты и публиковал результаты в StackOverflow, но внутри самого Вопроса, поскольку он все еще остается "заблокированным" и не может опубликовать ответ. Если вы могли бы, пожалуйста, добавьте свой голос, чтобы вновь открыть вопрос. Результаты интересны, на ARM Java выигрывает, на Intel - C # код в Mono намного быстрее.
Грегко
9
@gregko Стоит отметить, что вы видите, что C # эмулируется быстрее, но Java быстрее на реальных телефонах. Для меня это важное различие. Я не стал бы беспокоиться о производительности эмулятора, на самом деле я бы посоветовал вам, чтобы эмулятор был таким же медленным / быстрым, как и настоящий. Я проголосовал за повторное открытие.
Питер Лоури
14
Будьте осторожны при использовании регулярных выражений в качестве теста на производительность. Алгоритмические различия в реализации RE могут иметь огромное значение. То, что вы можете тестировать выше, это качество реализации RE, а не виртуальных машин Dalvik или Mono. Лучшим тестом был бы рукописный код синтаксического анализа, который использует идентичные, очевидные алгоритмы, написанные в стиле, идиоматическом для каждого языка.
Кристофер
4
Этот ответ бесполезен без объяснения того, как вы выполнили эти тесты или результаты тестов. Как сейчас: полностью основано на мнении.
Рольф ツ
34

Недавно мы исследовали использование Xamarin для приложения. Мы использовали код C #, который мы уже написали для версии нашего приложения для Windows RT. Некоторые конкретные детали должны были быть переписаны для версии Android.

Мы обнаружили, что ввод-вывод в Xamarin C # примерно в 2 раза медленнее, чем в Java. Наше приложение сильно связано с вводом / выводом. Мы еще не обнаружили причину этого, но в настоящий момент мы предполагаем, что это связано с маршалингом. Хотя мы пытаемся оставаться внутри ВМ Mono большую часть времени, мы не знаем, как Mono фактически обращается к диску.

Это также говорит о том, что наш код C # использует SQLite.NET ( https://github.com/praeclarum/sqlite-net ). Идентичные выборки с использованием кода SQLite.NET также в 2 раза медленнее, чем с помощью обёртки Android SQLite для Android. После просмотра исходного кода, он, кажется, связывается непосредственно с C .dll, поэтому я не знаю, почему он намного медленнее. Одна из возможностей заключается в том, что маршалинг строк из нативного в Java может быть быстрее на Android, чем нативный на C # на Xamarin.

Кристофер
источник
1
Скорее всего, это связано с «связыванием», которое Ксамерин должен взаимодействовать с системой. Каждый системный вызов по умолчанию направляется в класс Java, но его необходимо делегировать виртуальной машине Mono, что занимает много времени. То же самое происходит и в обратном порядке. Я объяснил это немного больше в своем ответе: stackoverflow.com/a/46973819/1052697
Рольф ツ
33

Это еще один обновленный пост в блоге, которым я хотел бы поделиться с вами . Он сравнивает Xamarin с нативным кодом и Cordova на IO и Android.

Короче говоря, Xamarin работает иногда лучше, чем собственный код. Он проверил размер приложения, время загрузки, загрузку списка из службы Azure и вычисление простых чисел.

Наслаждайтесь!

Изменить: я обновил мертвую ссылку, и я заметил, что есть часть 2

Даниэль
источник
11

Вот несколько сведений, которые я нашел в другом тесте между нативными решениями Xamarin и Xamarin.Forms (тесты также включают производительность iOS) на двух следующих устройствах:

Samsung Galaxy A7 : версия ОС Android: 6.0 Центральный процессор: Octa-core 1,9 ГГц Cortex-A53 Оперативная память: 3 ГБ Разрешение экрана: 1920 × 1080

iPhone 6s : версия iOS: 10.3.3 Центральный процессор: двухъядерный 1,84 ГГц Twister RAM: 2 ГБ Разрешение экрана: 1334 × 750

Сравнение проводится по нескольким общим характеристикам, каждое из которых имеет свое приложение:

- Basic Hello World
- REST API
- JSON Serialization/Deserialization
- Photo Loading
- SQL Database Insert and Get All

Каждый тест повторяется несколько раз, графики показывают средние результаты.


Привет мир

Сравнение производительности Basic Hellow World


API отдыха

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

Сравнение производительности Rest API


Операции JSON Тесты, выполненные с использованием инфраструктуры Newtonsoft Json.net, для сериализации и десериализации объектов JSON во всех приложениях Xamarin. Нативная сериализация и десериализация Android протестирована с использованием двух библиотек Java: Jackson и GSON.

Выполняется два прогона: первый с нуля, а второй с кэшированной информацией и операциями.

Первый забег :

Первый запуск сериализации JSON

Первый запуск десериализации JSON

(Кстати, собственный тест JSON Operations для iOS убивает этот тест, и Xamarin присоединяется к нему во втором)

Второй запуск сериализации JSON

Второй запуск десериализации JSON


Операции с фотографиями

Сначала загрузите изображения с тремя различными разрешениями:

Resolution  858×569, Size  868Kb
Resolution  2575×1709, Size  8Mb
Resolution  4291×2848, Size  28.9Mb

Изображение Первая загрузка Android

Изображение Сначала Загрузите iOS

Что-то казалось неуверенным в результатах Xamarin.Forms для этого теста, поэтому он не включен в график.


Операции SQLite

Проверено две операции:

BulkInsert: Loading rows of data into a database table.
GetAll: Retrieving all data from the database.

С базами данных, имеющими 10000 записей. Все операции были обработаны внутри устройства.

SQLite для Android

SQLite для iOS


Xamarin Native (Xamarin.iOS / Xamarin.Android) показывает себя как довольно хорошие альтернативы нативному коду, тогда как Xamarin.Forms во многих случаях кажется медленным, но это может быть действительно хорошим решением для быстрой разработки действительно простых приложений.

Полный тест происходит из этого источника:

https://www.altexsoft.com/blog/engineering/performance-comparison-xamarin-forms-xamarin-ios-xamarin-android-vs-android-and-ios-native-applications/

Спасибо, что дали мне объяснения, чтобы улучшить мой ответ, надеюсь, это немного поможет :)

оборота Бургито
источник
7

Представление

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

Android изначально поставляется с несколькими формами для выполнения кода в:

  • RenderScript (процессор и графический процессор)
  • Java (SDK)
  • C ++ (NDK)
  • OpenGL (GPU)

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

Но с другой стороны, если вы хотите измерить производительность в реальных условиях, Java будет propbaby быстрее, чем Xamarin.

Xamarin и почему это может быть медленнее

При сравнении Xamarin с простыми старыми приложениями Java производительность Xamarin вполне может быть выше, так как она может быть ниже.

В реальных примерах приложения Xamarin, скорее всего, будут работать медленнее, чем приложения Java, поскольку многие вызовы Android / Java (системные) необходимо делегировать во время выполнения Xamarin и из него, используя так называемые привязки.

Есть несколько разных типов привязок, которые важно знать:

  • JNI (собственный интерфейс Java): привязка, используемая во многих приложениях для Android для взаимодействия между кодом Java (SDK) и собственным кодом C ++ (NDK).
  • MCW (Managed Callable Wrappers): привязка, которая доступна в Xamarin для взаимодействия между управляемым кодом C # и кодом Java (среда выполнения Android).
  • ACW (Android Callable Wrappers): привязка, доступная в Xamarin для взаимодействия из кода Java (среды выполнения Android) с управляемым кодом C #.

Больше о MCW и ACW здесь: https://developer.xamarin.com/guides/cross-platform/application_fundamentals/building_cross_platform_applications/part_1_-_understanding_the_xamarin_mobile_platform/

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

Кто-то провел тест производительности, чтобы рассчитать, сколько в среднем Java-операций стоит на вызове JNI. Каковы количественные издержки при выполнении вызова JNI?

Но не только звонки JNI являются дорогостоящими, так и звонки в и из MCW и ACW. Реальные приложения Xamarin делают много вызовов, используя привязки, и из-за этого использование Xamarin в реальном мире может быть (и будет в целом) медленнее, чем обычное старое Java-приложение. Однако в зависимости от того, как было разработано приложение Xamarin, очень вероятно, что пользователь даже не заметит разницы.

TLDR / Вывод: Xamarin необходимо использовать все виды привязок, которые являются дорогостоящими.

Помимо привязок, существует множество других факторов, связанных с реальной производительностью, например: размер двоичного файла, загрузка приложения в память, операции ввода-вывода и многое другое. Сообщение в блоге, в котором рассматриваются некоторые из этих вещей, можно найти здесь: https://magenic.com/thinking/mobile-development-platform-performance-part-2-native-cordova-classic-xamarin-xamarin-forms

Рольф ツ
источник
2

Это довольно старые тесты, но они могут быть актуальны: https://github.com/EgorBo/Xamarin.Android-vs-Java

Арифметический тест

введите описание изображения здесь

Коллекции, дженерики, пользовательские типы значений

введите описание изображения здесь

Работа со строками

введите описание изображения здесь

UPD: новые данные с Google Pixel 2 (спасибо yousha-aleayoub )

Тесты Pixel 2

Денис Гордин
источник
1
Обновление 2018 года camo.githubusercontent.com/...
Yousha Aleayoub