LINUX.ORG.RU

Результаты теста скорости языков C++, Java, PHP, Ocaml, Perl, Python, Ruby...


0

1

Периодически появляются темы, в которых сторонники разных языков утверждают, что их языки быстрее/лучше. Захотелось это проверить. В паре тредов в качестве примера упоминались конкретные тесты. Их я и реализовал.

Общая идея теста

Тест проводится на каком-либо примере, позволяющем проверить производительность в той или иной области.

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

Замер выполнялся десяток раз и для каждого языка выбиралось наилучшее время. Чем время меньше - тем лучше.

.

Числодробилки: длинная арифметика

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

$ make
Language            real     user   system
c++                0.179    0.175    0.004
php                0.307    0.292    0.015
perl (gmp)         2.045    2.041    0.004
ocaml              2.109    1.840    0.269
ocaml (recurse)    5.015    4.743    0.271
java               8.422    8.571    0.176
ruby               8.974    8.963    0.007
python            17.609   17.602    0.004
bc                49.716   49.607    0.099
perl             123.891  123.840    0.014

Замечание: Хотя нерекурсивная версия для ocaml-а написана «не совсем» в функциональном стиле, она работает в два с половиной раза быстрее, чем рекурсивная. Почему?

.

Объекты: создание и удаление

Проверялась скорость создания и удаления объектов. Это важно в ООП-задачах при передаче объектов, как параметров. Реализация теста подсказана форумом (последнее время недоступен)

$ make
Language           real      user   system
c++ (stack)       0.614     0.613    0.000    <1 MB
c++ (heap)       11.489    11.484    0.002    <1 MB
c++ (pool)        1.929     1.909    0.001    <1 MB
java              2.703     2.511    0.185   170 MB
ocaml           100.633   100.301    0.152     1 MB
python          389.973   389.882    0.009     3 MB
php             535.231   535.064    0.011     7 MB
ruby            756.593   680.874   75.535     3 MB
perl           4549.582  4548.612    0.027     3 MB

Замечание1: Можно было бы оставить только один тест для С++, но любители Java могли бы возмутиться, что C++ выделяет объект в стеке, а Java - в heap-е, и это нечестно по отношению к Java (и к тому же не возможно при большой глубине рекурсии и больших объектах). Потому добавлен С++ heap-тест (т.к. в Java для таких объектов автоматическое управление памятью, пришлось использовать его аналог - auto_ptr в С++).

Но тут сторонники С++ могли возмутиться, что С++-ный аллокатор оптимизирован для выделения больших объектов, и работает неэффективно для частого мелкого выделения. Поэтому был добавлен тест с более подходящим аллокатором (boost::object_pool).

Замечание2: Поскольку тест работал с памятью, логично было замерять, сколько же памяти в нем используется. И из всех тестируемых языков java - единственный, потребовавший 170МБ. Остальные уложились в десяток.

.

Выводы (если очень хочется): ООП-задачи кроме как на С++ и Java эффективно решать больше не на чем (но если важна память - остается только С++). А задачи, требующие высокой точности вычислений, быстрее всего решаются на С++. Его догоняют языки, умеющие работать с сишной libgmp, хотя и они примерно на порядок отстают от С++ по скорости.

Воспроизвести результаты можно скачав архив с тестами, и выполнив make в bash-е (для замера использовался bash-евый time, чтобы заменить его на что-то свое - надо править Makefile).

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

>>> Исходники теста


Ответ на: комментарий от Macil

> Тьфу ты. Ты же хотел первый параметр. ... <code> ...

Благодарю. Теперь работает. Итого получилось:

$ cat fact_recurse.hs
import System

factorial n =
  if n == 0 then
    1
  else
    n * factorial (n-1)

main = do
  v <- getArgs
  putStrLn $ show $ factorial (read $ head v)

$ ghc -O -o fact_recurse_hs fact_recurse.hs
$ time ./fact_recurse_hs 30000 > /dev/null

real    0m0.703s
user    0m0.455s
sys     0m0.249s
и
$ cat fact.hs
import System

factorial n = product [1..n]

main = do
  v <- getArgs
  putStrLn $ show $ factorial (read $ head v)

$ ghc -O -o fact_recurse_hs fact_recurse.hs
$ time ./fact_hs 30000 > /dev/null

real    0m0.567s
user    0m0.430s
sys     0m0.136s

Нерекурсивная реализация опять оказалась быстрее рекурсивной. Может быть, если ее, как в ocaml-е, написать через присваивания, будет еще быстрее? ;)

PS: Вообще, я про хаскель спрашивал из-за того, что он тоже использует libgmp, к тому же компилируется через промежуточный сишный код. Я ожидал, что скорость будет близкой к скорости С++. Получилось, правда, в 3-4 раза медленнее, но тоже один из лучших результатов. Быстрее только PHP. :)

sergem
() автор топика
Ответ на: комментарий от sergem

>Каждый из элементов выборки дает мне "время работы + помехи". Где "время работы" - это постоянная величина, зависящая от языка (и компилятора), которая определяется только сгенерированными машинными кодами, не меняющимися от запуска к запуску.

Это не правда. Не постоянная. И именно, что зависящая и вот почему:

>зависящая от состояния кеша процессора, положения головки на диске, наличия пакетов в сетевой карте, библиотек в памяти...

А то, что ты считаешь "временем работы" - некое сферическое время в ваккуме. Оно неимеет никакого физического смысла.

Waterlaz ★★★★★
()
Ответ на: комментарий от sergem

Вот тебе рекурсивный факториал(вообще product делает почти тоже самое):

{-# LANGUAGE BangPatterns#-}
import System
factorial !n = let f res 0 = res
                   f res n = f (res*n) (n-1)
                in f 1 n

main = do 
    v <- getArgs 
    putStrLn $ show $ factorial (read $ head v)
Время почти совпадает...

Waterlaz ★★★★★
()
Ответ на: комментарий от sergem

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

Но если на объективность тебе положить, то вот:
факториал:
http://paste.lisp.org/display/88867
фибоначчи:
http://paste.lisp.org/display/88868

компилировать:
sbcl --script имя_файла
запускать, соответственно:
./fib_lisp(или fact_lisp) n
В случае с факториалом I/O очень сильно влияет на время, и соответственно, объективность. И в обоих случаях, влияет время загрузки рантайма, которое у SBCL не такое уж и маленькое.

Love5an
()
Ответ на: комментарий от Waterlaz

ЗЫ. можешь еще такой факториал попробовать: factorial2 n = foldl (*) 1 [1..n]

ЗЗЫ правда у меня есть подозрение, что где-то в Prelude так и написано:

product = foldl (*) 1

Waterlaz ★★★★★
()
Ответ на: комментарий от Waterlaz

Блин... не туда восклицательный знак влепил:

 
{-# LANGUAGE BangPatterns#-} 
import System 

factorial n = let f res 0 = res 
                  f !res !n = f (res*n) (n-1) 
               in f 1 n

main = do 
    v <- getArgs 
    putStrLn $ show $ factorial (read $ head v) 

Waterlaz ★★★★★
()
Ответ на: комментарий от sergem

Фибоначчи:

{-# LANGUAGE BangPatterns#-}
import System

fib 1 = 1
fib 2 = 1
fib !n = fib(n-1) + fib(n-2)

main = do 
    v <- getArgs 
    putStrLn $ show $ fib (read $ head v)

Waterlaz ★★★★★
()

Обновленная версия таблицы для первого теста.

Language            real     user   system
c++                0.179    0.175    0.004
php                0.307    0.292    0.015
haskell            0.567    0.430    0.136
haskell (recurse)  0.703    0.455    0.249
ocaml              1.911    1.692    0.219
perl (gmp)         2.045    2.041    0.004
ruby 1.9          ~3.033   ~2.807   ~0.011
ocaml (recurse)    3.623    3.346    0.276
java               8.422    8.571    0.176
ruby               8.974    8.963    0.007
python            17.609   17.602    0.004
bc                49.716   49.607    0.099
tcl               76.448   76.120    0.274
perl             123.891  123.840    0.014

Отличия от таблицы в шапке:

  • ocamlc заменен на ocamlopt (спасибо dave за подсказку)
  • время для ruby 1.9 приблизительно отмасштабировано из сообщения elipse
  • добавлен тест для haskell-я (спасибо mv за ссылку из гугла и Macil за помощь с кодом)
  • добавлен тест для tcl (спасибо таинственному anonymous за код)
sergem
() автор топика
Ответ на: комментарий от Waterlaz

> Блин... не туда восклицательный знак влепил: ... <code> ...

Проверил оба варианта с разными восклицательными знаками. Обе скорости примерно одинаковые:

$ ghc -O -o fact_recurse2_hs fact_recurse2.hs
$ time ./fact_recurse2_hs 30000 > /dev/null

real    0m0.720s
user    0m0.477s
sys     0m0.240s
И обе - чуть чуть (на несколько процентов) медленнее, чем реализация, которую я написал тут.

Нерекурсивный вариант через foldl по скорости один к одному сходится с нерекурсивным через product из моего сообщения.

Думаю, в общую таблицу эти варианты можно не добавлять, если они хуже, чем то, что там уже есть. :)

sergem
() автор топика
Ответ на: комментарий от Love5an

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

Ну не так уж и много уходит этого времени:

$ time ./fact_cl 1 > /dev/null

real    0m0.051s
user    0m0.016s
sys     0m0.036s
Может у тебя в «оффтопике» оно больше, я не знаю. По сравнению с другими, конечно, это немало, но учитывая, что «скомпилированный» бинарник такой программы занимает 43241520 байта (!) - это довольно неплохо. Кстати, поменьше его не сделать? ;)

> факториал: http://paste.lisp.org/display/88867

Еще одна вещь. Можно ли как-то вынести параметры компиляции и оптимизации из исходника, и оставить только на этапе компиляции, не зависимые от исходного кода? Все-таки в С++ #pragma-директивы не использовались. А то как-то это неэквивалентно выглядит... Функция на хаскеле:

factorial n =
  if n == 0 then
    1
  else
    n * factorial (n-1)
Функция на лиспе:
(compile 'fact
         '(lambda (n &aux (result 1))
            (declare (type unsigned-byte n result)
                     (optimize (speed 3)
                               (space 0)
                               (safety 0)
                               (debug 0)))
            (loop do
                  (setf result (* result n))
                  (decf n)
                  while (> n 1))
            result))
Не хватает только ассемблерных вставок... :) Или на лиспе иначе нельзя?

> фибоначчи: http://paste.lisp.org/display/88868

Увы, это не то. Тест «фибоначчи» проверял не скорость вызова рекурсивных функций, а скорость создания объектов (экземпляров классов).

sergem
() автор топика
Ответ на: комментарий от sergem

>Кстати, поменьше его не сделать?
Нет. Хотя у меня он занимает ~25 мб, а не 43, странно.
>Можно ли как-то вынести параметры компиляции и оптимизации из исходника, и оставить только на этапе компиляции, не зависимые от исходного кода?

Не понял. В каком плане? Чем это мешает?
Декларации, вообще говоря, с кодом составляют единое целое, это не сишки всякие там.
>Все-таки в С++ #pragma-директивы не использовались

А при чем тут #pragma?
>Функция на лиспе:

Тут суть, вообще, такая - компилируется функция, а ядро с этой функцией сохраняется в исполняемый файл. Можно по-тупому в файле оставить один исходник, конечно, и в --eval аргумент к компилирующему ядру наворотить compile-file, load и save-lisp-and-die, но какой смысл, если и так можно?
>Функция на хаскеле

А на Хаскеле нельзя динамически генерировать код(т.е. "compile" :) и сохранять ядро рантайма :)

>Увы, это не то. Тест "фибоначчи" проверял не скорость вызова рекурсивных функций, а скорость создания объектов (экземпляров классов).

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

Love5an
()
Ответ на: комментарий от sergem

А как иначе вывести его на экран? Не выводить его я не могу. По двум причинам.

Ты задал вопрос о разнице в 15 раз, я тебе ответил почему.

sdio ★★★★★
()
Ответ на: комментарий от Love5an

clos-классы: http://paste.lisp.org/display/88874#1 структуры: http://paste.lisp.org/display/88874#2

компилировать и запускать так же. файл с clos-классами создает бинарник fib_clos, а со структурами - fib_lisp

под наследованием структур понимается то, что в CL макросу defstruct можно поставить опцию :include. Вот как это работает:

(defstruct vector2
  (x 0.0 :type single-float)
  (y 0.0 :type single-float))

(defstruct (vector3 (:include vector2))
  (z 0.0 :type single-float))
В примере структура vector3, как понятно, получает и слоты vector2, x и y. И к этим слотам этого типа структур можно обращаться как через ацессоры с префиксом vector3, так и через оные с префиксом vector2. Например
(let ((v (make-vector3 :x 5.0)))
  (= (vector2-x v) (vector3-x v)))
- естественно будет T(истина, т.е.). Т.е. vector3 как бы является и vector2.

Love5an
()
Ответ на: комментарий от sergem

> Медленнее в 15 раз? Где ошибка?

Ошибка в бредовом самом подходе :))
вот вам теория:
http://mathworld.wolfram.com/Factorial.html
т.е. при расчетах в лоб 30000! получаем астрономические числа
и переполнение "разрядной" сетки, и скорее всего для всех интерпретируемых языков.

И мало того , на практике нормальные люди избегают попадания в подобные ситуации с расчетами.:))

Вот примеры вычислений малых (именно малых значений !!) факториалов на разных языках:
http://www.codecodex.com/wiki/Calculate_the_factorial_of_a_number

вот из lib tcl описание работы нормального факториала:
http://tcllib.sourceforge.net/doc/combinatorics.html

и пример его использования:

#!/usr/bin/tclsh
package require math::bignum

# Factorial example
proc fact n {
# fromstr is not needed for 0 and 1
set z 1
for {set i 2} {$i <= $n} {incr i} {
set z [::math::bignum::mul $z [::math::bignum::fromstr $i]]
}
return $z
}

puts [::math::bignum::tostr [fact [lindex $argv 0]]]

------------------------------------
Для 30000 должен работать не просто долго, а ОЧЕНЬ долго !!
Так как уже ведутся расчеты с float и да и вопросы точности вычислений вылезут тут на первый план ...





elipse ★★★
()
Ответ на: комментарий от Love5an

>>Кстати, поменьше его не сделать?

>Нет. Хотя у меня он занимает ~25 мб, а не 43, странно.

Может потому, что у меня x86_64? Или потому, что он кроме как от системных либ при этом больше ни от чего не зависит, никакого рантайма не требует... Ну нет, так нет. Для теста не страшно. Жаль только, что он и памяти при этом столько же сжирает.

> Не понял. В каком плане? Чем это мешает? Декларации, вообще говоря, с кодом составляют единое целое, это не сишки всякие там.

В том плане, что параметры компиляции обычно не пишут в исходниках. Для этого есть make-и, ant-ы, cmake-и и т.д. И в остальных программах я их тоже не писал.

> при чем тут #pragma?

Это про то же. :) В лисповой функции были явно указаны параметры оптимизации (speed 3, safety 0 ...). Это чем-то похоже на #pragma в сях. Так обычно не пишут. Параметры оптимизации выносятся внаружу в параметры компилятора. Или на лиспе так принято писать все функции? Если принято, то что ж, пусть будет. :)

> Можно по-тупому в файле оставить один исходник, конечно, и в --eval аргумент к компилирующему ядру наворотить compile-file, load и save-lisp-and-die, но какой смысл, если и так можно?

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

(... экспериментирую с кодами на лиспе ... пока для факториала получилось 0.893 0.827 0.067)

sergem
() автор топика
Ответ на: комментарий от sdio

>> А как иначе вывести его на экран? Не выводить его я не могу. По двум причинам.

> Ты задал вопрос о разнице в 15 раз, я тебе ответил почему.

Не, там дело не в выводе было. Я потом проверил. Если убрать вывод, то транслятор действительно выкидывает почти все вычисление, и время становится 4 секунды. А если сохранить результат вычисления в переменную и затем вывести ее значение дважды - остаются те же 76 секунд.

sergem
() автор топика
Ответ на: комментарий от elipse

> т.е. при расчетах в лоб 30000! получаем астрономические числа

Спасибо, кэп, я знаю, именно на это и был расчет. Я же все таки тестировал длинную арифметику. :)

> и переполнение «разрядной» сетки, и скорее всего для всех интерпретируемых языков.

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

> вот из lib tcl описание работы нормального факториала: ...

Не работает. :)

$ ./fact2.tcl 30
can't find package math::bignum
    while executing
"package require math::bignum"
    (file "./fact2.tcl" line 2)

Зато этот - работает.

> Для 30000 должен работать не просто долго, а ОЧЕНЬ долго !!

76 секунд для tcl-я, или 0.179 секунд для С++. Не ОЧЕНЬ-то и долго. :)

sergem
() автор топика
Ответ на: комментарий от sergem

> Не работает. :)

так не установлена tcl lib

> Зато этот - работает.

и не значит еще что правильно работает
так как:
#!/usr/bin/tclsh
package require math

puts [::math::factorial [lindex $argv 0]];

уже выдает:
time tclsh test_fac3.tcl 100
9.33262154439e+157

real 0m0.107s
user 0m0.044s
sys 0m0.052s

а "махать" неупакованными числами подобного размера tcl просто не умеет.

Короче, филькина грамота и тесты и расчеты.



elipse ★★★
()
Ответ на: комментарий от anonumoustoo

>Скорость языка = скорость выполнения программы + скорость ее написания.

Скорость языка = N*скорость выполнения программы + K*скорость ее написания.

Где N и K зависят от задачи :)

KRoN73 ★★★★★
()
Ответ на: комментарий от sergem

>Это не самый простой вариант. Потому что надо хранить все результаты.

Ты их всёравно хранишь чтобы найти наилучший.

>А так - какими должны были быть эти обстоятельства?

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

>Получается, что проще и правильнее считать минимальное время. :)

Не, никак не получается:) Среднее, медианное и прочие весёлые слова не просто так придумали.

golodranez ★★★★
()
Ответ на: комментарий от golodranez

Для отбора наиболее достоверных результатов можно использовать "ящики с усами". Тестов конечно нужно побольше чем 3:) Желательно 20-30+++

golodranez ★★★★
()

Language real user system
c++ 0.121 0.116 0.000
hs 0.180 0.172 0.000
ocaml 0.978 0.972 0.000
ocaml (recurse) 1.336 1.328 0.008
python 11.494 11.309 0.004


fact :: Integer -> Integer
fact n = foldl1' (*) [1..n]

main = print . fact . read . head =<< getArgs

anonymous
()

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

naryl ★★★★★
()

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

http://wiki.tcl.tk/9585

naryl ★★★★★
()
Ответ на: комментарий от elipse

>> Не работает. :)

> так не установлена tcl lib

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

> и не значит еще что правильно работает так как:

Значит. Я-то проверил. :)

> а "махать" неупакованными числами подобного размера tcl просто не умеет.

Ну, он "машет" хуже, чем С++, но лучше, чем Perl. Так что все не так уж и плохо.

> Короче, филькина грамота и тесты и расчеты.

Тесты предназначены для конкретной задачи, и успешно с ней справляются.

Есть какие-то другие - предлагайте... "Все плохо" - это любой дурак сказать может. А вот предложить что-то получше, да еще и обосновать, чем оно лучше - тут думать придется, а это ведь не так просто, правда? ;)

sergem
() автор топика
Ответ на: комментарий от KRoN73

> Скорость языка = N*скорость выполнения программы + K*скорость ее написания.

С N - согласен. Программы часто запускаются больше одного раза. А причем тут K? Пишется программа обычно только раз... Или K==1? ;)

sergem
() автор топика
Ответ на: комментарий от golodranez

>>Это не самый простой вариант. Потому что надо хранить все результаты.

>Ты их всёравно хранишь чтобы найти наилучший.

Не храню. Мне достаточно хранить только лучший и обновлять его по мере появления более хорошего результата. Хранить все нет необходимости.

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

Вообще, я обращаю внимание на загрузку CPU, и многожрущие процессы я бы заметил. Но даже в таком варианте, если поступить по-моему, я получу один правильный замер и кучу неправильных. А если поступить по-твоему, то все замеры будут неправильными. И что же лучше? ;)

>>Получается, что проще и правильнее считать минимальное время. :)

>Не, никак не получается:) Среднее, медианное и прочие весёлые слова не просто так придумали.

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

Почему выбранная мной методика ей отвечает - я уже обосновал. А можно услышать обоснование того, чем она плоха, что есть лучше ее и ПОЧЕМУ? ;)

sergem
() автор топика
Ответ на: комментарий от sergem

Есть некий, скажем, сервер. И на одной машине он не при каких условиях не вытянет требуемые нагрузки. И если его писать, скажем, на ассемблере, то поребуется 6 лет разработки и 2 физических сервера, а если на ерланге -- 3 месяца и 10 серверов соотв. K ну никак не равен 1. Впрочем, N с количеством запусков тоже никак не связан.

kemm
()
Ответ на: комментарий от sergem

>А причем тут K?

Ну, время написания же со временем выполнения за жизненный цикл не нацело соотносятся :)

...

На самом деле, скорость выполнения программы и скорость её написания - наверняка имеют разную размерность. Вот и нужно нормировать :)

...

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

KRoN73 ★★★★★
()
Ответ на: комментарий от naryl

> К сожалению в такой реализации это тест не "скорости языка", а скорости запуска интерпретатора и компиляции в байткод.

Да. Время запуска интерпретатора действительно учитывается. И это правильно. Когда я кликаю на иконку на десктопе, и запускается программа, время ее запуска тоже включает запуск интерпретатора.

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

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

> Правильнее было бы запускать все тесты для одного языка из одного интерпретатора

Неправильно. Тогда получится, что каждый язык меряется разным способом. А чем замерять С++? Нельзя сравнивать длины, отмеренные разной линейкой. Нельзя сравнивать скорости, измерявшиеся разным таймером.

> и отбрасывать первый, лучший и худший результаты.

Почему?

sergem
() автор топика
Ответ на: комментарий от sergem

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

В натив - редко, если на целевой машине лиспа не будет. Обычно в faslах (impelementation dependent) - это байткод. Но самым кошерным являются исходники - в лиспе даже своя система сборки есть для этого дела.

anonymous
()
Ответ на: комментарий от anonymous

>Обычно в faslах (impelementation dependent) - это байткод.
У кого байткод, а у кого самые настоящие машшинные коды(как у sbcl)

Love5an
()
Ответ на: комментарий от sergem

> Когда я кликаю на иконку на десктопе, и запускается программа, время ее запуска тоже включает запуск интерпретатора.

У тебя на десктопе есть иконка, которая при запуске создаёт сотни тысяч обьектов или считает миллионноразрядные числа? :D

Это разные классы задач. Скрипт и числодробилка. Некоторые реализации при запуске всех тестов в одном процессе будут работать на порядки быстрее. Например эта:

facs = scanl (*) 1 [1..]

fac n = facs !! n

naryl ★★★★★
()
Ответ на: комментарий от sergem

>А можно услышать обоснование того, чем она плоха, что есть лучше ее и ПОЧЕМУ? ;)

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

>Но даже в таком варианте, если поступить по-моему, я получу один правильный замер и кучу неправильных.

Ты получишь один "правильный" замер для с++ и кучу "неправильных" для всех других языков. И как при твоём методе ты собираешься сравнивать полученные результаты? Если же выкинуть явно "вылетающие" из выборки результаты, то всё будет замечательно:)

golodranez ★★★★
()
Ответ на: комментарий от sergem

> (видимо, надо выложить отдельную табличку замера скорости запуска, а то недумащий народ не может осознать порядки величин)

видимо да, стоит чуть переделать Makefile чтобы там считалось и репортилось также время тривиальных задач, типа факториал 1

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от naryl

> У тебя на десктопе есть иконка, которая при запуске создаёт сотни тысяч обьектов или <...> ?

У меня есть иконка OpenOffice.org

Скажите, при его (достаточно тормозном кстати) запуске, он занимается чем-то, кроме создания объектов?

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от sergem

>Все задачи писались максимально эквивалентным кодом (например, для вычисления факториала использован примерно одинаковый цикл во всех языках с точностью до синтаксиса).

Гы-гы-гы!

Дальше читать бесполезно.

prasthita
()
Ответ на: комментарий от Love5an

Результаты теста скорости для Lisp-а

пока для факториала получилось 0.893 0.827 0.067)

И это без всяких GMP :)

Вообще-то кто его знает, в 40МБ-бинарнике вполне может быть замаскированная статически слинкованная GMP. :) Хотя какая разница...

Итак, результаты экспериментов с лиспом.

Код для факториала отсюда: http://paste.lisp.org/display/88867

$ sbcl --script fact.lisp
...
$ time ./fact_lisp 30000 > /dev/null

real    0m0.901s
user    0m0.835s
sys     0m0.065s

В результате отделения опций компилятора от кода получилось:

$ cat fact.cl
(defun factorial (n &aux (result 1))
    (declare (type unsigned-byte n result))
    (loop while (> n 1) do
        (setf result (* result n))
        (decf n)
    )
    result)

(defun main ()
    (princ (factorial (parse-integer (second sb-ext:*posix-argv*))))
    (write-line "")
    0)

$ sbcl --eval "(compile-file \"fact.cl\")" \
       --eval "(load \"fact.cl\")" \
       --eval "(sb-ext:save-lisp-and-die \"fact_cl\" :executable t :toplevel 'main)"
...
$ time ./fact_cl 30000 > /dev/null

real    0m0.904s
user    0m0.831s
sys     0m0.072s
Разница для 40МБ бинарника укладывается в погрешность измерений. :)

Заодно удалось нагуглить и рекурсивную реализацию для лиспа:

$ cat fact_recurse.cl
(defun factorial (n)
    (if (<= n 1)
        1
        (* n (factorial (- n 1)))
    ))

(defun main ()
    (princ (factorial (parse-integer (second sb-ext:*posix-argv*))))
    (write-line "")
    0)

$ sbcl --eval "(compile-file \"fact_recurse.cl\")" \
       --eval "(load \"fact_recurse.cl\")" \
       --eval "(sb-ext:save-lisp-and-die \"fact_recurse_cl\" :executable t :toplevel 'main)"
...
$ time ./fact_recurse_cl 30000 > /dev/null

real    0m0.868s
user    0m0.823s
sys     0m0.046s

`write-line` добавлен, чтобы сделать переход на следующую строку (иначе md5-сумма вывода программы не сходилась). Может быть можно и как-то красивее, но это было первое, что я нагуглил. :)

Еще я проверял один рекурсивный вариант, который вроде бы должен быть быстрее:

(defun factorial (n &optional (acc 1)) (if (<= n 1) acc (factorial (- n 1) (* acc n))))

Но он оказался примерно таким же, как и циклический (0.902 0.838 0.064). :)

А также удалось нагуглить еще один вариант, который я не понимаю, но он работает быстрее всех (0.825 0.776 0.049):

(defun factorial(n) (if (< n 1) 1 (do ((f 1)(i 2 (1+ i))) ((> i n) f) (setq f (* f i)))))

Таким образом lisp в тесте на вычисление обошел Ocaml и приблизился к хаскелю. Кроме того lisp - пока что первый функциональный язык, в котором рекурсивный вариант быстрее циклического. А также язык, в котором есть неизвестно какой вариант, который быстрее их обоих. :)

Имеет смысл собирать результаты для фибоначчи? Или это уже никому не интересно? ;)

sergem
() автор топика
Ответ на: Результаты теста скорости для Lisp-а от sergem
(defun factorial(n)
  (if (< n 1)
    1
    (do ((f 1)(i 2 (1+ i)))
      ((> i n) f)
      (setq f (* f i)))))

Это тоже итеративный вариант, макрос do. Просто, видимо, SBCL умеет его хорошо оптимизировать.

Love5an
()
Ответ на: комментарий от Love5an

> вот с gmp: http://paste.lisp.org/display/89017

> у меня расхождение с C++ версией - буквально в пределах погрешности time.

Ну, это уже трудно назвать простым решением. То есть в таком случае уж лучше писать на С++ или Хаскеле (если нужен именно функциональный язык), там той же производительности можно будет достичь меньшими усилиями. :) Это во-первых.

А во-вторых: оно не работает. :)

$ sbcl --eval "(compile-file \"fact2.cl\")" \
       --eval "(load \"fact2.cl\")" \
       --eval "(sb-ext:save-lisp-and-die \"fact2_cl\" :executable t :toplevel 'main)"
...
$ ./fact2_cl 30
CORRUPTION WARNING in SBCL pid 9934(tid 140737353983728):
Memory fault at 1 (pc=0x3fab869499, sp=0x7ffff10a8d50)
The integrity of this image is possibly compromised.
Continuing with fingers crossed.

debugger invoked on a SB-SYS:MEMORY-FAULT-ERROR in thread #<THREAD "initial thread" RUNNING {1002838021}>:
  Unhandled memory fault at #x1.

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

(no restarts: If you didn't do this on purpose, please report it as a bug.)

(SB-SYS:MEMORY-FAULT-ERROR)
0]

sergem
() автор топика
Ответ на: комментарий от www_linux_org_ru

стоит чуть переделать Makefile чтобы там считалось и репортилось также время тривиальных задач, типа факториал 1

Табличка подсчета факториала 1 выглядит так:

Language          real     user     system
c++               0.002    0.002    0.002
php               0.022    0.013    0.008
haskell           0.002    0.002    0.000
haskell (recurse) 0.002    0.002    0.000
lisp (unknown)    0.051    0.018    0.032
lisp (recurse)    0.051    0.017    0.035
lisp              0.051    0.013    0.038
ocaml             0.001    0.000    0.001
perl (gmp)        0.039    0.035    0.004
ocaml (recurse)   0.001    0.000    0.001
java              0.134    0.081    0.017
ruby              0.005    0.002    0.003
python            0.015    0.008    0.007
bc                0.002    0.000    0.001
tcl               0.004    0.003    0.002
perl              0.059    0.050    0.010

Общая таблица по тесту «факториал»:

Language            real     user   system
c++                0.179    0.175    0.004
php                0.307    0.292    0.015
haskell            0.567    0.430    0.136
haskell (recurse)  0.703    0.455    0.249
lisp (unknown)     0.825    0.776    0.049
lisp (recurse)     0.868    0.823    0.046
lisp               0.904    0.831    0.072
ocaml              1.911    1.692    0.219
perl (gmp)         2.045    2.041    0.004
ruby1.9           ~3.033   ~2.807   ~0.011
ocaml (recurse)    3.623    3.346    0.276
java               8.422    8.571    0.176
ruby               8.974    8.963    0.007
python            17.609   17.602    0.004
bc                49.716   49.607    0.099
tcl               76.448   76.120    0.274
perl             123.891  123.840    0.014

sergem
() автор топика
Ответ на: комментарий от sergem

Почему не простое решение? В C++ тогда тоже не простое, там нет длинной арифметики из коробки.
Если же, допустим, существовала бы библиотека-биндинг к GMP для CL, что не так уж и сложно написать, вообще говоря, было бы простое. Только это нафиг никому не надо, потому как в основных реализациях CL поизводительности длинной арифметики для подавляющего большинства случаев и так достаточно. А там, где не достаточно, там и C++ не к месту.

Ну и, я говорю, тест неадекватен, потому как меряет не столько расчет, сколько IO. А текстовое IO в CL гораздо медленнее mpz_out_str/mpz_in_str.

>То есть в таком случае уж лучше писать на С++ или Хаскеле (если нужен именно функциональный язык), там той же производительности можно будет достичь меньшими усилиями

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

>А во-вторых: оно не работает. :)

>#-win32 (sb-sys:int-sap 1)

сюда нужно подставить указатель на структуру FILE для stdout.

Love5an
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.