Тарифы Услуги Сим-карты

Сравнительная характеристика, назначение и возможности современных языков. Большое обзорное тестирование языков программирования

Цель этой статьи: Пролить свет и обобщить многочисленные дискуссии о я зыках программирования. Она больше касается сферы соревнований по программированию, но я стараюсь писать мои замечания как можно более конкретно, улучшать и добавлять что-то в процессе своего развития и развития языков программирования. Сегодня 18 ое апреля 2010 го года.

Pascal ( императивный , структурированный )

За: очень простой язык со строгим синтаксисом – прост для начинающих – на нем просто писать программы и отлаживать их.

Против : отсутствие стандартных библиотек (в сравнении с библиотеками C ++ и Java ).

C ++ ( поддерживает много парадигм(multi-paradigm ) : объектно-ориентированное , обобщённое , процедурное , метапрограммирование )

За: STL (стандартная библиотека шаблонов) – много стандартных типов данных и алгоритмов. Большая “свобода” – можно реализовать одни и те же вещи по-разному. Хорошая производительность скомпилированного кода. Хорошая поддержка C ++ сегодня.

Против: Отсутствие BigInteger и BigDecimal (они есть в библиотеках Java и C #). Возможны различные ошибки, вызванные непониманием между компилятором и программистом. Вы можете найти много тем об этом, но это не проблема языка. Но из-за очень большой свободы может быть сложнее писать и отлаживать программы на C ++.

Java ( объектно-ориентированный , структурный , императивный )

За: более строгий синтаксис , чем в C ++ – более простое чтение кода – быстрая и простая отладка. Подсказки об ошибках и неиспользуемом коде. Очень много библиотек различного типа. Сборщик мусора. Новые возможности в последних версиях яв ы ( пр.: вариации цикла for ).

Против: Медленная работа программ (в 3-4 раза медленнее чем C / C ++), длинный (постоянно длинный ) код, но набор кода быстрый, потому что присутствует автодополнение .

Opinion of Petr : I think Java/C# (I don"t see much difference between them except speed) are best suited for programming contests, since it"s so much harder to make a mistake and so much easier to find and fix a mistake in a Java program than in a C/C++ program.

Much more strict type checking (implicit casts from long long to int and from int to bool ??), out-of-range checking, code flow checking (allowing to read from uninitialized variables? why would a language allow that?), fantastic IDE which finds a lot of other mistakes for you, fantastically convenient debugging, more explicit syntax (a language with less power actually leads you to writing more readable programs), more explicit error messages (and the errors are always reproducible!) - to name a few advantages, but I"ve probably missed some more.

I think that writing correct programs and fixing them quickly when they"re not correct far outweigh the disadvantages mentioned above (slower execution, longer programs). Even a 2x slowdown is almost never important in programming competitions, while a WA always is:) And I believe that most of the time at a programming contest is spent in thinking (including the thinking you do _while_ writing code), not in writing code, so the length of the program (or the typing speed, for that matter) is irrelevant.

And I believe the availability of various libraries is also not that important. So if I were to choose between C++ and Pascal, I"d choose Pascal because of the same argument (much more strict checking of everything).

Я не перевел мнение Петра, потому что оно намного лучше звучит на английском.

C # ( поддерживает много парадигм(multi-paradigm ) : объектно-ориентированное , обобщённое , процедурное программирование)

За: Быстрее чем Java . Стандартные библиотеки C #: в последней версии . NET присутствуют, как и в Java , классы для работы с длинной арифметикой, но теперь вы можете использовать их как переменные базовых типов: c = a + b , и т.п.

Против: Последняя версия. NET все еще не доступна на большинстве соревнований по программированию.

VB ( процедурный , объектно-ориентированный , компонентно-ориентированный , событийно-ориентированный )

Отличие от C #: Язык программирования – Visual Basic , а не C #.

Мнение alliumnsk : VB . NET это всего лишь C # с синтаксисом Visual Basic , который был сделан, чтобы облегчить перенос программ, написанных на VB . Т.е. нет никаких причин думать о VB . NET .

Python ( объектно-ориентированный , императивный , функциональный , аспектно-ориентированный )

Мнение _ph_ :

За: Python - язык широкого назначения, на нем пишут практически любые типы программ, за исключением программ реального времени. Не случайно, питон - это официальный язык #3 в Google .

Python отлично подходит для решения не очень сложных задач благодаря краткости записи и наличию встроенных средств:

· встроенная длинная арифметика (как целочисленная, так и дробная)

· встроенные list (aka vector<>), set, dict , tuple (aka struct )

· библиотека для работы с регулярными выражениями re

· функция sorted () для любых последовательностей

· удобные строковые операции

· удобные конструкторы списков

· функции sum (), max (), min (), способные обрабатывать списки и т.д.

Против: К недостаткам Python с точки зрения олимпиадного программирования относятся:

· низкая скорость исполнения программ (в среднем проигрыш в 6 раз по сравнению с С ++) и особенно медленный ввод-вывод (так что без специальных ухищрений 10^6 чисел даже прочитать за 1 сек. не успеешь)

· мало удобных IDE (единственная нормальная, что я знаю, PyDev для Eclipse )

PHP и другие языки программирования.

Пока я не вижу никаких причин использовать их на соревнованиях. Если у вас есть возражения - пишите.

Заключение:

Лучше всего знать и практиковать как можно больше языков, учиться, знать все нюансы, но это не так просто и не всегда возможно. Мы – люди, и мы не можем изменить своей природы, но мы можем постараться стать лучше. Каждый язык программирования имеет свои преимущества и недостатки, и вы всегда можете выбрать один из них для более эффективного решения определенных разных задач.

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

Надеюсь, что эта статья помогла вам понять отличия разных языков программирования, самые основные их преимущества и недостатки.

Дополнительная и использованная информация:

Lisp as an Alternative to Java: http://norvig.com/java-lisp.html

Выбор оружия - обсуждение: http ://codeforces .ru /blog /entry /254

Выбор оружия 2 – обсуждение: http://codeforces.ru/blog/entry/316

C #. Почему не моно? : http ://codeforces .ru /blog /entry /229

Немного о C # и Linq : http ://codeforces .ru /blog /entry /245

Тесты и сравнение производительности Java , C #, C ++:

Определения:

Есть много параметров по которым можно сравнивать языки программирования. Именно как языки, а не платформы. Популярность, инструменты разработки, область применения, позиция на рынке — это важно, но интересно именно посмотреть на языки сами по себе.
Причём пофигу на их парадигмы: и на Си можно писать в ООП стиле (GTK+ например) и на Java можно делать монады .
А как они именно в жизни?

Если вам хочется узнать какой язык популярнее то крайне советую вам почитать статью В поисках самого востребованного языка программирования откуда я нагло стырил КДПВ.

Я же хочу обратить ваше внимание на такие почти субъективные параметры:

1. Набор базовых аксиом.
Есть языки которые похожи на китайский — куча иероглифов. А есть такие как английский, где всего 26 букв, зато из них можно комбинировать слова а из слов предложения.
Очевидно что чем меньше таких вот базовых символов тем проще начать обучение. Но получается больше текста, да.
С другой стороны доводить набор букв до 1 и 0 тоже бессмысленно. Тут нужно подбирать компромис. В среднем во всех языках около 15ти операторов и до десяти базовых типов.
Аксиоматичную мощность можно посчитать и вывести коэффициент. Я даже нашёл какое-то сравнение мощности языков столетней давности .
Это вполне серъёзная и слегка научная задача годная для диссертации. Тут даже и графики красивые в маткаде можно нарисовать и из Википедии накопипастить заумных терминов. По крайней мере всерьёз годик разбираться.
Но вместо этого все пишут диссеры про какую то фигню, в лучшем случае о рефакторинге или RESTе. И кстати тоже не особо мудрённое дело для диссера: метод экстрактнуть или HTTP запрос отправить. Ловите намёк 😉

2. Читабельность.
Большую часть времени программисты читают код а не пишут.
Что такое читабельность начинаешь понимать когда сравниваешь код на питоне и на перле.
Тут такая закономерность: чем высокоуровневей язык программирования, тем лучше читаемость. SQL вообще читается как обычный текст.

3. Синтаксический сахар
Для часто повторяющихся примитивных вещей. Например, в яве строки — это объекты, и по хорошему нужно их конкатенировать вызовом метода. Но чтобы облегчить людям жизнь специально добавили синтаксический сахар когда оператор + выполняет конкатенацию строк.
Но плюс работает только для строк в виде исключения: всем остальным классам переопределять операторы нельзя, чтобы не было удивления в коде.
А вот в C++, C#, Groovy и многих других языках можно делать переопределение операторов и в результате потом часто удивляешься что это за фигня такая в коде: users << new User() пока не узнаёшь что добрые молодцы добавили сахару для добавления в список.
Вообщем, тут опять таки как с языками — если в английском жёсткая грамматика и порядок слов, то в русском пиши как хочешь и синтезируй слова.
В результате иностранцы не могут понять русский.

4. Возможность выстрелить в ногу.
Лучше всего стрелять в ногу на Си: тут ты выхватываешь настоящий Segmentation fault а не унылый NPE. Делишь 1/3 и получаешь 0. Пишешь if (x = 3) и долго удивляешься почему код не работает. И через переполненный буфер хакеры тебе валят сервак.
А ещё в Си бывают приколы когда из-за оптимизирующей опции компилятора программа перестаёт работать вообще.

5. Магия.
Магия это некая фича которая позволяет делать всякие трюки не предусмотренные изначально создателями.
Очень часто мы даже не задумываемся что используем магию.
Например, директивы прекомпилятора и макросы — ими можно и вовсе перевести C++ на олбанский:

#include #include #if !defined (_MSC_VER) || _MSC_VER < 1400 #error Wrong compiler! Use MSVS 8.0 #endif #define НАЧЕЛ { #define КОНЧЕЛ;} #define ТИПА int #define ВДРУГ if (#define ТАДА) #define НИХРИНА else #define ВЗАД return #define КАГДИЛА (#define ЙО; #define ЖЖОШ(p,n) for (; (p) <= (n); (p)++) #define БАЗАР std::cout << #define СЛЫШЬ << #define СТОЙ system ("echo. & pause"); #define БЛИН _wsetlocale (LC_ALL, L"Russian_Russia.ACP"); #define ВРОДЕ try #define ИБАНУЦЦО throw #define АПСТЕНУ catch (const char* __чё__) #define ПРЕВЕД ТИПА main КАГДИЛА ТАДА #define МЕДВЕД ВЗАД 0; КОНЧЕЛ ТИПА КРУТО КАГДИЛА ТИПА фигня ТАДА НАЧЕЛ БАЗАР "ВАЩЕ " ЙО ВДРУГ фигня == 8 ТАДА ИБАНУЦЦО "мля! " ЙО ВЗАД 0 КОНЧЕЛ ПРЕВЕД НАЧЕЛ БЛИН ВРОДЕ НАЧЕЛ ТИПА фишка = 0 ЙО ЖЖОШ (фишка, 10) НАЧЕЛ БАЗАР фишка СЛЫШЬ " "; ВДРУГ фишка >= 5 ТАДА КРУТО (фишка) ЙО КОНЧЕЛ КОНЧЕЛ АПСТЕНУ НАЧЕЛ БАЗАР "ИБАНУЦЦО invoked: " СЛЫШЬ __чё__; КОНЧЕЛ СТОЙ МЕДВЕД

Теперь давайте сравним языки по этим критериям:
Си
1. Минимальный базовый набор аксиом: В Си всё просто: вот функции, вот структуры, вот указатели, в атаку! Труъ сишники, типа Торвальдса, смотрят на плюсников как на позеров.
2. Читабельность: в Си она низкая i++, j—, for (;;) но сильно выручает что набор минимален.
3. Синтаксический сахар: квадратные скобочки для массивов (можно обойтись указателем) и строковые литералы.
4. В ногу вы просто стреляете: выходите за рамки буферов, забываете подчистить память, в результате имеете настоящий Segmentation fault.
5. Как это не удивительно без магии в Си не сделать ни шагу. Магия в Си — это директивы прекомпилятора. На них делается всё: инклдюды, константы, DSL’ы.

Си++
1. Тут уже появляются объекты, но с ними ещё куча свистелок и перделок по числу которых плюсы впереди планеты всей. Вообще появляется ощущение что плюсы тестовый полигон для всех всех концепций программирования.
2. Читабельность почти никакая. Местами спасает только Си-подобный синтаксис.
3. Сахарка хватает: даже хелоу ворлд начинается с переопределённого оператора << для cout.
4. Стреляем в ногу из пулемёта. Классика жанра — множественное наследование.
5. Магия достигается через шаблоны STL и Boost.

Ява
1. Тут просто взяли Си++ и убрали всё лишнее. В результате всё просто: вот класс, вот методы, создаёшь объект и дергаешь методы, если что-то пошло не так кидаешь исключение, «шо не йасно?».
Но всё равно перестарались: вложенные классы, статические методы, примитивные типы. Когда начинаешь готовится к сертификации OSCJP внезапно понимаешь что Явы ты не знаешь.
2. Синтаксис вполне уже читабельный, если не обращать внимание на реликты после Си. По сути lingva franca из-за чего её любят использовать в учебниках.
3. Если знаешь все приколы Си то в ногу стрельнуть уже довольно сложно. Разве что Out of memory или Null pointer exception. Собственно поэтому на Яве и написали кучу энтерпрайзного софта.
4. Магия постигается с помощью аннотаций, которые вообще то не должны влиять на программу. Но по факту по следам аннотаций потом курочат байт код до неузнаваемости.

Си шарп
1. Взяли яву и натащили всего нужного и ненужного в целях маркетинга. Чем структуры отличаются от классов? Зачем нужен yield ? Судя по всему он и не так часто используется .
3. Опять переопределение операторов и прочий зоопарк из C++, но уже с уборщиком мусора.
2. Читабельность нормальная. Ну примерно как у Делфи (создатель один и тот же).
4. Для магии всегда придумывают целые технологии, типа LINQ.

Пайтон
1. Большой набор базовых принципов которые плохо подобраны.
2. Читабельность хорошая — издалека вообще со стихами Маяковского можно спутать.
4. Магия достигается использованием сишных либ которые всё умеют. Поэтому на питоне написано половина гуёв на линуксах.
Вообще про питон лично я ничего хорошего не могу сказать — я на нём написал с десяток скриптов и каждый раз это было отстойно. Может в нём и есть какая фишка.

Перл
2. Перл получился в процессе эволюции текстового редактора. И бесспорно имеет самый ужасный write-only синтаксис. Регулярные выражения как раз пришли из перла, что о многом говорит. Мне кажется создатели Brainfuck просто немного упростили Perl.
4. Магия перла в том что одной строчкой можно переколбасить весь текст в документе.

Руби
По сути — объектно-ориентированный Перл.
1. Синтаксис настолько засахаренный что можно получить диабет.
2. Читабельность как у перла, но выручает единообразие — главное понять концепцию.
4. Магия достигается с помощью метапрограммирования поверх динамизма. Например, если вы дёргаете метод которого нет, то можно его перехватить и всё таки выполнить. Так делают динамик файндеры, например.
Всё что в книжках по яве написано «так делать нельзя» в руби пишут «смотрите, можно даже так сделать!».

Ada
Аду создавали в рамках конкурса для армии США. Т.е. изначально попросили академиков придумать самый крутой язык программирования. За что боролись на то и напоролись.
1. Базовый набор огромен. До появления Scala это был самый богатый язык программирования.
2. При этом великолепный читабельный паскальный синтаксис.
3. Сахару достаточно.
4. Магии практически нет — всё итак уже в языке. Хотя тут я не уверен, я не так много кода видел на Аде.

Я не просто так упомянул Аду, почитайте её критику:

Хоар выразил своё сожаление тем, что «погремушки и побрякушки возобладали над фундаментальными требованиями надёжности и безопасности» и предостерёг от «армады ракет, летящих не туда из-за не обнаруженной вовремя ошибки в компиляторе Ады». Никлаус Вирт высказался более сдержанно, но тоже негативно. Он сказал: «Слишком много всего вываливается на программиста. Я не думаю, что, изучив треть Ады, можно нормально работать. Если вы не освоите всех деталей языка, то в дальнейшем можете споткнуться на них, и это приведёт к неприятным последствиям». Жан Ишбиа, руководитель группы разработчиков Ады, выразив своё «уважение и восхищение» Виртом, не согласился с ним, сказав: «Вирт верит в простые решения сложных проблем. Я не верю в такие чудеса. Сложные проблемы требуют сложных решений».

Стоит ли говорить что в результате Ада в жопе а в космос летает софт написанный на Сишечке?

Scala
Скала это попытка сделать Яву функциональной в ущерб здравому смыслу.
Точно так же как и Аду её придумали искусственно в «лаборатории по созданию языков программирования» что уже должно настораживать.
Как и любая функциональщина, она доводит до оргазма всяких умников с «математическим складом ума».
Нормальным инженерам становится страшно когда они представляют как поддерживать проект на Скале. Scala хуже, чем Java. Как минимум, для половины Java проектов .

Очень хорошо это описано в статье Андрея Платова О выборе языка программирования :

Scala даст огромный рост продуктивности по сравнению с Java (так же как C->C++). Это верно для проекта с одним автором в вакууме. Сложность, и безграничные возможности языка отразятся гемороем, который нихера не будет способствовать продуктивности. Вопрос только в том чего будет больше. Я пока не знаю, да и больших Scala проектов в мире по большому счету еще нет.

Вывод: я пришёл к такому же выводу как Андрей Платов — мы сейчас находимся во время ломки языков программирования и реально выбирать на самом деле не из чего — все текущие языки явно морально устаревшие.
Нужно совершенно новое поколение языков программирования. Да такое чтобы все текущие языки показались такой же глупой затеей как писать на перфокартах.
Но об этом я позже.

Ещё по теме

PS

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

Не поленитесь, и почитайте ещё несколько очень важных моих .
Также не забудьте вступить в группу IT Juniors куда я пытаюсь собирать ссылки на другие полезные статьи для вас и анонсы курсов и интернатуры в компаниях.

Недавно очередной раз отработал со студентам 2-го курса 2-семестровую дисциплину «Алгоритмические языки». Обзорно рассмотрели несколько дюжин языков программирования. Один из студентов, Вадим Шукалюк, захотел получше с ними познакомиться, получить более четкое представление о каждом из них. Посоветовал ему провести небольшое исследование. Чем и увлёк. Предлагаю свой отчёт по проделанной за несколько месяцев вместе с ним работе.

У каждого языка программирования есть свои достоинства и недостатки. Одна из важнейших характеристик транслятора с любого языка — это скорость исполнения программ. Очень трудно или даже невозможно получить точную оценку такой скорости исполнения. Ресурс http://benchmarksgame.alioth.debian.org/ предлагает игровую форму для проверки такой скорости на разных задачах. Но число языков, представленных на этом ресурсе, довольно невелико. Предельную ёмкость стека, критическую величину для рекурсивных вычислений проверить проще, но она может меняться в разных версиях транслятора и быть зависимой от системных настроек.

Тестировались следующие трансляторы: си (gcc, clang, icc), ассемблер (x86, x86-64), ява (OpenJDK), паскаль (fpc), яваскрипт (Google Chrome, Mozilla Firefox), лисп (sbcl, clisp), эрланг, хаскель (ghc, hugs), дино, аук (gawk, mawk, busybox), луа, рубин, бейсик (gambas, libre office), питон-2, пи-эйч-пи, постскрипт (gs), пролог (swipl, gprolog), перл, метапост, Т E Х, тикль, бэш. Исследовались как собственно скорость исполнения нескольких небольших, но трудоёмких алгоритмов, так и:

  • качество оптимизации некоторых трансляторов;
  • особенности при работе с процессорами Intel и AMD;
  • предельное число рекурсивных вызовов (ёмкость стека).
В качестве первой задачи, на которой тестировались все трансляторы, выбран расчёт числа Фибоначчи двойной рекурсией согласно определению: числа с номерами 1 и 2 — это единицы, а последующие — это сумма двух предыдущих. Этот алгоритм имеет несколько привлекательных особенностей:
  1. Если время расчета n-го числа t, то (n+1)-го — t*φ, где φ — это золотое сечение равное (√5+1)/2;
  2. Само вычисляемое n-e число равно округлённой до ближайщего целого величине φ n /√5;
  3. Расчёт fib(n+1) требует n-й вложености вызовов.
Первая особенность позволяет за небольшое время протестировать трансляторы, скорости работы которых различаются в сотни тысяч раз. Вторая особенность позволяет быстро проверять правильность расчетов. Третья особенность теоретически позволяет исследовать ёмкость стека, но из-за того, что расчет при n > 50 становится очень медленным даже на суперкомпьютере, практически использовать эту особенность не представляется возможным.

В следующей таблице 1 во второй колонке указывается название языка, название компилятора и его версия и, если использовалась, опция оптимизации генерируемого кода. В третьей колонке приводится относительное время вычисления на процессоре AMD Phenom II x4 3.2 ГГц. Тесты проводились и на AMD FX-6100 на такой же частоте, но их результаты мало отличаются от приведённых. За единицу принято время вычисления на языке бэш, таким образом, расчёт на эрланге примерно в 20000 раз быстрее бэш. В 4-й колонке приводится относительное время вычисления на процессоре Intel Core i3-2100 3.1 ГГц. Так как сравнение процессоров не было целью исследования, часть трансляторов не были протестированы на платформе Intel. В пятой — оценка сверху (точность 10%) максимального числа рекурсивных вызовов, поддерживаемых транслятором при вычислении ack(1,1,n) на компьютере с 8 Гб оперативной памяти c размером системного стека (ulimit -s) 8192 КБ. Некоторые трансляторы используют собственные настройки, которые определяют размер используемого стека — всегда используются значения по умолчанию для выбранной версии транслятора. Измерения проводились в системе Linux, но их результаты не должны меняться при переходе к другой ОС. Данные отсортированы по 3-й колонке. Все исходники можно посмотреть .

Табл 1.

N Язык AMD Intel Стек
1 C/C++ (gcc 4.7.2, -O5) 354056 493533 790000
2 C/C++ (clang 3.0-6.2, -O3) 307294 270000
3 C/C++ (icc 14.0.3, -fast) 250563 232665 530000
4 Assembler x86-64 243083 271443 350000
5 Assembler x86 211514 301603 700000
6 Java (OpenJDK 1.7.0_25) 186401 239659 8000
7 Pascal (fpc 2.6.0, -O3) 170604 186401 180000
8 C/C++ (gcc 4.7.2, -O0) 159672 173261 180000
9 C/C++ (clang 3.0-6.2, -O0) 146726 110000
10 C/C++ (icc 14.0.3, -O0) 136862 156602 530000
11 Javascript (Mozilla Firefox 25) 121979 4200
12 Javascript (Google Chrome 31) 92850 10000
13 Lisp (sbcl 1.0.57) 54925 51956 31000
14 Erlang (5.9.1) 19845 18589 предела нет
15 Haskell (ghc 7.4.1, -O) 18589 22946 260000
16 Awk (mawk 1.3.3) 6621 6306 44000
17 Lua (5.2) 6420 7075 150000
18 Ruby (1.9.3) 5297 6969 6600
19 Dino (0.55) 5024 6420 190000
20 Basic (Gambas 3.1.1) 3968 4373 26000
21 Python (2.7.3) 3678 4013 1000
22 PHP (5.4.4) 2822 3720 предела нет
23 Awk (gawk 4.0.1) 2648 2547 предела нет
24 Postscript (gs 9.05) 2355 3246 5000
25 Prolog (swipl 5.10.4) 1996 2407 2300000
26 Perl (5.14.2) 1516 1670 предела нет
27 Prolog (gprolog 1.3.0) 1116 1320 120000
28 Lisp (clisp 2.49) 998 1023 5500
29 Awk (busybox 1.20.2) 981 1113 18000
30 T E X (3.1415926) 239 333 3400
31 Metapost (1.504) 235 470 <4100
32 Tcl (8.5) 110 123 1000
33 Haskell (hugs 98.200609.21) 82 121 17000
34 Basic (LibreOffice 3.5.4.2) 20 35 6500
35 bash (4.2.37) 1 0,77 600

В качестве второй задачи выбрана функция Аккермана в форме, когда к ней сводятся все арифметические операции, т. е. ack(1,x,y)=x+y, ack(2,x,y)=x*y, ack(3,x,y)=x y , ack(4,x,y) — тетрация x и y и т. д.

Эта функция с ростом n растёт очень быстро (число ack(5,5,5) настолько велико, что количество цифр в порядке этого числа многократно превосходит количество атомов в наблюдаемой части Вселенной), но считается очень медленно. Последнее свойство теоретически удобно для тестирования быстродействия. Однако, расчет этой функции требует значительного числа рекурсивных вызовов и большинство тестируемых языков оказалось не в состоянии их поддерживать для вычислений, имеющих заметную длительность. Известно, что вычисление этой функции нельзя свести к итерации. Расчет по этой задаче позволил исследовать максимальную ёмкость стека исследуемых языков: расчёт ack(1,1,n-1) требует n-й вложенности вызовов и очень быстр. В следующей таблице 2 представлены результаты расчета пентации ack(5,2,3), для тех языков, стек которых смог его (вложенность вызовов 65539) выдержать. За единицу скорости выбрано время работы gcc с опцией -O5, т. е. php примерно в 420 раз медленнее.

Табл 2.

gcc -O5 1
asm x86 2.15
icc -fast 2.18
asm x86-64 2.36
clang -O3 2.76
fpc -O3 4.44
gcc -O0 7.75
icc -O0 8.36
clang -O0 9.64
Erlang 18.51
ghc -O 50.18
lua 122.55
php 423.64
gawk 433.82
swipl 766.55
dino 915.64

Идея использовать приведённые две задачи позаимствована из труда Б. В. Кернигана и Р. Пайка «Unix — универсальная среда программирования», где она была использована для тестирования языка hoc.

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

Время измерялось стандартной командой time, а тогда, когда это было невозможно (яваскрипт, офисный бейсик) использовались встроенные в язык средства.

По результатам исследования сделаны следующие выводы, некоторые из которых оказались несколько неожиданными:

  1. Скорость работы программ на ассемблере может быть более 50% медленнее, чем программ на си/си++, скомпилированных с максимальной оптимизаций;
  2. Скорость работы виртуальной ява-машины с байт-кодом часто превосходит скорость аппаратуры с кодами, получаемыми трансляторами с языков высокого уровня. Ява-машина уступает по скорости только ассемблеру и лучшим оптимизирующим трансляторам;
  3. Скорость компиляции и исполнения программ на яваскрипт в популярных браузерах лишь в 2-3 раза уступает лучшим трансляторам и превосходит даже некоторые качественные компиляторы, безусловно намного (более чем в 10 раз) обгоняя большинство трансляторов других языков сценариев и подобных им по скорости исполнения программ;
  4. Скорость кодов, генерируемых компилятором языка си фирмы Intel, оказалась заметно меньшей, чем компилятора GNU и иногда LLVM;
  5. Скорость ассемблерных кодов x86-64 может меньше, чем аналогичных кодов x86, примерно на 10%;
  6. Оптимизация кодов лучше работает на процессоре Intel;
  7. Скорость исполнения на процессоре Intel была почти всегда выше, за исключением языков лисп, эрланг, аук (gawk, mawk) и бэш. Разница в скорости по бэш скорее всего вызвана разными настройками окружения на тестируемых системах, а не собственно транслятором или железом. Преимущество Intel особенно заметно на 32-разрядных кодах;
  8. Стек большинства тестируемых языков, в частности, ява и яваскрипт, поддерживают только очень ограниченное число рекурсивных вызовов. Некоторые трансляторы (gcc, icc, ...) позволяют увеличить размер стека изменением переменных среды исполнения или параметром;
  9. В рассматриваемых версиях gawk, php, perl, bash реализован динамический стек, позволяющий использовать всю память компьютера. Но perl и, особенно, bash используют стек настолько экстенсивно, что 8-16 ГБ не хватает для расчета ack(5,2,3). В версии 5.4.20 php стек оказался ограниченным примерно 200000 вызовов.

В заключении несколько слов от студента, начинающего осваивать искусство программирования.

Чтобы написать программы для требуемых расчётов на любом языке, необходимо в первую очередь понять как в конкретном языке объявляются переменные, как построить конструкцию типа if-else и как организовать рекурсию. Свою работу я начал с простого языка Pascal, так как на тот момент знал его лучше всех. После паскаля, я взялся за C, Java и Dino, так как их синтаксисы примерно похожи. С оказался довольно интересным, простым, и в то же время с интуитивно понятными операторами. Ява показался менее удобным, чем си/си++ — надо писать много не относящегося к делу, такого, что могло бы быть взято по умолчанию. Также напряг момент необходимости одинаковости имён класса и файла. От Haskell остались только положительные эмоции. Удобный, понятный и мощный. PHP, язык для разработки веб-приложений, очень похож на С: можно просто вставить код на си с минимальными изменениями и все будет работать так, как надо. Erlang похож по синтаксису на Haskell и немного на Prolog. Тоже довольно приятный и понятный язык, никаких трудностей не возникло. Cинтаксис JavaScript похож на синтасис Java или C. Visual Basic как в офисном, так и GAMBAS исполнении имеет несколько угловатый и неудобный синтаксис, но в целом, с ним было не очень трудно. Затем, после приобретения знаний о базом синтаксисе С и Java, получилось довольно быстро написать код на Python, так как Python схож с С. Никаких проблем не возникло с Lua и его довольно мощными и гибкими конструкциями. У awk также схожее строение с С, довольно быстро удалось его осилить. С лиспом возникли некоторые трудности, как у человека, который до этого изучал С-подобные языки, например, с базовым пониманием префиксной записи. Которая после небольших затрат на освоения, показалась очень удобной, логичной и красивой. После, я перешел на язык логического программирования Prolog, который оказался специфичным, но очень интересным и фундаментальным. Ruby — язык с мощной поддержкой объекто-ориентированного программирования и с очень красивым ярко-красным рубином на иконке оказался превосходным языком: никаких лишних скобок, точек с запятой и прочих ненужных знаков. Один из наиболее запомнившихся. Хотя питон, если не считать конструкций ООП, не менее лаконичен. Perl — хоть и носит название «жемчужина», символом языка является верблюд, что видимо является отсылкой к тому, что верблюд не слишком красивое, но очень выносливое животное, способное выполнять тяжёлую работу. После Ruby опять ставить доллары, скобки и точки с запятой было не очень приятно. Синтаксис местами похож на синтаксис языка терминальной оболочки Bash. Затем я взялся за ассемблер. Здесь были определенные трудности и необходимость понимания работы процессора и его регистров. Удивлению не было предела, когда оказалось, что С справляется с расчётами быстрее чем ассемблер, машинный код! Проблем не возникло с Bash, хоть там и нужно ставить много долларов, а при расчётах и скобок. Язык Metapost/Metafont вызвал некоторые проблемы — там поддерживаются только числа, не большие 4096. Хотя его синтаксис вполне традиционен. У тикля (TCL) тоже довольно традиционный синтаксис, но строчно-ориентированный — это и похожая на bash семантика поначалу очень сбивали с толку. Наиболее сложным показались PostScript. В этом языке синтаксис очень специфичен и без подготовки, интуитивно ничего написать не получится, поэтому пришлось изучать соответствующую литературу и начать тренироваться с самых простейших программок. PostScript был настоящим испытанием: написать двойную рекурсию постфиксной записью лишь при помощи стека, после привыкания ко всем инструментам и возможностям Ruby и C было проблематично. Писать и тестрировать на постскрипте функцию Аккермана, все равно что пытаться покрасить стену зубной щёткой. Но первое место по сложности определенно занимает T E X. Ничего более трудного я не встречал. И без прямой помощи преподавателя одолеть этот язык не получилось бы.

Любопытными оказались данные по размерам стека языков. Чем больше стек языка, тем больше вероятность, что он сможет справиться с функцией Аккермана. Но если программа на каком-то языке не смогла справиться с вычислением ack(5,2,3), это не значит что язык плохой и неудобный. Вполне вероятно, что этот язык мог создаваться для других полезных целей как, например, Metapost или Postscript.

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

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

— Разработаный в России

Недавно очередной раз отработал со студентам 2-го курса 2-семестровую дисциплину «Алгоритмические языки». Обзорно рассмотрели несколько дюжин языков программирования. Один из студентов, Вадим Шукалюк, захотел получше с ними познакомиться, получить более четкое представление о каждом из них. Посоветовал ему провести небольшое исследование. Чем и увлёк. Предлагаю свой отчёт по проделанной за несколько месяцев вместе с ним работе.

У каждого языка программирования есть свои достоинства и недостатки. Одна из важнейших характеристик транслятора с любого языка — это скорость исполнения программ. Очень трудно или даже невозможно получить точную оценку такой скорости исполнения. Ресурс http://benchmarksgame.alioth.debian.org/ предлагает игровую форму для проверки такой скорости на разных задачах. Но число языков, представленных на этом ресурсе, довольно невелико. Предельную ёмкость стека, критическую величину для рекурсивных вычислений проверить проще, но она может меняться в разных версиях транслятора и быть зависимой от системных настроек.

Тестировались следующие трансляторы: си (gcc, clang, icc), ассемблер (x86, x86-64), ява (OpenJDK), паскаль (fpc), яваскрипт (Google Chrome, Mozilla Firefox), лисп (sbcl, clisp), эрланг, хаскель (ghc, hugs), дино, аук (gawk, mawk, busybox), луа, рубин, бейсик (gambas, libre office), питон-2, пи-эйч-пи, постскрипт (gs), пролог (swipl, gprolog), перл, метапост, Т E Х, тикль, бэш. Исследовались как собственно скорость исполнения нескольких небольших, но трудоёмких алгоритмов, так и:

  • качество оптимизации некоторых трансляторов;
  • особенности при работе с процессорами Intel и AMD;
  • предельное число рекурсивных вызовов (ёмкость стека).
В качестве первой задачи, на которой тестировались все трансляторы, выбран расчёт числа Фибоначчи двойной рекурсией согласно определению: числа с номерами 1 и 2 — это единицы, а последующие — это сумма двух предыдущих. Этот алгоритм имеет несколько привлекательных особенностей:
  1. Если время расчета n-го числа t, то (n+1)-го — t*φ, где φ — это золотое сечение равное (√5+1)/2;
  2. Само вычисляемое n-e число равно округлённой до ближайщего целого величине φ n /√5;
  3. Расчёт fib(n+1) требует n-й вложености вызовов.
Первая особенность позволяет за небольшое время протестировать трансляторы, скорости работы которых различаются в сотни тысяч раз. Вторая особенность позволяет быстро проверять правильность расчетов. Третья особенность теоретически позволяет исследовать ёмкость стека, но из-за того, что расчет при n > 50 становится очень медленным даже на суперкомпьютере, практически использовать эту особенность не представляется возможным.

В следующей таблице 1 во второй колонке указывается название языка, название компилятора и его версия и, если использовалась, опция оптимизации генерируемого кода. В третьей колонке приводится относительное время вычисления на процессоре AMD Phenom II x4 3.2 ГГц. Тесты проводились и на AMD FX-6100 на такой же частоте, но их результаты мало отличаются от приведённых. За единицу принято время вычисления на языке бэш, таким образом, расчёт на эрланге примерно в 20000 раз быстрее бэш. В 4-й колонке приводится относительное время вычисления на процессоре Intel Core i3-2100 3.1 ГГц. Так как сравнение процессоров не было целью исследования, часть трансляторов не были протестированы на платформе Intel. В пятой — оценка сверху (точность 10%) максимального числа рекурсивных вызовов, поддерживаемых транслятором при вычислении ack(1,1,n) на компьютере с 8 Гб оперативной памяти c размером системного стека (ulimit -s) 8192 КБ. Некоторые трансляторы используют собственные настройки, которые определяют размер используемого стека — всегда используются значения по умолчанию для выбранной версии транслятора. Измерения проводились в системе Linux, но их результаты не должны меняться при переходе к другой ОС. Данные отсортированы по 3-й колонке. Все исходники можно посмотреть .

Табл 1.

N Язык AMD Intel Стек
1 C/C++ (gcc 4.7.2, -O5) 354056 493533 790000
2 C/C++ (clang 3.0-6.2, -O3) 307294 270000
3 C/C++ (icc 14.0.3, -fast) 250563 232665 530000
4 Assembler x86-64 243083 271443 350000
5 Assembler x86 211514 301603 700000
6 Java (OpenJDK 1.7.0_25) 186401 239659 8000
7 Pascal (fpc 2.6.0, -O3) 170604 186401 180000
8 C/C++ (gcc 4.7.2, -O0) 159672 173261 180000
9 C/C++ (clang 3.0-6.2, -O0) 146726 110000
10 C/C++ (icc 14.0.3, -O0) 136862 156602 530000
11 Javascript (Mozilla Firefox 25) 121979 4200
12 Javascript (Google Chrome 31) 92850 10000
13 Lisp (sbcl 1.0.57) 54925 51956 31000
14 Erlang (5.9.1) 19845 18589 предела нет
15 Haskell (ghc 7.4.1, -O) 18589 22946 260000
16 Awk (mawk 1.3.3) 6621 6306 44000
17 Lua (5.2) 6420 7075 150000
18 Ruby (1.9.3) 5297 6969 6600
19 Dino (0.55) 5024 6420 190000
20 Basic (Gambas 3.1.1) 3968 4373 26000
21 Python (2.7.3) 3678 4013 1000
22 PHP (5.4.4) 2822 3720 предела нет
23 Awk (gawk 4.0.1) 2648 2547 предела нет
24 Postscript (gs 9.05) 2355 3246 5000
25 Prolog (swipl 5.10.4) 1996 2407 2300000
26 Perl (5.14.2) 1516 1670 предела нет
27 Prolog (gprolog 1.3.0) 1116 1320 120000
28 Lisp (clisp 2.49) 998 1023 5500
29 Awk (busybox 1.20.2) 981 1113 18000
30 T E X (3.1415926) 239 333 3400
31 Metapost (1.504) 235 470 <4100
32 Tcl (8.5) 110 123 1000
33 Haskell (hugs 98.200609.21) 82 121 17000
34 Basic (LibreOffice 3.5.4.2) 20 35 6500
35 bash (4.2.37) 1 0,77 600

В качестве второй задачи выбрана функция Аккермана в форме, когда к ней сводятся все арифметические операции, т. е. ack(1,x,y)=x+y, ack(2,x,y)=x*y, ack(3,x,y)=x y , ack(4,x,y) — тетрация x и y и т. д.

Эта функция с ростом n растёт очень быстро (число ack(5,5,5) настолько велико, что количество цифр в порядке этого числа многократно превосходит количество атомов в наблюдаемой части Вселенной), но считается очень медленно. Последнее свойство теоретически удобно для тестирования быстродействия. Однако, расчет этой функции требует значительного числа рекурсивных вызовов и большинство тестируемых языков оказалось не в состоянии их поддерживать для вычислений, имеющих заметную длительность. Известно, что вычисление этой функции нельзя свести к итерации. Расчет по этой задаче позволил исследовать максимальную ёмкость стека исследуемых языков: расчёт ack(1,1,n-1) требует n-й вложенности вызовов и очень быстр. В следующей таблице 2 представлены результаты расчета пентации ack(5,2,3), для тех языков, стек которых смог его (вложенность вызовов 65539) выдержать. За единицу скорости выбрано время работы gcc с опцией -O5, т. е. php примерно в 420 раз медленнее.

Табл 2.

gcc -O5 1
asm x86 2.15
icc -fast 2.18
asm x86-64 2.36
clang -O3 2.76
fpc -O3 4.44
gcc -O0 7.75
icc -O0 8.36
clang -O0 9.64
Erlang 18.51
ghc -O 50.18
lua 122.55
php 423.64
gawk 433.82
swipl 766.55
dino 915.64

Идея использовать приведённые две задачи позаимствована из труда Б. В. Кернигана и Р. Пайка «Unix — универсальная среда программирования», где она была использована для тестирования языка hoc.

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

Время измерялось стандартной командой time, а тогда, когда это было невозможно (яваскрипт, офисный бейсик) использовались встроенные в язык средства.

По результатам исследования сделаны следующие выводы, некоторые из которых оказались несколько неожиданными:

  1. Скорость работы программ на ассемблере может быть более 50% медленнее, чем программ на си/си++, скомпилированных с максимальной оптимизаций;
  2. Скорость работы виртуальной ява-машины с байт-кодом часто превосходит скорость аппаратуры с кодами, получаемыми трансляторами с языков высокого уровня. Ява-машина уступает по скорости только ассемблеру и лучшим оптимизирующим трансляторам;
  3. Скорость компиляции и исполнения программ на яваскрипт в популярных браузерах лишь в 2-3 раза уступает лучшим трансляторам и превосходит даже некоторые качественные компиляторы, безусловно намного (более чем в 10 раз) обгоняя большинство трансляторов других языков сценариев и подобных им по скорости исполнения программ;
  4. Скорость кодов, генерируемых компилятором языка си фирмы Intel, оказалась заметно меньшей, чем компилятора GNU и иногда LLVM;
  5. Скорость ассемблерных кодов x86-64 может меньше, чем аналогичных кодов x86, примерно на 10%;
  6. Оптимизация кодов лучше работает на процессоре Intel;
  7. Скорость исполнения на процессоре Intel была почти всегда выше, за исключением языков лисп, эрланг, аук (gawk, mawk) и бэш. Разница в скорости по бэш скорее всего вызвана разными настройками окружения на тестируемых системах, а не собственно транслятором или железом. Преимущество Intel особенно заметно на 32-разрядных кодах;
  8. Стек большинства тестируемых языков, в частности, ява и яваскрипт, поддерживают только очень ограниченное число рекурсивных вызовов. Некоторые трансляторы (gcc, icc, ...) позволяют увеличить размер стека изменением переменных среды исполнения или параметром;
  9. В рассматриваемых версиях gawk, php, perl, bash реализован динамический стек, позволяющий использовать всю память компьютера. Но perl и, особенно, bash используют стек настолько экстенсивно, что 8-16 ГБ не хватает для расчета ack(5,2,3). В версии 5.4.20 php стек оказался ограниченным примерно 200000 вызовов.

В заключении несколько слов от студента, начинающего осваивать искусство программирования.

Чтобы написать программы для требуемых расчётов на любом языке, необходимо в первую очередь понять как в конкретном языке объявляются переменные, как построить конструкцию типа if-else и как организовать рекурсию. Свою работу я начал с простого языка Pascal, так как на тот момент знал его лучше всех. После паскаля, я взялся за C, Java и Dino, так как их синтаксисы примерно похожи. С оказался довольно интересным, простым, и в то же время с интуитивно понятными операторами. Ява показался менее удобным, чем си/си++ — надо писать много не относящегося к делу, такого, что могло бы быть взято по умолчанию. Также напряг момент необходимости одинаковости имён класса и файла. От Haskell остались только положительные эмоции. Удобный, понятный и мощный. PHP, язык для разработки веб-приложений, очень похож на С: можно просто вставить код на си с минимальными изменениями и все будет работать так, как надо. Erlang похож по синтаксису на Haskell и немного на Prolog. Тоже довольно приятный и понятный язык, никаких трудностей не возникло. Cинтаксис JavaScript похож на синтасис Java или C. Visual Basic как в офисном, так и GAMBAS исполнении имеет несколько угловатый и неудобный синтаксис, но в целом, с ним было не очень трудно. Затем, после приобретения знаний о базом синтаксисе С и Java, получилось довольно быстро написать код на Python, так как Python схож с С. Никаких проблем не возникло с Lua и его довольно мощными и гибкими конструкциями. У awk также схожее строение с С, довольно быстро удалось его осилить. С лиспом возникли некоторые трудности, как у человека, который до этого изучал С-подобные языки, например, с базовым пониманием префиксной записи. Которая после небольших затрат на освоения, показалась очень удобной, логичной и красивой. После, я перешел на язык логического программирования Prolog, который оказался специфичным, но очень интересным и фундаментальным. Ruby — язык с мощной поддержкой объекто-ориентированного программирования и с очень красивым ярко-красным рубином на иконке оказался превосходным языком: никаких лишних скобок, точек с запятой и прочих ненужных знаков. Один из наиболее запомнившихся. Хотя питон, если не считать конструкций ООП, не менее лаконичен. Perl — хоть и носит название «жемчужина», символом языка является верблюд, что видимо является отсылкой к тому, что верблюд не слишком красивое, но очень выносливое животное, способное выполнять тяжёлую работу. После Ruby опять ставить доллары, скобки и точки с запятой было не очень приятно. Синтаксис местами похож на синтаксис языка терминальной оболочки Bash. Затем я взялся за ассемблер. Здесь были определенные трудности и необходимость понимания работы процессора и его регистров. Удивлению не было предела, когда оказалось, что С справляется с расчётами быстрее чем ассемблер, машинный код! Проблем не возникло с Bash, хоть там и нужно ставить много долларов, а при расчётах и скобок. Язык Metapost/Metafont вызвал некоторые проблемы — там поддерживаются только числа, не большие 4096. Хотя его синтаксис вполне традиционен. У тикля (TCL) тоже довольно традиционный синтаксис, но строчно-ориентированный — это и похожая на bash семантика поначалу очень сбивали с толку. Наиболее сложным показались PostScript. В этом языке синтаксис очень специфичен и без подготовки, интуитивно ничего написать не получится, поэтому пришлось изучать соответствующую литературу и начать тренироваться с самых простейших программок. PostScript был настоящим испытанием: написать двойную рекурсию постфиксной записью лишь при помощи стека, после привыкания ко всем инструментам и возможностям Ruby и C было проблематично. Писать и тестрировать на постскрипте функцию Аккермана, все равно что пытаться покрасить стену зубной щёткой. Но первое место по сложности определенно занимает T E X. Ничего более трудного я не встречал. И без прямой помощи преподавателя одолеть этот язык не получилось бы.

Любопытными оказались данные по размерам стека языков. Чем больше стек языка, тем больше вероятность, что он сможет справиться с функцией Аккермана. Но если программа на каком-то языке не смогла справиться с вычислением ack(5,2,3), это не значит что язык плохой и неудобный. Вполне вероятно, что этот язык мог создаваться для других полезных целей как, например, Metapost или Postscript.

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

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

— Разработаный в России

В предыдущих частях была проделана реализация совершенно однотипного приложения на нескольких разных языках программирования. Сначала мы рассмотрели реализации на 10-ти самых часто употребляемых языков в разработке реальных проектов, а затем - такие же реализации на языках новых, или более экзотических, или имеющих ограниченные сферы применения. Целью такого сравнения было посмотреть как внешне, не предвзятым взглядом, выглядит однотипный код, когда он записан на разных языках.

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

Примечание : Подобные оценки не могут служить критерием качества и даже производительности. Различные по идеологии языки будут иметь совершенно различающуюся относительную производительность на различных классах задач. Поэтому речь может идти только о сравнении порядков производительности и на определённом классе задач.

Примечание : Не ищите какого-то скрытого смысла и подтекста в том порядке, в котором представлены различные языки - они описаны в том произвольном порядке, в котором они хронологически тестировались.

Задача

Для сравнения производительности непригодна задача, демонстрируемая в предыдущей части. Поэтому нам предстоит снова реализовать линейку идентичных приложений на разных языках для такого сравнения. Задачу мы хотим использовать вычислительного сорта, простейшую и в реализации и понимании, которая имела бы очень высокую степень роста от размерности (например экспоненциальную), чтобы можно было в самых широких пределах изменять интегральную потребность в вычислительных операциях.

Для грубых оценок вполне пригодна задача рекурсивного вычисления чисел Фибоначчи. Эта функция настолько проста, что её формулировка будет просто показана в изложении кода на языке C.

Примечание (для дотошной публики) : Существуют 2 определения последовательности чисел Фибоначчи: а). F 1 =0, F 2 =1, F N =F N-1 +F N-2 и б). F 1 =1, F 2 =1, F N =F N-1 +F N-2 . Как легко видеть, эти последовательности сдвинуты на 1 член, так что не стоит ломать копья по этому поводу: можно использовать любую форму. Мы будем использовать 2-ю.

Существуют эффективные алгоритмы вычисления последовательности чисел Фибоначчи (циклические, слева направо). Мы же сознательно будем использовать неэффективную рекурсивную реализацию (справа налево), именно в той форме, как выражения записаны выше. При таком алгоритме задача как раз удовлетворяет требованию высогой степени роста вычислительной сложности, о которой было сказано ранее.

Подготовка приложений к исполнению очень различается между рассматриваемыми языками: где-то это просто исходный код, который подаётся на вход интерпретатора, в других случаях требуется компиляция в промежуточный байт-код или компиляция в исполняемый машинный код. Все промежуточные фазы подготовки, где они требуются, сведены в один Makefile.

Многие языковые средства предполагают и предоставляют те или иные способы оптимизации выполнения (например, уровень оптимизации, указываемый компилятору). Там, где мне известны способы оптимизации выполнения, будет использоваться максимальный уровень оптимизации.

Запуск команд на хронометраж мы станем делать командами вида:

# time nice -19 <команда_fibo> 30
  • команда выполняется от root, чтобы позволить повысить приоритет (nice -9) задачи выше нормального, снизив дисперсию результатов;
  • хронометраж выполняется системной командой time (не будем вмешиваться в процесс временных измерений);
  • параметр (30, порядковый номер числа Фибоначчи) определяет размерность задачи, объём вычислений в зависимости от него нарастает экспоненциально.

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

Результаты выполнения могут радикально меняться в зависимости от версии используемых инструментальных средств (компилятора, интерпретатора). Поэтому в итогах выполнения будет показываться версия используемого программного обеспечения.

Язык C

Листинг 1. Реализация задачи на языке C (fibo_c.c):
#include unsigned long fib(int n) { return n < 2 ? 1: fib(n - 1) + fib(n - 2); } int main(int argc, char **argv) { unsigned num = atoi(argv[ 1 ]); printf("%ld\n", fib(num)); return 0; }

Выполнение:

$ gcc --version gcc (GCC) 4.8.2 20131212 (Red Hat 4.8.2-7) ... # time nice -19 ./fibo_c 30 1346269 real 0m0.013s user 0m0.010s sys 0m0.002s

Можно предположить, что приложение, компилированное из C кода, будет самым быстрым. Поэтому именно эти цифры мы станем использовать как базовые значения для сравнения.

C++

Реализация будет выглядеть так:

Листинг 2. Реализация на языке C++ (fibo_c.cc):
#include #include using namespace std; unsigned long fib(int n) { return n < 2 ? 1: fib(n - 1) + fib(n - 2); } int main(int argc, char **argv) { unsigned num = atoi(argv[ 1 ]); cout << fib(num) << endl; return 0; }

Из этого единого кода будет создано 2 приложения - компиляцией GCC и компиляцией Clang:

$ g++ -O3 fibo_cc.cc -o fibo_cc $ clang++ fibo_cc.cc -o fibo_cl

Выполнение приложения, собранного GCC:

# time nice -19 ./fibo_cc 30 1346269 real 0m0.014s user 0m0.012s sys 0m0.002s

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

Выполнение приложения, собранного Clang:

$ clang++ --version clang version 3.3 (tags/RELEASE_33/final) Target: i386-redhat-linux-gnu Thread model: posix # time nice -19 ./fibo_cl 30 1346269 real 0m0.035s user 0m0.033s sys 0m0.001s

Здесь всё гораздо хуже! Это в 2.7 раза медленнее, чем для GCC. Но в объяснение этого может быть то, что в команде компиляции Clang вообще не устанавливалась опция оптимизации (-O...).

Java

Листинг 3. Реализация задачи на Java (fibo.java):
public class fibo { public static long fib(int n) { return n < 2 ? 1: fib(n - 1) + fib(n - 2); } public static void main(String args) { int num = new Integer(args[ 0 ]).intValue(); System.out.println(fib(num)); } }

Компиляция приложения выполняется в реализации OpenJDK:

$ java -version java version "1.7.0_51" OpenJDK Runtime Environment (fedora-2.4.5.1.fc20-i386 u51-b31) OpenJDK Server VM (build 24.51-b03, mixed mode) $ javac fibo.java $ ls -l *.class -rw-r--r-- 1 olej olej 594 Фев 15 16:09 fibo.class

Если то же самое проделать с оригинальном Oracle JDK, то временные результаты могут отличаться.

Выполнение:

# time nice -19 java fibo 30 1346269 real 0m0.176s user 0m0.136s sys 0m0.047s

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

Python

Аналогичный код на Python:

Листинг 4. Реализация на Python (fibo.py):
#!/usr/bin/python # -*- coding: utf-8 -*- import sys def fib(n) : if n < 2: return 1 else: return fib(n - 1) + fib(n - 2) n = int(sys.argv[ 1 ]) print("{}".format(fib(int(sys.argv[ 1 ]))))

Для этого кода (он написан в совместимом синтаксисе) мы можем также предложить 2 различных способа исполнения:

  • Python версии 2: $ python --version Python 2.7.5 # time nice -19 python fibo.py 30 1346269 real 0m1.109s user 0m1.100s sys 0m0.005s
  • Python версии 3: $ python3 --version Python 3.3.2 # time nice -19 python3 fibo.py 30 1346269 real 0m1.838s user 0m1.823s sys 0m0.009s

Первое, что здесь сразу бросается в глаза: Python 2 быстрее Python 3 на 65%. Это достаточно ожидаемо - это естественная плата за существенно расширенный синтаксис. Ряд публикаций показывают даже существенно большую разницу на определённых классах задач, до 2-х или 3-х раз.

А вот в сравнении с нативным компилированным кодом C Python 2 проигрывает до 100 (85) раз! Это тоже соответствует тому, что звучит в публикациях.

Ruby

Листинг 5. Реализация задачи на Ruby (fibo.rb):
#!/usr/bin/ruby # coding: utf-8 def fib(n) return n < 2 ? 1: fib(n - 1) + fib(n - 2) end puts fib(ARGV[ 0 ].to_i)

Выполнение:

$ ruby --version ruby 2.0.0p353 (2013-11-22 revision 43784) # time nice -19 ruby fibo.rb 30 1346269 real 0m0.566s user 0m0.554s sys 0m0.009s

Здесь время выполнения, на удивление (непонятно почему), почти в 2 раза (1.77) лучше, чем у Python, и медленнее нативного кода C примерно в 43 раза.

Perl

Листинг 6. Реализация задачи на Perl (fibo.pm):
#!/usr/bin/perl sub fib { my $n = shift; $n < 2 ? 1: fib($n - 1) + fib($n - 2) } $f = fib($ARGV[ 0 ]); print "$f\n";

Выполнение:

$ perl --version This is perl 5, version 18, subversion 2 (v5.18.2) built for i386-linux-thread-multi ... # time nice -19 perl fibo.pm 30 1346269 real 0m2.335s user 0m2.329s sys 0m0.002s

Здесь проигрыш нативному коду C составляет свыше 179 раз! Но это достаточно естественно и ожидаемо - Perl не язык для вычислений, и его ниша это текстовая обработка.

JavaScript

Листинг 7. Реализация на JavaScript (файл fibo.js):
#!/usr/bin/js -U var fib = function(n) { // функциональный литерал return n < 2 ? 1: fib(n - 1) + fib(n - 2); } print(fib(arguments[ 0 ]))

Выполнение приложения (начиная с уточнения версии):

$ js -v JavaScript-C 1.8.5 2011-03-31 # time nice -19 js fibo.js 30 1346269 real 0m0.689s user 0m0.683s sys 0m0.005s

Этот результат удивил: это почти те же цифры, что и у Ruby, и в 2 раза лучше, чем Python. От нативного кода C здесь отставание в 53 раза.

PHP

Эквивалент задачи, выраженный на языке PHP:

Листинг 8. Реализация PHP (файл fibo.php):
#!/usr/bin/php

Выполнение приложения:

$ php --version PHP 5.5.9 (cli) (built: Feb 11 2014 08:25:04) Copyright (c) 1997-2014 The PHP Group Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies # time nice -19 php fibo.php 30 1346269 real 0m1.307s user 0m1.292s sys 0m0.013s

Это в 108 раз медленнее, чем эквивалентное C приложение.

Lua

Листинг 9. Реализация задачи на языке Lua (файл fibo.lua):
#!/usr/bin/lua fib = function(n) -- функциональный литерал if(n < 2) then return 1 else return fib(n - 1) + fib(n - 2) end end print(fib(arg[ 1 ] + 0))

Выполнение такого приложения (с проверкой версии Lua):

$ lua Lua 5.2.2 Copyright (C) 1994-2013 Lua.org, PUC-Rio > # time nice -19 lua fibo.lua 30 1346269 real 0m0.629s user 0m0.624s sys 0m0.003s

Это те же результаты, что и у JavaScript и Ruby.

bash

Можно ли организовать подобные вычисления в интерпретаторе bash, учитывая, что функции bash могут возвращать только значения кода завершения в пределах , т. е. в нашем смысле - не имеющие возвращаемых вычисленных значений? Прежде всего, можно организовать подобные вычисления, если сам скрипт будет рекурсивно вызывать свои копии. Вот только то и всего:

Листинг 10. Реализация задачи в bash (файл fido.sh):
#!/bin/bash if [ "$1" -lt "2" ] then echo "1" else f1=$($0 `expr $1 - 1`) f2=$($0 `expr $1 - 2`) echo `expr $f1 + $f2` fi

Я не рискну вызывать такое решение с аргументом 30 (как остальные варианты) - я просто не дождусь решения... Но выполняется такого скрипт вполне успешно:

$ bash --version GNU bash, version 4.2.37(1)-release (i486-pc-linux-gnu) … # time nice -19 ./fibo.sh 10 89 real 0m1.137s user 0m0.350s sys 0m0.475s # time nice -19 ./fibo.sh 12 233 real 0m2.979s user 0m0.935s sys 0m1.248s # time nice -19 ./fibo.sh 14 610 real 0m7.857s user 0m2.528s sys 0m3.166s

Получается, что скрипт bash вычисляет функцию от 8 столько же, сколько не очень «спешному» Perl требуется для вычисления функции от 29 (это при экспоненциальном то росте!):

# time nice -19 perl fibo.pm 29 832040 real 0m1.464s user 0m1.448s sys 0m0.004s

Практического смысла показанная реализация bash не имеет, но сама такая возможность интересна. Другой возможностью может быть искусственно организованная рекурсия (с очередью, стеком возвратов) при вызове функции внутри скрипта:

Листинг 11. Внутренняя рекурсия в bash (файл fido_f.sh):
#!/bin/bash declare -a res fib () { if [ "$1" -lt 2 ] then res[ $1 ]=1. else. fib `expr $1 - 1` let s=${res[ `expr $1 - 1` ]}+${res[ `expr $1 - 2` ]} res[ $1 ]=$s fi } res[ 0 ]=1 fib $1 echo ${res[ $1 ]}

Здесь уже совсем другие результаты:

# time nice -19 ./fibo_f.sh 30 1346269 real 0m0.157s user 0m0.037s sys 0m0.083s # time nice -19 ./fibo_f.sh 60 2504730781961 real 0m0.337s user 0m0.075s sys 0m0.167s

Для N=60 результат даже превосходит результаты выполнения нативного C кода. Но здесь мы просто наблюдаем результат обмана: при вычислениях сделана «оптимизация» и фактически рекурсивное вычисление выродилось в циклическое, не порождающее 2-х деревьев рекурсивных вызовов.