Производительность языков программирования. Большое обзорное тестирование языков программирования. Компиляторы и интерпретаторы

На iOS - iPhone, iPod touch 25.02.2019
На iOS - iPhone, iPod touch

Цель этой статьи: Пролить свет и обобщить многочисленные дискуссии о я зыках программирования. Она больше касается сферы соревнований по программированию, но я стараюсь писать мои замечания как можно более конкретно, улучшать и добавлять что-то в процессе своего развития и развития языков программирования. Сегодня 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 ++:

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

В предыдущих частях была проделана реализация совершенно однотипного приложения на нескольких разных языках программирования. Сначала мы рассмотрели реализации на 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-х деревьев рекурсивных вызовов.

Недавно очередной раз отработал со студентам 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 разными способами. Также, понимание принципа работы регистров процессора и написания двойной рекурсивной функции лишь при помощи стека и трех операций: добавить, удалить и прокрутить стек сильно расширило мой кругозор.

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

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

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

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

Но реальность программирования гораздо сложнее.

Программирование сегодня

Это странно, но большинство действительно “глобальных” идей в компьютерном программировании были разработаны еще в 1950-х и 60-х годах. С тех пор появилось много новых языков, но ни один из них не реализует действительно нового подхода к логике и вычислениям.

Разработка новых языков программирования в течение последних нескольких десятилетий была основана на опыте разработчиков. Это означает, что появился код, который стало проще писать (движущая сила Ruby) и проще читать (Python), и делать определенные типы логических структур и способы решения проблем более интуитивными.

Некоторые языки были разработаны для решения конкретных проблем в программировании (например PHP и SASS), чтобы управлять определенными типами систем (), или для работы в определенной среде или на определенной платформе (Java и JavaScript). Некоторые языки были разработаны специально для того, чтобы помочь новичкам научиться программировать (классическими примерами являются BASIC и Scratch).

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

Относительно недавнее развитие включает в себя такое понятие, как SOA (Service Oriented Architecture- сервисо-ориентированная архитектура ) и MVC (Model-View-Controller), а также фреймворки, такие как , позволяющие программистам легко работать в рамках этих парадигм.

Список языков программирования

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

Кодировка ASCII

  • Кодировка символов является одним из основных компьютерных и Интернет аспектов. ASCII – это первая, широко использованная система кодировки символов. Она была вытеснена UTF-8, но ASCII по-прежнему является основой для подавляющего большинства символов в Интернете и на сегодняшний день. Понимание этого очень важно для программистов. Читайте подробнее здесь (англ):

ASP / ASP.NET

  • ASP – это аббревиатура для Active Server Pages. Это первый скриптовый серверный язык для веб-сервера Microsoft IIS. ASP был заменен на серверный фреймворк с открытым исходным кодом – ASP.NET. Подробнее (англ):

AutoLISP

  • AutoLISP – это простой, легкий, интерпретируемый язык программирования, созданный специально для автоматизированного проектирования программного обеспечения. Читайте об этом (англ):

Awk

  • Awk является чрезвычайно мощным языком программирования для обработки текстов, позволяющим извлекать данные из файла или другого источника, и выводить их в любом формате, который вам нужен. Он является уже старым инструментом, но все еще так же полезен, как и раньше. Узнайте подробнее (англ): .

BASH

  • Bash – это наиболее часто используемый интерфейс командной строки в мире Unix. Это интерфейс на основе текста по умолчанию и для Linux и для Mac OS X. Подробнее (англ):

Common Lisp

  • Lisp является довольно уникальным языком программирования, возможно, самым древним языком и до сих пор продолжает использоваться. Это особенно важно в области искусственного интеллекта. Подробнее (англ):

C

  • Если мы включим сюда две производные этого языка, то смело можно будет сказать, что ни один язык не проиносил большей пользы и большего влияния, чем С. Это особенно важно для развития операционных систем и другого программного обеспечения. Многие компиляторы и интерпретаторы для других языков написаны на языке C. Подробнее (англ):

C++

  • Первоначально он назывался “C с классами”, C++, во многих отношениях, просто более продвинутый преемник C (хотя в целом ситуация сложнее). C++ был разработан, чтобы добавить высокий уровень парадигмы программирования C, сохраняя при этом возможности аппаратной манипуляции низкого уровня. Многие из этих дополнений добавлялись в C на протяжении многих лет, и языки больше похожи на два диалекта одного и того же языка. Подробнее (англ):

C#

  • Использовался в качестве основного языка для.NET программирования, похож на C++, является расширением языка программирования C, но с важным дополнением в виде объектно-ориентированных возможностей. Подробнее (англ):

CSS / CSS3

  • CSS или Cascading Style Sheets, также не является языком программирования, а языком стиля страницы – это язык, предоставляющий стиль и правила компоновки документам и приложениям. Является основным используемым в Интернете языком стиля. Подробнее:

Emacs Lisp

  • Emacs уже давно был известен как популярный и мощный текстовый редактор. Но добавление в него Emacs Lisp, превращает его в интегрированную среду разработки для почти любого языка программирования. Подробнее (англ): .

F#

  • F# – язык программирования общего назначения. Разработан, чтобы быть чрезвычайно эффективным. Будучи изначально только языком Microsoft, теперь является языком с открытым исходным кодом и используется на всех платформах. Подробнее (англ): .

FORTAN

  • Fortran впервые появился в 1957 году и до сих пор используется для решения некоторых из наиболее сложных проблем современной науки и техники. Подробнее (англ):

FORTH

  • Работа над Forth началась в 1968 году, и язык обычно используется на оборудовании, не имеющем традиционную операционную систему. Он также широко используется для управления станками. Подробнее (англ):

Haskell

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

HTML

  • HTML не является языком программирования. Это язык разметки – язык добавления смысловых и стилистических аннотаций содержимому. Является основным языком для веб-контента. Знание его необходимо и обязательно всем веб-дизайнерам и веб-разработчикам, а также всем (писателям, редакторам), кто производит Интернет контент. Подробнее (англ): и

IDL

  • IDL, или Interactive Data Language, это язык программирования, используемый в основном для анализа и визуализации данных. Он до сих пор широко используется в аэрокосмической промышленности и астрономии. Подробнее (англ):

INTERCAL

  • INTERCAL является пародийным компьютерным языком, разработанным в начале 1970-х годов. Его создали как шутку, чтобы показать как технически сложны языки и трудно читаемы. Это реальный язык, который можно скачать, и с помощью которого можно даже что-то сделать. Подразумевается, что вы должны быть хорошо с ним знакомы для этого – но, опять же, не слишком хорошо, ведь и это не понравится самому INTERCAL. Подробнее (англ):

Java

  • Java является языком высокого уровня и предназначен для использования на Java Virtual Machine. Имеет очень мало внешних зависимостей, и был предназначен для работы на любой физической машине. Много используется в сетевой архитектуре, а также во встраиваемых устройствах и других вычислительных приложениях. Подробнее (англ): .

Javascript

  • JavaScript (не имеет фактического отношения к Java) это скриптовый язык, изначально разработанный для использования в веб-браузерах. Поэтому он имеет встроенную возможность работы с Document Object Model (DOM), отображением находящегося в памяти контента веб-страниц. Является основным языком программирования для front-end веб-разработки. В основном управляется событиями, и, благодаря Node.JS, в последнее время получил признание как серверный язык. Подробнее (англ): и . И здесь:

Ksh

  • Korn Shell (ksh) представляет собой интерфейс командной строки, используемый на Unix. Он был ранней оболочкой (shell), совместимый со стандартной оболочкой Bourne, но со всеми классными интерактивными функциями оболочки C. Подробнее (англ):

Linux Programming

  • Программирование Linux включает в себя все: начиная от скриптов оболочки до разработки приложений и разработки ядер. Подробнее (англ):

Logo

  • Logo один из самых ранних языков по обучению программированию, и до сих пор, вероятно, самый известный. Он известен своей черепахой, которую дети заставляют передвигаться компьютерными командами. Весело обучает программированию. Подробнее (англ):

ML

  • ML первоначально разработан как язык мета-программирования: язык для создания других языков. Но со временем он стал языком общего назначения, широко использовался в образовании, математике, естественных науках и даже финансах. Подробнее (англ): .

MPI

  • Message Passing Interface (Интерфейс передачи сообщений) представляет собой стандартный протокол для отправки сообщений между процессами или программами. Был реализован в ряде языков программирования, включая C, C++, Java и Python. Благодаря MPI стали возможны параллельные вычисления. Подробнее (англ):

Сетевое программирование с интернет-сокетами

Objective-C

  • Еще одна версия C, созданная в 1980-е годы для того, чтобы обеспечить полностью объектно-ориентированную реализацию C. Сейчас основное применение этого языка приходится на Mac OSX и операционные системы iOS. До недавнего времени iOS приложения должны были быть написаны на Objective-C, но сейчас можно писать также на Swift. Подробнее (англ):

OCaml

  • OCaml является объектно-ориентированным функциональным компьютерным языком. По ML традиции, он много используется для написания других языков программирования и фреймворков. Подробнее (англ): .

Разработка операционной системы

  • Эверестом среди работ по программированию считается разработка операционной системы. Если вы хотите доказать себе, что можете написать все, что угодно, то нет ничего лучше, чем написать свое собственное ядро операционной системы и связанные с ней инструменты.Но будьте осторожны: это путешествие по силам только храбрым и истинным программистам! Подробнее (англ): .

Perl

  • Очень полезный инструмент практически любого программиста. В качестве интерпретируемого языка его не нужно компилировать, иногда упоминается как “швейцарский армейский нож” скриптовых языков. Подробнее (англ):

PROLOG

  • Пролог – язык логического программирования, разработан для обработки естественного языка. Подробнее (англ):

Pure Data

  • Pure Data является уникальным визуальным языком программирования. Был создан специально для того, чтобы позволить пользователям создавать видео, аудио и графические работы. Подробнее (англ): .

Python

  • Python является языком программирования высокого уровня. Интерпретируемый (некомпилируемый) язык, также известный как “скриптовый язык”. В основном используется в качестве инструмента для выполнения специализированных задач программирования, таких как задачи по автоматизации и анализу данных. Имеет сильный набор инструментов для математических и научных вычислений, часто используется исследователями. Подробнее (англ):

Ruby on Rails

  • Ruby on Rails – это фреймворк для веб-разработки для языка программирования Ruby. Он обеспечивает архитектуру MVC (Model View Controller), уровень абстракции базы данных, а также множество инструментов для ускорения процесса программирования веб-приложений. Очень популярен для быстрой разработки веб-приложений. Подробнее (англ):

SAS

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

Scala

  • Scala является относительно новым языком – более или менее новой и лучшей Java. Это отличный язык для Java-программистов, которые хотят быть более эффективными, или для людей, кто только начинают изучать программирование и хотят изучать мощный язык, который не будет ограничивать их в будущем. Подробнее (англ): .

Scheme

  • Scheme – старый язык, но до сих пор используется для обучения программированию и более сложных предметов в информатике. Основан главным образом на Lisp, и частично на ALGOL. Подробнее (англ): .

Scratch

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

Simula

  • Simula – исторически важный язык, так как это был первый язык, внедривший понятия, ставшие основой для объектно-ориентированного программирования. Подробнее (англ): .

SMIL

  • SMIL (Synchronized Multimedia Integration Language) инструмент для тех людей, которые хотят создавать и распространять презентации. Особенно полезен, если вы хотите создавать презентации, которые должны время от времени обновляться. Подробнее (англ):

SQL

  • SQL (Structured Query Language) – язык, используемый для связи с Relational Database Management Systems (RDBMSes). SQL позволяет программисту создавать структуры данных, вставлять и редактировать данные, а также их запрашивать. Подробнее (англ):

Stata

  • Stata это среда разработки и язык программирования для решения серьезных статистических проблем. И хотя он создан довольно давно, но все еще широко используется. Если вы связаны со статистической работой, Stata – отличный инструмент. Подробнее (англ):

Swift

  • Swift является новыйм языком программирования, разработанным компанией Apple, для iOS, OS X, watchOS, tvOS и Linux. Это язык будущего для разработчиков программ и приложений для устройств Apple. Подробнее (англ):

S-PLUS

  • S-PLUS является коммерческой версией мощного языка программирования S, разработанного для выполнения статистического анализа. Проект GNU имеет свою собственную версию S, называемую R. Все необходимые ресурсы о S с акцентом на S-PLUS:

UNIX Programming

  • Широта программирования на Unix велика. Она охватывает диапазон от административных скриптов к коду на основе текста до разработки X Window. Подробнее (англ):

XML

  • XML хорошо структурированный язык для разметки, предназначен, как для чтения человеком, так и машиной. Подробнее (англ):

Урок подготовил: Акулов Иван

Программисты и ученые, как правило, пристрастны, когда речь заходит о достоинствах и недостатках различных языков программирования. Сравнив несколько языков, автор попытался получить объективную информацию о Си, Си++, Java, Perl, Python, Rexx и Tcl.

Для сопоставления была использована одна программа, которая предъявляет одинаковый набор требований ко всем языкам. Такой прием сужает область сравнения, но делает ее однородной. Кроме того, для каждого языка было проанализировано несколько отдельных реализаций программы, подготовленных различными программистами. Групповой подход имеет два преимущества. Во-первых, сглаживаются различия между отдельными программистами, которые могут лишить достоверности любые сравнения, основанные на единственном «образце» для каждого языка. Во-вторых, появляется возможность сравнить изменчивость характеристик программ, составленных на разных языках.

В ходе сравнительного исследования сопоставлялись различные аспекты каждого языка, в том числе длина программы, усилия, затраченные на программирование, время выполнения, занимаемое пространство памяти и надежность. Языки сравнивались индивидуально и по группам. Языки сценариев, такие как Perl, Python, Rexx и Tcl чаще интерпретируются, чем компилируются (по крайней мере, на этапе разработки программ), и обычно не требуют определения переменных.

Более традиционные языки программирования - Си, Си++ и Java - чаще компилируются, чем интерпретируются и требуют описания типов переменных. Поскольку многие считают язык Java очень неэффективным, я иногда относил Си и СИ++ к одной группе, а Java к другой.

Диаграммы и статистические методы

В качестве основного инструмента оценки в статье используется блочная диаграмма, показанная на рис. 1. Каждая линия представляет одно подмножество данных, имя которого указано слева. Каждым малым кружком обозначено одно значение данных. Остальная часть диаграммы помогает визуально сравнить два или несколько подмножеств данных. В затененном блоке заключена средняя половина значений, между верхними границами первой четверти (25%) и третьей четверти (75%). «Усы» слева и справа от блока показывают нижние и верхние 10%, соответственно. Жирная точка внутри блока - верхняя граница второй четверти (50%). Символ «M» и разорванная линия вокруг него показывает среднее арифметическое, плюс/минус среднеквадратическую ошибку.

Рис. 1. Время работы программы с набором данных z1000
Исполнение трех программ было завершено безрезультатно по истечении примерно 21 минуты. Разброс отношений «плохих и хороших величин» составляет от 1,5 для Tcl до 27 для Си++. Обратите внимание на логарифмический масштаб оси. Пояснения к данному рисунку относятся также к Рис. 2-7. Более подробное описание дано в разделе «Диаграммы и статистические методы»

Для количественного описания разброса значений в группе было использовано отношение «плохих и хороших величин»: представьте, что данные разделены между верхней и нижней половинами, и отношение «плохих и хороших величин» представляет собой частное от деления среднего значения верхней половины на среднее значение нижней половины. На блочной диаграмме средним считается значение, полученное в результате деления величины у правого края блока на величину у левого края блока. В отличие от такой меры разброса величин, как среднеквадратичное отклонение, отношение «плохих и хороших величин» эффективно исключает резкие выбросы.

Самый важный вывод можно сделать непосредственно из диаграммы. Однако для перепроверки были выполнены статистические тесты. Для сравнения средних значений используется односторонний U-критерий Манна-Уитни (также называемый критерием суммы рангов Уилкоксона). Результат каждого теста - p, величина, характеризующая вероятность того, что наблюдаемая разница между двумя выборками лишь случайна, и что в действительности различия между двумя группами величин отсутствуют или имеют противоположный знак. Обычно само p-значение не приводится, а в тексте статьи указывается «... больше, чем...», если 0 0,10, то «существенных различий нет».

В ряде случаев указаны доверительные интервалы для различий в средних значениях или для различий в логарифмах средних значений - т. е., отношений средних величин. Выбраны открытые уровни доверительности, с бесконечной верхней границей. Доверительные интервалы вычислены методом раскрутки (bootstrap), подробно описанным во многих источниках (см., например, ).

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

Результаты

Для оценки программ использовались три различных входных файла: z1000, содержащий 1000 непустых случайных телефонных номеров; m1000, содержащий 1000 произвольных случайных телефонных номеров, некоторые из которых могли быть пустыми; и z0, не содержащий телефонных номеров и служащий исключительно для измерения времени загрузки словаря.

Время выполнения программы

Я начал анализ с измерения полного времени выполнения, а затем исследовал отдельно этапы инициализации и поиска.

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

  • Среднее время выполнения программ Tcl незначительно больше, чем Java и даже Си++.
  • Среднее время выполнения как для Python, так и для Perl меньше, чем Rexx и Tcl.
  • Средний показатель Си++ может ввести в заблуждение. Из-за довольно большого разброса между соседними большими и меньшими величинами, среднее значение нестабильно. Критерий Уилкоксона, который учитывает весь набор данных, подтверждает, что среднее время для Си++, как правило, меньше среднего времени для Java (p = 0,18).
  • Среднее время выполнения для Си меньше, чем для Java, Rexx и Tcl, и как правило, меньше чем для Perl и Python.
  • Время выполнения для Tcl и Perl - за исключением двух очень медленных программ - как правило, более стабильно, чем время выполнения программ на других языках.

Не следует придавать особенно большого значения диаграммам для Си и Rexx, построенным всего по нескольким точкам. Время выполнения программ на Rexx может быть снижено примерно в четыре раза, если перекомпилировать интерпретатор Regina для использования хеш-таблиц большего размера; требования к памяти при этом возрастают незначительно. Если объединить языки всего в три группы (одна - Си и СИ++ , вторая - Java, третья - языки сценариев), то программы на Си и Си++ работают быстрее, чем Java (p = 0,074), и как правило, быстрее сценариев (p = 0,15).

Между средним временем выполнения программ на Java и сценариев нет существенной разницы. С вероятностью 80% сценарий будет выполняться в 1,29 раза дольше - а программа на Java по меньшей мере в 1,22 раза дольше - чем программа на Си или Си++. Отношение «плохих и хороших величин» значительно меньше для сценариев (4,1), чем для Java (18) и даже для Си и Си++ (35).

Только этап инициализации, набор данных z0. Затем я измерил время, необходимое для считывания, предварительной обработки и сохранения словаря. Соответствующие времена приведены на рис. 2. Результаты явно свидетельствуют, что Си и Си++ выполняют эту фазу быстрее, чем другие протестированные языки. И вновь, самыми быстрыми языками сценариев оказались Perl и Python. Как выяснилось (с вероятностью 80%) при сравнении укрупненных групп, программа на Java будет выполняться по крайней мере в 1,3 раза дольше, чем программы на Си и Си++, а для выполнения сценария потребуется по крайней мере в 5,5 раз больше времени. Сценарий будет выполняться по крайней мере в 3,2 раза дольше программы на Java.

Рис. 2. Время, затраченное программой только на загрузку и предварительную обработку словаря (набор данных z0). Обратите внимание на логарифмический масштаб оси. Соотношение «плохих и хороших величин» в пределах от 1,3 для Tcl до 7,5 для Python

Только этап поиска. И наконец, я вычел время время этапа загрузки (набор данных z0) из полного времени выполнения (набор данных z1000), чтобы получить время только поискового этапа программы. На рис. 3 показаны соответствующие времена, из которых можно сделать следующие выводы.

  • Очень быстрые программы составлены на всех языках, за исключением Rexx и Tcl, а очень медленные программы встречаются на всех языках.
  • Среднее время выполнения программ на Tcl больше, чем время программ на языках Python, Perl и Си, но меньше, чем на Rexx.
  • Среднее время выполнения программ на Python меньше, чем времена для Rexx и Tcl, и как правило, меньше, чем время выполнения Java (p = 0,13).
  • Среднее время выполнения программ на Perl меньше средних показателей для Rexx, Tcl и Java.
  • Среднее время Си++ существенно отличается от результатов любого другого языка.

Рис. 3. Время, затраченное программой только на поиск, вычисленное как разность между временем работы с набором данных z1000 и набором данных z0. Обратите внимание на логарифмический масштаб оси. Соотношение «плохих и хороших величин» в пределах от 2,9 для Perl до 50 для Си++

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

Требования к памяти

На рис. 4 показан общий размер процесса в конце обработки входного файла z1000. Из него можно сделать несколько выводов.

  • Очевидно, что наиболее эффективно память используется в программах групп Си и Си++, а наименее эффективно - в программах группы Java.
  • За исключением Tcl, лишь немногие сценарии потребляют больше памяти, чем худшая половина программ на Си и Си++.
  • Для сценариев Tcl требуется больше памяти, чем для других сценариев.
  • Относительный разброс требований к памяти программ на Python и Perl, как правило меньше, чем программ на Си и в особенности Си++.
  • Некоторые сценарии занимают большие области памяти.
  • При сравнении укрупненных групп можно утверждать с вероятностью 80%, что в среднем программы на Java занимают по крайней мере на 32 Мбайт больше памяти (297%), чем программы на Си и Си++, и по крайней мере на 20 Мбайт больше (98%), чем сценарии. Сценарии занимают по крайней мере на 9 Мбайт больше памяти (85%), чем программы на Си и Си++.

Рис. 4. Объем памяти, необходимый программе, в том числе для размещения интерпретатора или исполняющей системы, собственно программы и всех статических и динамических структур данных. Соотношение «плохих и хороших величин» в пределах от 1,2 для Python до 4,9 для Си++

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

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

Длина программ и число комментариев

На рис. 5 показано число строк исходного файла каждой программы, в которых содержится любая семантическая информация программы: операторы, определения или по крайней мере разделители, такие как закрывающая фигурная скобка. Несценарные файлы обычно вдвое или втрое длиннее сценариев. Даже самые длинные сценарии короче средних несценарных исходных текстов. Вместе с тем, плотность комментариев в сценариях значительно выше (p = 0,020): в несценарных программах в среднем содержится на 22% больше строк с комментариями, чем строк с операторами; для сценариев этот показатель составляет 34%.

Рис. 5. Длина программы: число строк исходного текста без комментариев. Соотношение «плохих и хороших величин» в пределах от 1,3 для Си до 2,1 для Java и 3,7 для Rexx

Надежность программ

При работе с входным файлом z1000 три программы - одна на языке Си, одна на Си++ и одна на Perl - не выдали корректных результатов, поскольку не могли загрузить большой словарь или из-за истечения времени, отведенного на завершение этапа загрузки. Две программы на Java имели почти нулевую надежность и отказали по другим причинам; а одна программа на Rexx выдала много результатов, используя некорректный, только форматирующий сценарий - ее надежность составила 45%.

Отбросив эти явно ошибочные программы - тем самым, были исключены 13% программ на Си и Си++, 8% программ на Java и 5% сценариев - и сравнив остальные по языковым группам, мы обнаружили, что программы на Си и Си++ менее надежны, чем Java и сценарии. Однако эти различия были вызваны лишь несколькими программами с дефектами и потому не могут служить основанием для общих выводов.

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

Затем я сравнивал поведение при работе с входным файлом m1000, в котором могут содержаться телефонные номера без цифр, лишь с тире и наклонными чертами. Такие телефонные номера должны преобразовываться в пустые записи, но программисты склонны забывать о таких требованиях, читая задание. Поэтому на файле m1000 проверяется добротность программы. Большинство программ успешно справились с этой ситуацией, но половина программ Java и четыре сценария - один Tcl и три на языке Python - зависли на первом пустом телефонном номере, после того, как было выдано 10% результатов. Обычно сбой происходил из-за неправильного индекса строки или массива. Среди прочих программ, 15 - одна составленная на Си, пять Си++, четыре Java, две Perl, две Python и одна Rexx - не смогли обработать пустые телефонные номера, но в остальном работали корректно; их надежность составила 98,4%.

В целом, надежность сценариев, по-видимому, не ниже надежности несценарных программ.

Время работы и продуктивность труда

На рис. 6 показано общее время, затраченное на проектирование, составление и тестирование программы, сообщенное авторами сценариев и измеренное для программистов несценарных программ.

Рис. 6. Полное время, затраченное на реализацию программы
Программисты сценарной группы измеряли и сообщали время работы: время языков «несценарной» группы измерялось экспериментатором. Соотношение «плохих и хороших величин» в пределах от 1,5 для Си до 3,2 для Perl. Три величины для Java - рабочее время 40, 49 и 63 час - выходят за границы диаграммы и потому не показаны.

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

К счастью, можно одновременно проверить два фактора, а именно, корректность сообщений о времени, затраченном на составление программ, и равенство квалификации программистов в сценарной и несценарной группах. Оба этих фактора, если они действительно играют роль, должны способствовать снижению времени работы сценарной группы: я предполагаю, что субъективные сообщения будут содержать уменьшенное, а не преувеличенное время работы программы, а в случае появления различий мы ожидали обнаружить более квалифицированных программистов в сценарной группе, поскольку в 1997 и 1998 годах, когда проводилось исследование, программисты Java были менее опытными, чем специалисты по другим языкам. В основу данной проверки положено старое практическое правило, согласно которому число строк исходного текста, выдаваемых программистом в час, не зависит от языка программирования. В нескольких широко применяемых методах оценки трудозатрат - в том числе Cocomo Барри Боэма и таблицах языков программирования для оценки функциональных точек Кейперса Джонса - явно предполагается, что почасовое число строк исходного текста не зависит от языка программирования. На рис. 7 показаны оценки рабочего времени, составленные на основе этого правила. Судя по достоверно известному диапазону продуктивности при работе с Java, все величины, за исключением трех лучших результатов для Tcl и лучших времен для Perl, выглядят правдоподобно.

Рис. 7. Число строк исходного текста, написанных за полный рабочий час. Соотношение «плохих и хороших величин» в пределах от 1,4 для Си до 3,1 для Tcl

Ни одно из медианных различий не имеет явной статистической значимости, хотя отличия Java от Си, Perl, Python и Tcl (0,07

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

Структура программы

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

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

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

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

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

Важнейшие выводы

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

  • Проектирование и составление программ на языках Perl, Python, Rexx и Tcl занимает не более половины времени, необходимого для программирования на Си, Си++ и Java - а длина исходного текста вдвое меньше.
  • Явных различий в надежности между программами различных языковых групп не обнаружено.
  • Типичный сценарий занимает примерно вдвое больше памяти, чем программа на Си или Си++. Программы на Java занимают втрое и вчетверо больше памяти, чем программы на Си и Си++.
  • Программы на Си и Си++ выполняют инициализацию программы преобразования телефонных номеров, которая заключается в считывании словарного файла размером в 1 Мбайт и построении 70-килобайтной внутренней структуры данных, в три или четыре раза быстрее, чем программы на Java и примерно в пять или десять раз быстрее, чем сценарии.
  • На основном этапе программы преобразования телефонных номеров ведется поиск во внутренней структуре данных, и программы на Си и Си++ работали всего примерно вдвое быстрее Java. Сценарии, как правило, работают быстрее программ на Java.
  • Среди сценарных языков Perl и Python выполняли оба этапа быстрее, чем Tcl.
  • Разброс любых исследованных параметров программ, возникший вследствие различий между программистами, работающими на одном языке - отраженный отношениями «плохих и хороших величин» - в среднем такой же или больше разброс характеристик, вызванного различиями языков.

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

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

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

Лутц Прехельт ([email protected]) - руководитель службы контроля качества компании abaXX Technology (Штуттгарт, Германия).

Литература

1. E. Bradley and R. Tibishirani, «An Introduction to the Bootstrap,» Monographs on Statistics and Applied Probability 57, Chapman and Hall, New York, 1993
2. L. Prechelt, An Empirical Comparison of C, C++, Java, Perl, Python, Rexx and Tcl for Search/String-Processing Program, Tech Report 2000-5, Fakultat fur Informattik , Universitat Karlsruhe, Germany, Mar. 2000.
3. B.W. Boehm, Software Engineering Economics, Prentice Hall, Englewood Cliffs, N.J., 1981
4. C. Jones, Software Productivity Research , Programming Languages Table, Version 7, 1996.

An Empirical Comparison of Seven Programming Languages, Lutz Prechelt, IEEE Computer, October 2000, pp. 23-29. Copyright IEEE CS, Reprinted with permission. All rights reserved.

Достоверность сравнения

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

Программы получены из двух различных источников. Программы на Java, Си и Си++ были составлены в 1997 и 1998 годах во время контролируемого эксперимента, в котором участвовали студенты старших курсов факультета вычислительной техники (L. Prechelt and B. Unger, A Controlled Experiment on the Effects of PSP Trimming: Detailed Description and Evaluation, Tech Report 1/1999, Fakultat fur Informattik, Universitat Karlsruhe, Germany, Mar. 1999). Программы на Perl, Python, Rexx и Tcl были составлены в различных условиях добровольцами, которые откликнулись на приглашение, опубликованное в нескольких конференциях. Поэтому авторы имели разные уровни подготовки и опыт.

Квалификация программистов

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

В несценарной группе программисты на Java имели меньший опыт работы со своим языком, чем программисты на Си и Си++, поскольку в 1997 и 1998 годах Java был еще новым языком. В сценарной группе программисты на Perl имели более высокую квалификацию, чем другие участники эксперимента; по-видимому, язык Perl привлекает особенно талантливых программистов - по крайней мере, таково мое личное впечатление.

Точность измерения времени работы

Мы точно знаем время программирования в условиях контролируемого эксперимента с несценарными языками, однако ничто не мешало авторам сценариев занизить время, потраченное ими на составление программ. Хуже того, некоторые из авторов, очевидно, приступили к работе спустя много дней после того, как ознакомились с требованиями, предъявляемыми к программам. Один программист сообщил, что начал составлять программу через две недели после прочтения объявления «... в это время подсознательно я мог уже работать над решением.»

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

Различие задач и условий работы

Главным требованием к несценарной группе была правильность программы; приемный контроль подразумевал высокую надежность и по меньшей мере некоторую степень эффективности.

К сценарной группе наряду с главным критерием корректности предъявлялись восемь дополнительных качественных требований. Вместо приемного контроля несценарной группы авторам сценариев были предоставлены входные и выходные данные z1000 для собственного тестирования. В обоих случаях различия могут дать преимущество сценарной группе.

Выводы

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



Рекомендуем почитать

Наверх