Документ взят из кэша поисковой машины. Адрес оригинального документа : http://www.mmonline.ru/forum/read/7/47522/
Дата изменения: Mon Apr 11 14:33:33 2016
Дата индексирования: Mon Apr 11 14:33:33 2016
Кодировка: Windows-1251
MMOnline | Форумы | Разное | Вопрос о скорости выполнения C++, Java и C#

Вопрос о скорости выполнения C++, Java и C#

Автор темы egor 
17.04.2005 14:38
Вопрос о скорости выполнения C++, Java и C#
Недавно прочитал, что Java в арифметике может обгонять C++.

Забавы ради, решил сам проверить на простейшем арифметическом примере, где нет существенных манипуляций с объектами. Вот время вычисления факториала 100000 (тривиальным алгоритмом):

33 sec - Visual C++ inline assembler
35 sec - Visual C++ (Release, not Debug)
42 sec - Java
58 sec - C#

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

Меня поразило следующее:
1) хотя промежуточный код C# во время выполнения интерпретируется, скорость вполне приличная;
2) C# Release по скорости не отличается от C# Debug;
3) C++ Debug выполняется около 90 секунд.

Пожалуйста, проясните ситуацию.

P. S. Скорость программирования в данный момент не интересует. wink

17.04.2005 15:12
в общем и целом
http://forums.airbase.ru/index.php?showtopic=32510&st=30


Dejan Jelovic
Why Java Will Always Be Slower than C++

"Java обладает высоким быстродействием. Под высоким быстродействием мы подразумеваем достаточное. Под достаточным - низкое." ї Mr.Bunny

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

Однако, многие уверяют нас, что это лишь временное явление. Java не является медленной по своей сути, говорят они. Быстродействие мало из-за несовершенства сегодняшних JIT компиляторов, которые недостаточно хорошо оптимизирует код.

Это неверно. Вне зависимости от того, насколько хорошими станут JIT компиляторы, Java всегда будет уступать С++ в быстродействии.

Суть дела

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

Это правда. FORTRAN до сих пор уделывает С++ при работе с числами, поскольку является более строгим языком. C++ может конкурировать с FORTRAN лишь при использовании грамотно спроектированной библиотеки, например Blitz++.

Однако, для того чтобы получить подобные результаты, язык должен предоставлять компилятору возможности для оптимизации. К сожалению, Java не был разработан с учетом этого. Таким образом, каким бы умным не был компилятор, Java никогда не догонит С++.

Тесты

Парадоксально, но единственной областью, где Java может догнать С++ по скорости, являются типичные тесты на быстродействие. Если вам нужно вычислить энное число Фибоначчи или запустить Linpack, нет никаких причин для того, чтобы Java уступала С++. Пока вычисления происходят внутри одного класса и используются лишь встроенные типы данных вроде int и double, Java не отстает от С++.

Реальность

Как только вы начинаете использовать объекты в своей программе, Java теряет потенциальные возможности для оптимизации. В данном разделе перечислены некоторые причины этого.

1. Все объекты создаются в куче

Java выделяет память в стеке только для встроенных типов вроде int и double, а так же для ссылок. Все объекты создаются в куче.

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

Что за небольшие объекты? Для меня это итераторы. Я очень широко использую их в своих разработках. Кто-то другой может использовать комплексные числа. Программист 3-D графики может использовать вектора или точки. Всем этим категориям замена мгновенного выделения памяти в стеке на выделение памяти в куче определенно не понравится. Если подобное происходит в цикле, получаем время O(n) вместо нуля. Вложенный цикл даст уже O(n2).

2. Частое приведение типов

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

Что это значит с точки зрения быстродействия? Приведение типов в Java может быть только динамическим, а это ведет к накладным расходам. Насколько большим расходам? Допустим, вам необходимо реализовать динамическое приведение типов:

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

DestinationClass makeCast (Object o, Class destinationClass) {
Class sourceClass = o.getClass (); // JIT compile-time
int sourceClassId = sourceClass.getId (); // JIT compile-time

int destinationId = destinationClass.getId ();

int offset = ourTable [sourceClassId][destinationClassId];

if (offset != ILLEGAL_OFFSET_VALUE) {
return <object o adjusted for offset>;
}
else {
throw new IllegalCastException ();
}
}
Created with Colorer, type 'text'


Изрядное количество кода! И это оптимистичный вариант - ведь использование матрицы для хранения информации о взаимосвязи классов требует довольно большого объема памяти, и ни один нормальный компилятор так не сделает. Вместо этого, используют отображение (map) или проход по дереву наследования - оба этих способа еще более медленные.

3. Повышенные требования к объему памяти

Java-программы требуют примерно в два раза больший объем памяти для хранения данных, по сравнению с аналогами на С++. Это связано с тремя причинами:

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

- Объекты, память под которые может быть выделена в стеке (С++), будут размещены в куче при использовании Java.

- Объекты Java больше сами по себе, так как обязательно имеют таблицу виртуальных функций и поддерживают синхронизацию.

Повышенные требования к объему памяти увеличивают вероятность использования файла подкачки. Это в свою очередь убивает быстродействие как ничто другое.
17.04.2005 18:31
пара слов
Цитата

egor писал(а) :
Недавно прочитал, что Java в арифметике может обгонять C++.

Если программист квалифицирован, и притом в обоих языках,
то это не так. По очевидным причинам больших накладных
расходов, требуемых Java.

Цитата

Меня поразило следующее:

1) хотя промежуточный код C# во время выполнения интерпретируется, скорость вполне приличная;
Насчет интерпретации --- это неправда. CLR (среда
выполнения .NET) всегда транслирует промежуточный
в машинный. Причем, компилятор там очень неплохой.

Цитата

2) C# Release по скорости не отличается от C# Debug;
Это нормально, так как оптимизация, по крайней мере в основном,
проводится на этапе выполнения программы.

Цитата

3) C++ Debug выполняется около 90 секунд.
И это нормально, так как в режиме отладки для native кода
не проводится никакой оптимизации.

Что не вполне ясно, так это почему C# вообще отстал от
java. Должны идти либо вровень, либо в обратном порядке,
так как CLR спроектирован лучше javaVM.

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

Еще непонятно почему inline ассемблер вышел вперед,
может потому, что Вы ключиков оптимизации не добрали ?
release ведь не дает максимальной оптимизации.

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

Никогда java не отставала от оптимизированного С++ более чем
в два раза, а C# был всегда чуть лучше Java.

Ну и факториал, конечно, тот еще тест. Могли ведь и нарваться
на оптимизатор, который бы просто ответ вставил :))).

18.04.2005 02:42
спасибо за ответы
lubbertdas - спасибо за статью о скорости Java. Основную идею статьи я понял так: Java может тормозить с объектами-структурами, так как держит их в куче, а не в стеке.

Игорю Абрамову - спасибо за информацию о C#.

Основной вывод для меня состоит в том, что в простейших арифметических задачах Java и C# все-таки очень быстрые.

Цитата

Игорь Абрамов писал:
Что не вполне ясно, так это почему C# вообще отстал от java. Должны идти либо вровень, либо в обратном порядке, так как CLR спроектирован лучше javaVM.

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

Видимо, причина и в древности .NET Framework, и в том, что я никогда раньше не думал про опции. Если знаете, с какими опциями нужно запускать csc для уменьшения debug-информации и увеличения скорости, то поделитесь, пожалуйста.

После изменения какой-то опции в C# получилось
C# - 40 sec (.NET Framework 1.1.4322 Compiler 7.10.3052.4).

При запуске java -server -enablesystemassertions получилось
Java - 26 sec (Java 1.4.2_02-b03, NetBeans IDE 4.0)

Последний результат меня совсем раздавил, так как inline asm выполняется 33 секунды, и мне совершенно непонятно, как его улучшить.

Цитата

Игорь Абрамов писал:
Ну и факториал, конечно, тот еще тест. Могли ведь и нарваться на оптимизатор, который бы просто ответ вставил :))).
Неужели появились оптимизаторы, которые сразу считают факториал 100000 ?! И как же они вставляют ответ, занимающий 189589 байтов?

18.04.2005 10:00
о тестах и факториалах
Уважаемый egor !
На самом деле, неясно, что же Вы все-таки на самом деле
протестировали.

Сдается мне, что сравнивали Вы скорость реализации
длинной арифметики в БИБЛИОТЕКАХ соответственно
С++ (что это было ? gnu_mp ?), Java и .NET.
Вещь, вовсе не сильно связанная с эффективностью
языка как такового, так как все эти подпрограммы написаны во всех
трех средах на Си или вообще на ассемблере.

Еще одна интересная подробность состоит в том, что с
большой вероятностью реализация для C++ тоже хранила
все объекты в куче, используя malloc либо new.
А тогда мы еще сравнивали библиотеки управления памятью.
Тут результат и вовсе бессмысленен, потому как
слегка поигравшись параметрами настройки, либо,
в случае C++, подменив распределялку памяти, можно
расставить наши три языка в любом произвольном порядке.

Насчет оптимизатора, это была шутка с долей правды.
Реально посмотрев на сгенериованный для программы код,
частенько гадаешь, почему ЭТО считает то, что ты написал?

18.04.2005 11:43
о факториалах и параметрах компиляции
Цитата

Игорь Абрамов писал:
На самом деле, неясно, что же Вы все-таки на самом деле протестировали.

Похоже, я плохо выразился в самом начале. Тестировалась простейшая программка, написанная без использования стандартных арифм. библиотек. Вот:

int i, x = 0;
for (i = 0; i < length1; i++)
{
x += m * digits[ i ];
digits[ i ] = x & 0xFF;
x >>= 8;
}

Это основной кусок метода LongNatural::Mul(int m). В основной программе вызывается цикл m от 2 до 100000. Память выделяется под digits выделяется 1 раз, сразу в достаточном объеме.

Сначала я сомневался в том, что C# и Java довольно быстрые, и хотел проверить. Побочно выяснились интересные вещи. Например, что Visual C++ по умолчанию, даже в режиме Release, не хочет красиво распределить регистры и поэтому производит горы мусора. Возможно, тут какая-нибудь забота о многопоточности или что-то еще.

Теперь понятно, что все эти языки для меня слишком быстрые; нужно только научиться читать руководства, в которых говорится про опции и параметры.
Ибо по умолчанию c++ почему-то дает программу медленнее, чем java с опцей "-server".

Цитата

Насчет оптимизатора, это была шутка с долей правды.
Реально посмотрев на сгенериованный для программы код, частенько гадаешь, почему ЭТО считает то, что ты написал?
wink
У меня чаще возникали более тупые вопросы: 1) как я мог написать такую чушь? 2) почему же программа все-таки работает?

18.04.2005 12:37
понятно
Собственно, на таком тесте теоретически
все языки из перечисленных должны давать
один и тот-же результат. Java и C# должны
только аккуратно скомпилировать проверку диапазона
индекса цикла.

А какого типа digits ? Может еще оказаться полезным
сделать digits, m и x unsigned.
Далее, переписать цикл на указатель вместо индекса,
сказать компилятору нечто вроде
maximal optimization, omit-frame-pointer,
fast-call по умолчанию.

А на SSE2 на ассемблере или с intrinsics этот код раза в 2-3
еще наверняка можно разогнать.
19.04.2005 23:32
пока что не получается ускорить C#
C# компилировал командой csc /o+ /debug-. Время выполнения - 40 секунд.

gnu assembler, gnu c, gnu c++ и java, после некоторых усилий, стали считать факториал 100000 приблизительно за одно и то же время - от 25 до 27 секунд.

Во всех случаях числа хранились в 256-чной системе счисления, но на цифру отводился int или unsigned (понимаю, что лучше делать не так). Умножение массива цифр на unsigned m реализовано всюду одинаково:

for (i = 0; i < length1; i++)
{
sum += digits[ i ] * m;
digits[ i ] = sum & 0xFF;
sum >>= 8;
}

Таким образом, сравнивалась просто работа разных компиляторов в очень простой ситуации. Поделюсь небольшими "открытиями":

1. "Ручная оптимизация" в c, c++ и java - введение убывающего счетчика, замена for на do while, использования указателя вместо массива (в c и c++) - в данной программке только тормозила. Такое впечатление, что чем проще написано, тем лучше оптимизируется.

2. Спасибо Игорю Абрамову за перечисление опций оптимизации. При компиляции через gcc и g++, кроме -O3, немножко помогло развертывание циклов (unroll-all-loops). Остальные опции (в том числе SSE) здесь роли не сыграли.

3. java -server -esa выполнялась в 1.7 раз быстрее, чем java -client.

4. После замены в ассемблере всего двух команд:

movl (%edx), %ecx
imull %ebx, %ecx

на

movl %ebx, %ecx
imull (%edx), %ecx

- время выполнения уменьшилось с 33 секунд до 26 секунд! Неужели умножать на аргумент из памяти быстрее, чем на регистр?

05.05.2005 23:35
версия про асм
Возможно, во втором фрагменте кода физическая загрузка из памяти на регистр вообще отсутствует? Тогда как в первом фрагменте вы явно кладете в регистр определенное значение из памяти. По машинным кодам, наверное, можно посмотреть, что ассемблер сгенерил. Документацию надо глянуть.



This is like an expression of rage by the people,
who feel neglected and turned away by the system.
Извините, только зарегистрированные пользователи могут публиковать сообщения в этом форуме.

Кликните здесь, чтобы войти