Я не верю своим глазам! Java быстрее, чем C++???

 
+
-
edit
 

Balancer

администратор
★★★★★
Кстати, на тему "VC - плохой оптимизатор". Я когда собираю тесты, то очень часто приходится ломать голову, как при полной оптимизации кода, запретить локальную его оптимизацию в отдельных местах. А то компилятор может вплоть до того додуматься, что в вышеуказанном примере с комплексными числами, чуть упростишь его, всё сворачивает в серию сложений в цикле :) Или вычисление вместо того, чтобы 10 раз провести, проведёт один раз и потом 10 раз воспользуется результатом, как с ним не извращайся. Я, вон, в функции Аккермана даже 4-й параметр пробовал вводить, который номером цикла задавался - всё равно, зараза, всё в один цикл считает :D

Вот GCC за таким почти никогда пойман не был :)
 
MX [Реконструктор] #27.03.2005 12:35
+
-
edit
 
Balancer> Написанный на Си движок Lock-On'а тормознее при тех же параметрах :D

Вообще-то там больше вещей надо симулировать. Электроника, радиолокационное поле, не шутки все это.
 
RU Alex Privalov #27.03.2005 17:38
+
-
edit
 

Alex Privalov

новичок
Balancer>Понятия не имею :-/ М.б. JavaVM стала использовать SSE для плавучки? Хотя как раз в этом случае выигрышь не должен быть замтным на этом.

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

К слову — время выполнения довольно сильно меняется (в сторону улучшения) если запускать java с опцией -server, т.е. например java -server Complex. У меня это дает ускорение на 10-20%.
 
Это сообщение редактировалось 27.03.2005 в 17:47
+
-
edit
 

Balancer

администратор
★★★★★
Хм. В этом алгоритме Фибоначи затраты на память килобайтами исчисляются, так что и в случае Си и в случае Java всё должно, думаю, в кеш попасть...
 
RU Alex Privalov #27.03.2005 19:12
+
-
edit
 

Alex Privalov

новичок
С числами Фибоначчи как раз аномалий нет.

Я про тест с комплексными числами. Память выделяется в цикле при каждом вызове add(). Т.к. предыдущее значение z затирается, формально требуемый объем памяти совсем крошечный. Но — для ускорения выделения памяти JVM может использовать хитрый алгоритм, в результате чего адрес z путешествует по всей куче. А это около 2МБ — если судить по Runtime.getRuntime().totalMemory(). Отсюда обращения к основной памяти.

Повторюсь, это лишь гипотеза, т.к. особенности реализации данной конкретной JVM мне неизвестны. Другого объяснения, почему на одном компе отношение времени выполнения Java/С++ 2, а на другом 10, придумать не могу.
 
+
-
edit
 

Balancer

администратор
★★★★★
А, я думал, под "аномалией" ты имеешь в виду то, что в Фибоначчи Java быстрее, чем Си++ :)

А так - да, может быть. У меня кеш 1Мб на процессоре.
 

SEA

втянувшийся

Balancer:
[...]Или вычисление вместо того, чтобы 10 раз провести, проведёт один раз и потом 10 раз воспользуется результатом, как с ним не извращайся
 

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

Кстати, во время работы программы стек занимается максимум на 332 байта.
 
Это сообщение редактировалось 27.03.2005 в 22:22
RU Alex Privalov #29.03.2005 23:12
+
-
edit
 

Alex Privalov

новичок
Случайно наткнулся на статью как раз по теме. Кое-что сказанное в ней весьма спорно (или устарело), однако в целом достаточно разумно, imho.



Ниже перевод примерно половины этой статьи (не совсем дословный).




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 может быть только динамическим, а это ведет к накладным расходам. Насколько большим расходам? Допустим, вам необходимо реализовать динамическое приведение типов:

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

code text
  1. DestinationClass makeCast (Object o, Class destinationClass) {
  2.     Class sourceClass = o.getClass (); // JIT compile-time
  3.     int sourceClassId = sourceClass.getId (); // JIT compile-time
  4.  
  5.     int destinationId = destinationClass.getId ();
  6.  
  7.     int offset = ourTable [sourceClassId][destinationClassId];
  8.  
  9.     if (offset != ILLEGAL_OFFSET_VALUE) {
  10.         return <object o adjusted for offset>;
  11.     }
  12.     else {
  13.         throw new IllegalCastException ();
  14.     }
  15. }


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

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

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

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

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

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

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

 
MX несчастный #30.03.2005 01:26
+
-
edit
 

несчастный

новичок
Интересно, а это машинный перевод, или?...
 
RU Alex Privalov #30.03.2005 04:28
+
-
edit
 

Alex Privalov

новичок
Реконструктор>Интересно, а это машинный перевод, или?...

Не, это рукопашный перевод. Довольно кривой, судя по тому что такой вопрос возник :D
 
+
-
edit
 

Balancer

администратор
★★★★★
A.P.> Why Java Will Always Be Slower than C++

А какого года статья, интересно? А то JRE 1.4 по сравнению с более ранними на порядок быстрее. JRE 1.5 ещё немного в скорости прибавил...

Я помню времена, когда Java по скорости была лишь немногим быстрее скриптовых языков, типа Perl'а...
 
RU Alex Privalov #31.03.2005 01:23
+
-
edit
 

Alex Privalov

новичок
Balancer>А какого года статья, интересно?

Предположительно 2001 или 2002.

Balancer>А то JRE 1.4 по сравнению с более ранними на порядок быстрее. JRE 1.5 ещё немного в скорости прибавил...

При чем тут это. Статья о проблемах с быстродействием, связанных с особенностями языка. Как не оптимизируй JRE, никуда от них не денешься (по крайней мере, так считает автор). Тесты под JRE1.5 этому не противоречат, как бы.
 
RU Balancer #31.03.2005 10:27  @Alex Privalov#31.03.2005 01:23
+
-
edit
 

Balancer

администратор
★★★★★
A.P.> При чем тут это.

При том, что JIT всё совершенствуется.

A.P.> Статья о проблемах с быстродействием, связанных с особенностями языка. Как не оптимизируй JRE, никуда от них не денешься (по крайней мере, так считает автор). Тесты под JRE1.5 этому не противоречат, как бы. [»]

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

Много можно было писать и говорить о малой скорости, когда Java проигрывала нативному коду на порядок и более. Когда была по скорости сопоставима с обычными скриптовыми языками. Но это всё - давно в прошлом.
 
RU Alex Privalov #01.04.2005 04:31
+
-
edit
 

Alex Privalov

новичок
Balancer>При том, что JIT всё совершенствуется.

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

Balancer>Согласись, что максимум двукратный проигрышь (я так полагаю, что лучше рассматривать современные процессоры) нативному коду на задачах, связанных с манипуляциями с памятью - это немного.

Увы, не согласен по всем пунктам :)

>максимум двукратный проигрыш

- Не факт, что максимум.
- На другом ("устаревшем") процессоре он десятикратный на определенных тестах, это не стоит игнорировать. См. ниже.
- Повышенные требования к объему ОЗУ. Да, на современных ПК памяти вагон и маленькая тележка, но ОС не однозадачные. Уход в своп == жуткие тормоза.

>лучше рассматривать современные процессоры

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

>...нативному коду...

Упомянутые проблемы не решаются компиляцией в нативный код. В том же C# неспроста озаботились структурами, память под которые выделяется в стеке.

>на задачах, связанных с манипуляциями с памятью

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

Balancer>при среднем проигрыше всего в 20%

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

Balancer>а на некоторых задачах, типа породившей этот топик - и с выигрышем.

На задаче типа породившей топик — ничего особенного в небольшом выигрыше нет, при хорошем JIT компиляторе. Вот попробуй заменить int на Integer, в тех же самых Фибоначчи :)

Я, собственно, пытаюсь сказать следующее: с быстродействием Java не все так гладко, как может показаться на первый взгляд (по примитивным тестам). Чаще всего это некритично — но далеко не всегда.
 
Это сообщение редактировалось 01.04.2005 в 04:39
+
-
edit
 

Balancer

администратор
★★★★★
Есть прекрасный контрпример всем этим "заложенным в языке тормозам". Есть такая штука, как O'Caml. В случае байткода язык ничем не примечателен, а при компиляции в нативный код он "рвёт" Си++ :) При этом язык не просто крайне высокого уровня, но функциональный язык! Т.е. не только манипуляции памятью, но и куча рантаймовых проверок...

Конечно, на разных задачах он ведёт себя по-разному, но в лучших случаях он вдвое быстрее Си++, в худших - втрое медленнее. Средняя же программа получается на 30..50% быстрее, чем Си++. Это не считая синтаксиса, который в разы компактнее и надёжнее, чем у той же Java :)

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

Ну а про замену int на Integer - это вообще куда-то не в ту степь. Если я в Си++ заменю int на динамические классы, ИМХО, падение скорости будет не меньше :)
 
RU Alex Privalov #02.04.2005 21:32
+
-
edit
 

Alex Privalov

новичок
Еще о числах Фибоначчи. Оказывается, VC++ поддерживает возможность развертки рекурсии на несколько уровней. Делается это так:

code text
  1. #pragma inline_depth(6)
  2. #pragma inline_recursion(on)
  3. inline int fib(int n)
  4. {
  5.         return n < 2 ? 1 : fib(n - 1) + fib(n - 2);
  6. }


Оптимальная глубина может быть другой (меньше или больше), в зависимости от процессора.
Время вычисления 42-ого числа Фибоначчи (на моем компе) при компиляции с ключами /O2 /FAsc /G6 /Gr 5.2 сек. Java без ключа -server дает 10.5 сек, с ключом -server 8.3 сек.

Intel C++ эти прагмы не поддерживает, тем не менее при компиляции с теми же ключами быстродействие все равно выше чем у Java — 7.9 сек.

Balancer>Есть такая штука, как O'Caml. В случае байткода язык ничем не примечателен, а при компиляции в нативный код он "рвёт" Си++

Еще раз. Сам по себе байт-код Java под хорошим JIT-компилятором практически не уступает нативному коду. Тормоза начинаются из-за отсутствия возможности создавать объекты в стеке. Не вижу предпосылок для того, чтобы компиляция непосредственно в нативный код исправила положение. Идеологически близкий C# поддерживает структуры (немного урезанные классы), которые всегда создаются в стеке. Видимо, решить проблему с быстродействием другим способом трудно.

Лезть в дебри сравнения O'Caml и С++ наверное не стоит.

Balancer>Ну а про замену int на Integer - это вообще куда-то не в ту степь.

Да, Integer действительно не в ту спепь :) Потому как это встроенный в язык класс, что явно учитывается при оптимизации. Корректней сравнивать пользовательский класс-оболочку для int с аналогом в С++ :D

Balancer>Если я в Си++ заменю int на динамические классы, ИМХО, падение скорости будет не меньше

Это в Java все объекты "динамические" без альтернативы. Ни один С++ программист в здравом уме не будет создавать объекты, подобные Integer, в куче. Можно без особых потерь для быстродействия заменить int на класс-оболочку с перегруженными операторами — время выполнения увеличится не более чем в 1.5 раза. Аналогичный финт ушами в Java будет дорого стоить, о чем собственно и речь.
 

FR0

новичок
Balancer> Есть прекрасный контрпример всем этим "заложенным в языке тормозам". Есть такая штука, как O'Caml. В случае байткода язык ничем не примечателен, а при компиляции в нативный код он "рвёт" Си++ :) При этом язык не просто крайне высокого уровня, но функциональный язык! Т.е. не только манипуляции памятью, но и куча рантаймовых проверок...
Balancer> Конечно, на разных задачах он ведёт себя по-разному, но в лучших случаях он вдвое быстрее Си++, в худших - втрое медленнее. Средняя же программа получается на 30..50% быстрее, чем Си++. Это не считая синтаксиса, который в разы компактнее и надёжнее, чем у той же Java :)

Что-то не верится про средние на 30..50% быстрее.
Конечно на примерах с хвостовой рекурсией он может обогнать плюсы, но это вряд ли типичные задачи.
 
+
-
edit
 

Balancer

администратор
★★★★★
FR0> Что-то не верится про средние на 30..50% быстрее

Ну, народ по реальным стандартным бенчмаркам мерял... Хотя, повторюсь, всё сильно от задачи зависит.
 

SEA

втянувшийся

Скомпилировал фибоначи на новой Visual Studo 2005 betta 2, под Windows XP 64.
C++ 32 битная версия выполнилась 3.28 сек
C++ 64 битная версия выполнилась за 2.95 сек

code text
  1. int fib(int n)
  2. {
  3. 0000000000401020  sub         rsp,38h
  4.        return n<2 ? 1 : fib(n-1)+fib(n-2);
  5. 0000000000401024  cmp         ecx,2
  6. 0000000000401027  mov         qword ptr [rsp+28h],rdi
  7. 000000000040102C  mov         edi,ecx
  8. 000000000040102E  jge         fib+1Fh
  9. 0000000000401030  mov         eax,1
  10. 0000000000401035  mov         rdi,qword ptr [rsp+28h]
  11. }
  12. 000000000040103A  add         rsp,38h
  13. 000000000040103E  ret              
  14.        return n<2 ? 1 : fib(n-1)+fib(n-2);
  15. 000000000040103F  add         ecx,0FFFFFFFEh
  16. 0000000000401042  mov         qword ptr [rsp+30h],rbx
  17. 0000000000401047  call        fib  
  18. 000000000040104C  lea         ecx,[rdi-1]
  19. 000000000040104F  mov         ebx,eax
  20. 0000000000401051  call        fib  
  21. 0000000000401056  mov         rdi,qword ptr [rsp+28h]
  22. 000000000040105B  add         eax,ebx
  23. 000000000040105D  mov         rbx,qword ptr [rsp+30h]
  24. }
  25. 0000000000401062  add         rsp,38h
  26. 0000000000401066  ret


Точное время на Jave не знаю, около 3 секунд тоже. Скомпилировано в том же Visual Studo 2005. Не искал в Jave как программно замерить.

Уфффф, в Вин 64 клавиатурная 32-битная дллка транслита не работает, а самому писать 64 битную время жалко. В смысле, время которое нужно чтобы разобраться как.
Приходится на делать пока на translit.ru
 

FR0

новичок
Похоже самый фибоначный язык это D (Digital Mars C, C++ and D Compilers)

code text
  1. int fib(int n)
  2. {
  3.        return n<2 ? 1 : fib(n-1)+fib(n-2);
  4. }
  5.  
  6. int main()
  7. {
  8.    printf("%d\n", fib(36));
  9.    return 0;
  10. }


Работает у меня в полтора раза быстрее чем java. Так что откручивать хвосты рекурсии могут и нативные компиляторы.
 

в начало страницы | новое
 
Поиск
Настройки
Твиттер сайта
Статистика
Рейтинг@Mail.ru