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?) - предлагайте. :)

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


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

> clos-классы: http://paste.lisp.org/display/88874#1

> структуры: http://paste.lisp.org/display/88874#2

Не уверен, насколько применим такой тест к лиспу и что именно при этом будет тестироваться. Как-бы это и не совсем управление памятью, и не совсем классы... Но почему бы не попробовать...

Исходный скрипт с clos-классами:

$ sbcl --script fib_clos.cl
...
$ time ./fib_clos 40
102334155
real    0m29.384s
user    0m29.038s
sys     0m0.315s

После отделения опций сборки от кода и удаления неизвестных мне строк ... стало чуть-чуть быстрее. :)

$ cat fib_class.cl
(declaim (optimize (speed 3) (space 0) (safety 0) (debug 0)))

(defclass fib ()
    ((n :type fixnum :reader fib-n :initarg :n :initform 1)))

(defun fib-value (fib &aux (n (fib-n fib)))
    (declare (type fib fib) (type fixnum n))
    (the fixnum
        (if (<= n 2)
            1
            (+ (fib-value (make-instance 'fib :n (- n 1)))
               (fib-value (make-instance 'fib :n (- n 2)))))))

(defun main ()
    (princ (fib-value (make-instance 'fib :n (parse-integer (second sb-ext:*posix-argv*)))))
    0)
$ sbcl --eval "(compile-file \"fib_class.cl\")" \
       --eval "(load \"fib_class.cl\")" \
       --eval "(sb-ext:save-lisp-and-die \"fib_class_cl\" :executable t :toplevel 'main)"
[...]
$ time ./fib_class_cl 40
102334155
real    0m29.027s
user    0m28.648s
sys     0m0.360s

Аналогично, исходный вариант для структур:

$ sbcl --script fib_lisp.cl
...
$ time ./fib_lisp 40
102334155
real    0m1.800s
user    0m1.768s
sys     0m0.034s

И он же, после «чистки»:

$ cat fib_struct.cl
(declaim (optimize (speed 3) (space 0) (safety 0) (debug 0)))

(declaim (inline fib-n make-fib))
(defstruct fib
    (n 1 :type fixnum))

(defun fib-value (fib &aux (n (fib-n fib)))
    (declare (fib fib) (type fixnum n))
    (the fixnum
        (if (<= n 2)
            1
            (let ((a (make-fib :n (- n 2)))
                  (b (make-fib :n (- n 1))))
                 (declare (type fib a b) (dynamic-extent a b))
                 (+ (fib-value a)
                    (fib-value b))))))

(defun main ()
    (princ (fib-value (make-fib :n (parse-integer (second sb-ext:*posix-argv*)))))
    0)
$ sbcl --eval "(compile-file \"fib_struct.cl\")" \
       --eval "(load \"fib_struct.cl\")" \
       --eval "(sb-ext:save-lisp-and-die \"fib_struct_cl\" :executable t :toplevel 'main)"
[...]
$ time ./fib_struct_cl 40
102334155
real    0m1.797s
user    0m1.768s
sys     0m0.028s
(может и тут тоже стало быстрее, но в данном случае разница в скорости не превышает погрешность измерения)

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

>Не уверен, насколько применим такой тест к лиспу и что именно при этом будет тестироваться. Как-бы это и не совсем управление памятью, и не совсем классы... Но почему бы не попробовать...

Структуры вполне покрывают жабоподобную модель ООП(single inheritance, минимум метаинформации)(кстати, передаются копированием ссылки, аналогично объектам жабоклассов) с поправкой на CLOS(методы вне классов).

Тестируется тут создание объектов структур, а что еще. (sbcl, кстати, учитывает декларацию dynamic-extent и пихает их на стек)

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

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

Дополненная таблица по тесту «фибоначчи»:

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 (escape)     1.593     1.403    0.122   170 MB
java              2.703     2.511    0.185   170 MB
lisp (struct)     1.797     1.768    0.028    35 MB
lisp (class)     29.027    28.648    0.360    59 MB
ocaml            14.694    14.546    0.003     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

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

  • ocamlc заменен на ocamlopt (спасибо dave за подсказку)
  • добавлен замер для java + escape analysis (спасибо kamre за опцию) - эта опция делает создание некоторых объектов похожим на то, что в с++-stack тесте, сильно увеличивая производительность
  • добавлен замер для lisp-а - классы и структуры (спасибо Love5an за код)
sergem
() автор топика
Ответ на: комментарий от sergem

но все-равно, clos как то медленно слишком у тебя.
У меня c++_heap 24 секунды работает, lisp_clos - 11 секунд

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

> Тестируется тут создание объектов структур, а что еще. (sbcl, кстати, учитывает декларацию dynamic-extent и пихает их на стек)

Да, если убрать dynamic-extent - становится значительно медленнее: 6.963 6.750 0.207

> Структуры вполне покрывают жабоподобную модель ООП ... (кстати, передаются копированием ссылки, аналогично объектам жабоклассов) с поправкой на CLOS(методы вне классов).

Ага. Тогда это аналог c++-stack и java-escape тестов. Собственно, и результаты получились близкие.

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

> В C++ тогда тоже не простое, там нет длинной арифметики из коробки. Если же, допустим, существовала бы библиотека-биндинг к GMP для CL, что не так уж и сложно написать, вообще говоря, было бы простое.

Именно наличие такой библиотеки и делает решение простым, а язык удобным. Иначе можно было бы включать в тест еще и assembler или freepascal. Но в freepascal-е нет удобной библиотеки. При прочих равных условиях это делает его неподходящим языком для задач длинной арифметики. :)

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

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

В смысле, вывод на экран? Это можно измерить. После замены:

(defun main ()
    (princ (factorial (parse-integer (second sb-ext:*posix-argv*))))
    (write-line "")
    0)
на (я правильно написал?):
(defun main ()
    (setf f (factorial (parse-integer (second sb-ext:*posix-argv*))))
    (princ f)
    (write-line "")
    (princ f)
    (write-line "")
    0)
вместо 0.904 0.831 0.072 тест выполнялся за 0.998 0.927 0.071. Для вывода 120КБ-числа - это не так уж и много. Да и вычитание этого времени все равно не поставит lisp в один ряд с С++. :)

> Т.е. факториал сферический в вакууме может и да, но если не такой уж сферический, то по удобству хаскель, и, тем более, c++ проигрывают.

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

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

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

Э... И как это сделать?

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

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

>вместо 0.904 0.831 0.072 тест выполнялся за 0.998 0.927 0.071.

Это мемоизация. Второй раз компилятор просто не стал опять переводить результат в строку, напечатал на stdout строку, полученную в первый раз.
Вот как у меня влияет на время, к примеру:
http://pic.ipicture.ru/uploads/091023/e14CuP0BtY.png
>Э... И как это сделать?

Численное значение указателя.
Скомпилируй на си 'printf("%X",stdout)'
полученный хекс напиши вместо единицы
т.е.
"#-win32 (sb-sys:int-sap #xПОЛУЧЕННОЕ_ЧИСЛО)"

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

> Библиотеки к удобству языка отношения не имеют. Это библиотеки.

Я бы не согласился. Библиотеки - это одна из главных характеристик удобства языка. glibc или libstdc++ - это же тоже библиотеки. Возможность не изобретать велосипед, а использовать готовые решения - резко увеличивает скорость разработки, которая и определяет удобство языка для той или иной задачи.

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

«Сишная» - в смысле написанная на Си? А какая разница? Хоть на перле. Если она есть, она быстрая и ее можно просто использовать в данном языке для решения задачи, то почему бы этого не делать?

> Вот как у меня влияет на время, к примеру: http://pic.ipicture.ru/uploads/091023/e14CuP0BtY.png

Это - с выводом и без? Может разница - из-за того, что часть вычислений выкинулась транслятором? Раз выводить не надо, то можно и не считать... ;) Или просто под виндой вывод (как и работа с памятью) делается иначе и с другой скоростью?.. (раньше он точно был медленнее, но я давно не сравнивал) :)

> Скомпилируй на си 'printf(«%X»,stdout)' полученный хекс напиши вместо единицы

Ага. Сработало, хотя это больше смахивает на временный хак. Получилось:

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

real    0m0.232s
user    0m0.201s
sys     0m0.032s

Это если не считать того, что md5sum-а вывода не сходится с правильным ответом. :) (там выводятся две строки вместо одной, и некоторые цифры отличаются)

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

>самый быстрый код создают компиляторы от Интел

Только под Linux 2/3 пакетов, собранных им - глючат :) А так - на банальном gzip набирается до 10-20% прироста скорости. В Gentoo для любого пакета можно указать, чем его собирать - поиграл я, поиграл, да и как-то забросил. Глюки не окупают производительность :)

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

А что есть Intel Common Lisp Compiler? :)
А то если нету - неинтересно.
C++ и фортран того не стоят :)
Там, где действительно производительность нужна, либо ставят кластер и что-нибудь распределенное, либо ручками из xmm в xmm байты перекладывают.

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

>А что есть Intel Common Lisp Compiler?

По ссылке как обычно не ходим?

>В забеге участвовали след. компиляторы/интерпретаторы:
• GNU FORTRAN v 4.4.0
• INTEL VISUAL FORTRAN v 11.1.038
• GNU C++ v 4.4.0
• INTEL C++ v 11.1.035
• MSVS C++ v 8.00.50727.42 (из MSVS 2005)
• SUN CLIENT JVM 1.6_16
• SUN SERVER JVM 1.6_16
• EXCELSIOR JET 4.6
• GNU JAVA v 4.4.0

>Что же касается Java, то серверная JVM от Sun на некоторых тестах выглядит достаточно достойно, почти на уровне MSVS C++ и GNU G++.

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

Вот это
• SUN CLIENT JVM 1.6_16
• SUN SERVER JVM 1.6_16
некоторые до сих пор считают интерпретаторами

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

> самый быстрый код создают компиляторы от Интел.
> http://forum.vingrad.ru/index.php?showtopic=24644

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

Замечания к этому и подобным тестам:

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

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

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

Примерно это наблюдалось с TCL-ем в тесте "факториал". Если там убрать вывод - программа становится просто реактивной. :)

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