Pull to refresh

Сравнительное тестирование производительности платформ .Net, Java и Mono

Reading time 6 min
Views 26K

Идея Java vs .Net vs Mono


Сама идея создания подобного теста появилась из-за, постоянно не дававшего мне покоя, противопоставления .Net и Java, и я решил максимально объективно оценить реальную производительность данных платформ, затем в поле зрения попала интересная opensource разработка Mono (свободная реализация .Net), и было решено включить и её, а заодно прогнать тесты под Linux. Соответственно были разработаны две аналогичных программы тестирования на языках C# и Java. Далее будут приведены фрагменты исходников на C#, полный исходный код можно получить из репозитария Google Code:
http://code.google.com/p/dotnet-java-benchmark/source/checkout
Целью данного теста является сравнение производительности различных виртуальных машин, выполняющих по сути один и тот же код на одном и том же компьютере. В соревновании принимали участие следующие платформы:
  • Microsoft .Net 4.0 (Windows 7)
  • Oracle Java SE Version 6 Update 24 (Windows 7)
  • Oracle Java SE Version 6 Update 24 (Linux 2.6.35.27 Ubuntu 10.10)
  • Novell Mono 2.11 (Linux 2.6.35.27 Ubuntu 10.10)

Никакого тюна, разгона или оптимизации операционных систем не производилось, просто поставленные, пропатченные последними обновлениями операционки.
Тестирование производилось на моём потрёпанном ноутбуке Dell Inspirion 6400 Intel Core Duo T2300 1,66 ГГц 1,5 Гб RAM. Процессор 32-х битный.
Тест состоит из нескольких групп микротестов:
  • математические операции
  • генерация случайных чисел
  • работа с массивами
  • работа с коллекциями
  • строковые преобразования

Результат выполнения каждого микротеста – это среднее количество выполненных операций за микросекунду. Весь тест циклически прогонялся по 10 раз, и все полученные результаты по микротестам снова усреднялись. Также приблизительно оценивался максимальный объём управляемой памяти потребляемой программой в течении всего теста.

Java


Тест для Java платформ разрабатывался в IDE Eclipse под Windows, при запуске его в Linux совершенно никаких заморочек не возникло, но в обоих случаях пришлось явно указать максимальный размер памяти для Java машины в 768 Мб, значение по-умолчанию было слишком мало. Обе Java машины запускались со следующими параметрами:
-Xms32m   -Xmx768m


C#


Тест для .Net и Mono разрабатывался в Visual Studio и каково было моё разочарование, когда при первом запуске под Mono, среда выполнения прискорбно сообщила мне об отсутствии реализации метода int.TryParse(), мелочь а неприятно. Конструкция с int.TryParse() была заменена на int.Parse() с условием, прямо под Linux в MonoDevelop (которая на удивление прекрасно справилась с майкрософтским солюшином). Тест заработал, но здесь начались гораздо более интересные сюрпризы. Программа после каждой итерации планомерными порциями потребляла оперативную память, затем своп, и после того как вся оперативная память (включая своп) была исчерпана Linux уничтожал такой небогоугодный процесс.

Mono GC


Изначально было ощущение что некорректно работает сборщик мусора, после каждой итерации теста остаётся приличная порция массивов и коллекций, но память не освобождается. Начал гуглить, нашёл в документации к Mono ключ --desktop который должен предотвращать монопольное потребление памяти программой (Currently this sets the GC system to avoid expanding the heap as much as possible at the expense of slowing down garbage collection a bit). Также там был описан интересный ключик --gc с помощью которого можно было выбрать один из двух сборщиков мусора: Boehm или SGen, но ключик этот не работал, что наталкивало на мысль о моральном устаревании установленной версии Mono. На официальном сайте фигурировала последняя стабильная версия 2.10.1, но в репозитарии Ubuntu 10.10 была лишь версия 2.6.7. Но выход вскоре был найден: через Git была получена последняя версия 2.11, успешно собрана и установлена. Кстати TryParse() там уже реализовали, и вообще exe файл скомпилированный в Windows под Mono запустился без перекомпиляции, что очень приятно. После пробы разных сборщиков мусора выяснилось что тест не работает со сборщиком Boehm, и успешно выполняется с SGen. В результате параметры запуска Mono были такие:
--desktop --gc=sgen

Теперь результаты. На всех графиках ось Y отображает количество операций за микросекунду.

Математические функции


Здесь и далее я не буду приводить полный исходный код, только небольшие фрагменты, кому интересно ссылка на репозитарий выше.
namespace DotNetPerformance.Math
{
    public class MathTestParams
    {
        public static readonly int iterationCount = 5000000;
    }
   
    class Div10Test : SomeTest
    {
        public Div10Test()
        {
            _name = "Деление целых чисел на 10";
            _iterationCount = MathTestParams.iterationCount * 10;
        }
       
        public override void Do()
        {
            StartTiming();
            for (int i = 0; i < _iterationCount; ++i)
            {
                int x = i / 10;
            }
            StopTiming();
        }
    }   
 
    class SinTest : SomeTest
    {
        public SinTest()
        {
            _name = "Синус";
            _iterationCount = MathTestParams.iterationCount * 5;
        }
 
        public override void Do()
        {
            double val = 0;
            double dt = System.Math.PI * 2;
            dt /= _iterationCount;
 
            StartTiming();
            for (int i = 0; i < _iterationCount; ++i)
            {
                double x = System.Math.Sin(val);
                val += dt;
            }
            StopTiming();
        }
    }
    ...
}

Математические тесты

У .Net наблюдается значительный отрыв (на два порядка), для функций синуса, косинуса, квадратного корня и операции деления на 10, из-за него пришлось использовать логарифмическую шкалу, это единственные показатели во всем тесте имеющие такую большую разницу между платформами. Такой прирост скорости вероятно даёт использование таблиц в реализации. В остальном все конкуренты сравнительно близки к друг другу, в большинстве микротестов преимущество у .Net и Mono. Mono гораздо быстрее других платформ считает арксинус и арккосинус. Java лидирует в расчёте логарифмов. Заметим что результаты для Java под Linux и Windows очень близки к друг другу.

Случайные числа


namespace DotNetPerformance.RandomTests
{
    public class RandomTestParams
    {
        public static readonly int count = 10000000;
    }
   
    class IntRandomTest : SomeTest
    {
        public IntRandomTest()
        {
            _name = "Генерация случайных чисел int";
            _iterationCount = RandomTestParams.count;
        }
 
        private Random rnd = new Random();
 
        public override void Do()
        {
            StartTiming();
            for (int i = 0; i < _iterationCount; ++i)
            {
                int x = rnd.Next();
            }
            StopTiming();
        }
    }
    ...
}

image

Возможно микротесты на генерацию случайных чисел не совсем объективны, ведь в реализации генераторов случайных чисел могут использоваться разные алгоритмы, но меня интересует не близость к равномерному распределению, а конечная скорость генерации, которая получается на выходе. В этом соревновании .Net значительно лидирует, Mono чётко на втором месте.

Массивы


namespace DotNetPerformance.ArraysTests
{
    public class ArrayTestParams
    {
        public static readonly int arraySize = 50000000;
    }  
 
    class ArrayIntAccessTest : SomeTest
    {
        public ArrayIntAccessTest()
        {
            _name = "int[] последовательный доступ к элементам";
            _iterationCount = ArrayTestParams.arraySize;
        }
 
        public override void Do()
        {
            int[] array = null;
            while (array == null)
            {
                try
                {
                    array = new int[_iterationCount];
                }
                catch (OutOfMemoryException)
                {
                    _iterationCount /= 2;
                    Console.WriteLine("!! Недостаточно памяти для полного теста");
                }
            }
 
            for (int i = 0; i < array.Length; ++i)
            {
                array[i] = i;
            }
 
            StartTiming();
            for (int i = 0; i  0; –i)
            {
                int x = array[i];
            }
            StopTiming();
        }
    }
    ...
}

image

В этой группе микротестов .Net снова оказывается быстрее, Java уверенно на втором месте.

Коллекции


namespace DotNetPerformance.CollectionsTests
{
    class ListTestParams
    {
        public static readonly int ListInsertRemoveSize = 500000;
        public static readonly int ListAccessSize = 2000000;
    }  
 
    class DynamicArrayInsertRemoveTest : SomeTest
    {
        public DynamicArrayInsertRemoveTest()
        {
            _name = "DynamicArray вставка и удаление элементов";
            _iterationCount = ListTestParams.ListInsertRemoveSize / 10;
        }
       
        public override void Do()
        {
            List list = new List();
            StartTiming();
            for (int i = 0; i  0)
            {
                list.RemoveAt(0);
            }
            StopTiming();
        }
    }
    ...
}

image

Снова в большинстве микротестов быстрее .Net, Java в 4 микротестах на порядок медленнее, у Mono результаты сильно колеблются. Низкая скорость работы с параметризованными коллекциями Java вероятно вызвана тем, что шаблоны в Java не возможно специализировать примитивными типами, что приводит к необходимости упаковки/распаковки примитивных переменных в объекты.

Операции со строками


namespace DotNetPerformance.StringConversions
{
    public class StringConversionsTestParams
    {
        public static readonly int iterationCount = 10000000;
    }
 
    class IntParseTest : SomeTest
    {
        public IntParseTest()
        {
            _name = "Парсинг int";
            _iterationCount = StringConversionsTestParams.iterationCount / 10;
        }
 
        public override void Do()
        {
            string[] arr = new string[_iterationCount];
 
            for (int i = 0; i < arr.Length; ++i)
            {
                arr[i] = i.ToString();
            }
 
            StartTiming();
            for (int i = 0; i < arr.Length; ++i)
            {
                int x = int.Parse(arr[i]);
            }
            StopTiming();
        }
    }
    ...
}

image

Java значительно превосходит конкурентов в парсинге и преобразовании целых чисел в строку, но сильно отстаёт при парсинге чисел с плавающей точкой. .Net быстрее в операциях над числами с плавающей точкой. Странно но парсинг в double Java производит гораздо быстрее чем во float. Mono быстрее всех в удалении символов из StringBuilder.

Результаты


image

Предупрежу что я не промайкрософт настроенный фанатик, таких результатов не ожидал, здесь нет никакой пропаганды, просто субъективный тест. По сумме результатов .Net лидирует во всех группах микротестов, а также по потреблению памяти. В 4 из 5 группах Mono превосходит Java по скорости, но потребляя при этом почти в два раза (!) больше памяти. Интересно что в сумме результаты Java под Linux и Windows почти не отличаются или очень близки к другу.

image

Возраст Mono ещё слишком мал по сравнению с такими гигантами как Java и .Net, но тем не менее их уже можно сравнить, и в чём-то Mono даже может превзойти своих идеологических прародителей, при этом значительно уступая в другом, посмотрим что будет дальше.
За кроссплатформеность Java приходится платить скоростью, с другой стороны на создание .Net вероятно были использованы гораздо большие ресурсы.

Ссылки


Похожие интересные работы:
  • Stefan Krause Java vs. C benchmark
  • Java Micro Benchmark – сравнение производительности разных компьютеров с использованием микротестов

updated
Tags:
Hubs:
+82
Comments 208
Comments Comments 208

Articles