Август 22, 2010
PHP vs. Python vs. Ruby vs. Groovy
Холивар между поклонниками того или иного языка программирования не закончится никогда. Рискну подбросить дров в огонь, поделившись результатами сравнительного тестирования четырех динамических языков: PHP, Python, Ruby и Groovy. Выбор первых трех очевиден – они наиболее популярны в среде веб-разработчиков. Groovy пока малоизвестен, но в будущем может занять достойную нишу за счет врожденной 100% интеграции с технологиями Java.
Два года назад один хороший человек написал более-менее адекватные скрипты для тестирования производительности всех упомянутых языков и выложил результаты в блог. Я воспроизвел тесты на последних версиях интерпретаторов, результатами и комментариями делюсь ниже.
Тестовая среда
Все тесты проводились на моем домашнем ноутбуке Intel Core2 Duo T5500 (два ядра по 1.66 GHz) с тремя гигабайтами оперативной памяти и быстрым SSD-диском. Управляет парадом свежая инсталляция 32-битной Windows 7 Home Premium.
Каждый скрипт запускался три раза, через некоторое время после прогона всех языков, выполнялся еще один проход по три раза, в качестве итогового времени взяты усредненные значения.
Теоретически, каждый процесс должен был выполняться на одном из ядер процессора, загружая его под завязку (что косвенно подтверждалось постоянным показателем загрузки двухядерного процессора в 50%), но график Диспетчера Задач почему-то показывал одинаковую загрузку в 50% обоих ядер во время теста.
Во время второго прогона также замерялся расход памяти, согласно показаниям Диспетчера Задач.
Производительность
Привожу точные данные по времени выполнения одной итерации скрипта каждым из языков (считается автоматически): табличная форма дает точные цифры, диаграмма позволяет оценить их наглядно.
Точные данные производительности
| Язык | Время, микросекунд |
| Groovy 1.7.4 | 260 |
| PHP 5.2.14 | 1,200 |
| Python 2.6.5 | 210 |
| Python 2.6.5 + Psyco 2.0 | 70 |
| Ruby 1.8.7 | 400 |
| Ruby 1.9.2 | 300 |
Визуальное представление

Потребление памяти
Здесь необходимо сделать пару замечаний. Большинство интерпретаторов сразу занимали определенный обьем памяти, и в течение работы этот показатель уже не менялся. Однако запросы PHP во время работы линейно росли (особенность тестового кода?), а Ruby 1.8.7 забирал и отпускал память волнообразно (сборка мусора)? В табличку вошли максимальные значения.
| Язык | Память, мегабайт |
| Groovy 1.7.4 | 27 |
| PHP 5.2.14 | 37 |
| Python 2.6.5 | 3.8 |
| Python 2.6.5 + Psyco 2.0 | 4 |
| Ruby 1.8.7 | 2 |
| Ruby 1.9.2 | 1.8 |

Некоторые замечания
Прежде всего, очень разочаровал PHP. Отвратительное быстродействие и жуткий расход памяти. Я попытался улучшить ситуацию, установив eAccelerator 0.9.5.3, но он фактически никак не повлиял ни на скорость выполнения, ни на потребление памяти. Насколько я понимаю, его фишка состоит в кешировании предварительно скомпилированных скриптов, и устранении фазы интерпретации при повторном обращении к коду. На быстродействие самого кода он не влияет, поэтому в наших условиях толк от него нулевой.
Приятно удивил ускоритель Питона Psyco. Незначительно увеличился расход памяти, при многократно возросшей скорости выполнения. Впечатляющий прирост производительности, в три раза! Связка Python/Psyco позволяет оторваться от всех конкурентов, рассматривая удаляющиеся силуэты в зеркале заднего вида
С другой стороны, Ruby оказался очень экономичным, потребляя в два раза меньше памяти, чем Python, и более чем на порядок обходя PHP. Несомненный плюс. Я ожидал, что версия 1.9.2 будет раза в два-три быстрее предыдущей генерации 1.8.7, но чуда не произошло, разница составляет процентов 30%. Полагаю, что разрыв в большинстве реальных применений будет все-таки значительно больше.
Ну а что же C++ и Java?
Тесты для этих языков я также воспроизвел, но результаты сводить в общую табличку не стал. Например, Java выполняет каждую итерацию всего лишь за полторы микросекунды, то есть в 100-200 раз быстрее, чем указанные динамические языки. С другой стороны, в реальных приложениях разрыв будет гораздо меньше, так как основная часть работы динамических скриптов будет выполняться в подключаемых библиотеках, которые компилируются в двоичный выполнимый код конкретной платформы и работают без потерь производительности.
SLoC, Verbosity, Syntactic Sugar
Немалое значение для программистов, работающих с языками, имеют такие показатели, как количество строк кода (SLoC, Source lines of code), а также количество ключевых слов, скобок, спецсимволов (Verbosity) и вообще красота конструкций языка, легкость восприятия кода, и его красота (Syntactic Sugar).
Можно спорить о субьективных показателях, но с цифрами не поспоришь: программа на Python в два раза короче, чем аналогичный код на Groovy, PHP, C++ или Java. Ruby где-то посередине: программа в полтора раза длинее, чем питонный аналог, но все еще значительно короче, чем остальные скрипты.
Насчет красоты конструкций, изящности и легкости восприятия писать не буду – на вкус и цвет все фломастеры разные, смотрите сами, решайте сами
Так кто самый крутой?
Не буду прятаться за размытыми формулировками
На мой вкус, очевидно, что круче всех оказался Python (в случае ускорения за счет Psyco), где-то очень рядом находится новый Ruby 1.9.2 – теряя в производительности, получаем выигрыш в расходе памяти.
Конечно, все цифры и выводы относятся только к данному тестовому приложению, в других условиях, на других платформах, более прямыми руками выводы могут быть противоположными, поэтому приветствуется перепроверка моих результатов, критика и комментарии, а также варианты иных тестовых скриптов, которые могут изменить расстановку сил на Олимпе. Ниже вы найдете все исходные коды для разных языков, а значит – сможете самостоятельно повторить мои опыты. Дерзайте и делитесь результатами
Важные обновления
UPD1 Важное замечание для Python: если функцию range заменить на xrange, чтобы не генерировать огромные списки в памяти, а обходить цикл по итератору, производительность скрипта возрастает незначительно, но расход памяти падает в два раза, с 4 до 2 MB (2.5 MB в случае Psyco)! Таким образом, описанное выше преимущество Ruby по памяти становится неактуальным, интерпретаторы идут ноздря в ноздрю по этому показателю.
UPD2 Оптимизированный PHP-скрипт с форума SQL.ru (без сеттеров-геттеров), показывает более чем в два раза лучшее быстродействие. Среднее время выполнения подскочило до 500 микросекунд на итерацию. Потребление памяти, к сожалению, осталось на прежнем уровне.
Исходные тексты скриптов
Groovy
class Chain
{
def size
def first
def init(siz)
{
def last
size = siz
for(def i = 0 ; i < siz ; i++)
{
def current = new Person()
current.count = i
if (i == 0) first = current
if (last != null)
{
last.next = current
}
current.prev = last
last = current
}
first.prev = last
last.next = first
}
def kill(nth)
{
def current = first
def shout = 1
while(current.next != current)
{
shout = current.shout(shout,nth)
current = current.next
}
first = current
}
}
class Person
{
def count
def prev
def next
def shout(shout,deadif)
{
if (shout < deadif)
{
return (shout + 1)
}
prev.next = next
next.prev = prev
return 1
}
}
def main(args)
{
println "Starting"
def ITER = 100000
def start = System.nanoTime()
for(def i = 0 ; i < ITER ; i++)
{
def chain = new Chain()
chain.init(40)
chain.kill(3)
}
def end = System.nanoTime()
println "Total time = " + ((end - start)/(ITER * 1000)) + " microseconds"
}
def ITER = 100000
def start = System.nanoTime()
for(def i = 0 ; i < ITER ; i++)
{
def chain = new Chain()
chain.init(40)
chain.kill(3)
}
def end = System.nanoTime()
println "Time per iteration = " + ((end - start)/(ITER * 1000)) + " microseconds"
PHP
class Person
{
function __construct($c)
{
$this->count = $c;
}
function getPrev()
{
return $this->prev;
}
function setPrev($pr)
{
$this->prev = $pr;
}
function getNext()
{
return $this->next;
}
function setNext($nxt)
{
$this->next = $nxt;
}
function shout($shout, $nth)
{
if ($shout < $nth)
{
return $shout + 1;
}
$this->getPrev()->setNext($this->getNext());
$this->getNext()->setPrev($this->getPrev());
return 1;
}
}
class Chain
{
var $first;
function __construct($size)
{
$last = null;
for($i = 0; $i < $size ; $i++)
{
$current = new Person($i);
if ($this->first == null) $this->first = $current;
if ($last != null)
{
$last->setNext($current);
$current->setPrev($last);
}
$last = $current;
}
$this->first->setPrev($last);
$last->setNext($this->first);
}
function kill($nth)
{
$current = $this->first;
$shout = 1;
while($current->getNext() !== $current)
{
$shout = $current->shout($shout,$nth);
$current = $current->getNext();
}
$this->first = $current;
}
}
$start = microtime(true);
$ITER = 100000;
for($i = 0 ; $i < $ITER ; $i++)
{
$chain = new Chain(40);
$chain->kill(3);
}
$end = microtime(true);
printf("Time per iteration = %3.2f microseconds ",(($end - $start) * 1000000 / $ITER));
Python
import psyco
psyco.full()
class Person(object):
def __init__(self,count):
self.count = count;
self.prev = None
self.next = None
def shout(self,shout,deadif):
if (shout < deadif): return (shout + 1)
self.prev.next = self.next
self.next.prev = self.prev
return 1
class Chain(object):
def __init__(self,size):
self.first = None
last = None
for i in range(size):
current = Person(i)
if self.first == None : self.first = current
if last != None :
last.next = current
current.prev = last
last = current
self.first.prev = last
last.next = self.first
def kill(self,nth):
current = self.first
shout = 1
while current.next != current:
shout = current.shout(shout,nth)
current = current.next
self.first = current
return current
import time
ITER = 100000
start = time.time()
for i in range(ITER):
chain = Chain(40)
chain.kill(3)
end = time.time()
print 'Time per iteration = %s microseconds ' % ((end - start) * 1000000 / ITER)
Ruby
class Person
attr_reader :count, :prev, :next
attr_writer :count, :prev, :next
def initialize(count)
#puts 'Initializing person : ' + count.to_s()
@count = count
@prev = nil
@next = nil
end
def shout(shout, deadif)
if shout < deadif
return shout + 1
end
@prev.next = @next
@next.prev = @prev
return 1
end
end
class Chain
attr_reader :first
attr_writer :first
def initialize(size)
@first = nil
last = nil
for i in (1..size)
current = Person.new(i)
if @first == nil
@first = current
end
if last != nil
last.next = current
current.prev = last
end
last = current
end
@first.prev = last
last.next = @first
end
def kill(nth)
current = @first
shout = 1
while current.next != current
shout = current.shout(shout,nth)
current = current.next
end
@first = current
return current
end
end
ITER=100000
start = Time.now
ITER.times { |i|
chain = Chain.new(40)
chain.kill(3)
}
ends = Time.now
puts 'Time per iteration = ' + ((ends - start) * 1000000 / ITER).to_s() + " microseconds"
C++
[/cpp]
#include <stdio .h>
#include <stdlib .h>
#include <time .h>
#include <sys /time.h>
class Person
{
public:
Person(int count) : _next(NULL), _prev(NULL) { _count = count; }
int shout(int shout, int nth)
{
if (shout < nth) return (shout + 1);
_prev->_next = _next;
_next->_prev = _prev;
return 1;
}
int count() { return _count; }
Person* next() { return _next; }
void next(Person* person) { this->_next = person ; }
Person* prev() { return _prev; }
void prev(Person* person) { this->_prev = person; }
private:
int _count;
Person* _next;
Person* _prev;
};
class Chain
{
public:
Chain(int size) : _first(NULL)
{
Person* current = NULL;
Person* last = NULL;
for(int i = 0 ; i < size ; i++)
{
current = new Person(i);
if (_first == NULL) _first = current;
if (last != NULL)
{
last->next(current);
current->prev(last);
}
last = current;
}
_first->prev(last);
last->next(_first);
}
Person* kill(int nth)
{
Person* current = _first;
int shout = 1;
while(current->next() != current)
{
Person* tmp = current;
shout = current->shout(shout,nth);
current = current->next();
if (shout == 1)
{
delete tmp;
}
}
_first = current;
return current;
}
Person* first() { return _first; }
private:
Person* _first;
};
int main(int argc, char** argv)
{
int ITER = 1000000;
Chain* chain;
struct timeval start, end;
gettimeofday(&start,NULL);
for(int i = 0 ; i < ITER ; i++)
{
chain = new Chain(40);
chain->kill(3);
delete chain;
}
gettimeofday(&end,NULL);
fprintf(stdout,"Time per iteration = %d microseconds ", (((end.tv_sec - start.tv_sec) * 1000000) + (end.tv_usec - start.tv_usec)) / ITER);
return 0;
}
[cpp]
Java
public class Chain
{
private Person first = null;
public Chain(int size)
{
Person last = null;
Person current = null;
for (int i = 0 ; i < size ; i++)
{
current = new Person(i);
if (first == null) first = current;
if (last != null)
{
last.setNext(current);
current.setPrev(last);
}
last = current;
}
first.setPrev(last);
last.setNext(first);
}
public Person kill(int nth)
{
Person current = first;
int shout = 1;
while(current.getNext() != current)
{
shout = current.shout(shout, nth);
current = current.getNext();
}
first = current;
return current;
}
public Person getFirst()
{
return first;
}
public static void main(String[] args)
{
int ITER = 100000;
long start = System.nanoTime();
for (int i = 0 ; i < ITER ; i++)
{
Chain chain = new Chain(40);
chain.kill(3);
}
long end = System.nanoTime();
System.out.println("Time per iteration = " + ((end - start) / (ITER )) + " nanoseconds.");
}
}
class Person
{
int count;
private Person prev = null;
private Person next = null;
public Person(int count)
{
this.count = count;
}
public int shout(int shout, int deadif)
{
if (shout < deadif) return (shout + 1);
this.getPrev().setNext(this.getNext());
this.getNext().setPrev(this.getPrev());
return 1;
}
public int getCount()
{
return this.count;
}
public Person getPrev()
{
return prev;
}
public void setPrev(Person prev)
{
this.prev = prev;
}
public Person getNext()
{
return next;
}
public void setNext(Person next)
{
this.next = next;
}
}

Август 22, 2010 - 15:37 -
Касательно сравнения LoC, то для языка, который я знаю хорошо (Ruby) — такое ощущение, что писавший код либо не очень хорошо его знает, либо злонамеренно
удлиннял код, чтобы Python смотрелся выигрышно.
Т.е. в той части, которая про syntactic sugar и verbosity я бы этим результатам не стал доверять
Август 22, 2010 - 17:34 -
Бесспорно, что специалист по тому или иному языку перепишет соответствующий скрипт, сделав его и более коротким, и более производительным
Здесь интерес скорее в том, чтобы оценить языки с высоты птичьего полета, посмотрев на более-менее аналогичный код, который можно понять, не погружаясь в хитрые специфические идиоматические конструкции, которые присутствуют в каждом языке.
Хотя следующим шагом теста логично было бы сравнить как раз оптимизированные специалистами программы, в этом случае наверняка размеры сильно уменьшатся, а скорость всех скриптов возрастет раза в два.
Август 22, 2010 - 18:47 -
Тогда раздел «SLoC, Verbosity, Syntactic Sugar» не имеет смысла вообще.
Август 24, 2010 - 13:36 -
В приведенном оригинале статьи (с исходниками тестов) один из пользователей привел код на Perl, который значительно выиграл по скорости у Python и Ruby. Почему вы не включили в статью результат на Perl, если он явно быстрее чем Python?
Сентябрь 4, 2010 - 23:21 -
Если Вы приводите результаты тестирования питона с Psyco, то было бы честно привести ркзультаты тестирования:
jRuby (по некоторым данным всреднем в 10 раз быстрее Ruby 1.8.7, хотя и жрет много памяти)
Groovy++ (по некоторым данным уступает Java 2-3 раза)
Да ускорителей PHP хватает.
Сентябрь 30, 2010 - 0:23 -
Из всего исследования можно сделать только один вывод: когда пишешь велосипед, его КПД (производительность, LoC и прочее) зависит только от случая (в данном случае – от интерпретатора/транслятора). Не берусь говорить про все языки, скажу только про C++: то что Вы написали, это не C++. Это первая лабораторная работа студента-первокурсника. Причём, студент-первокурсник, очевидно, изучает Паскаль, а не C++.
В C++ есть такая практика как применение Boost (в Boost есть готовый интрузивный список – улучшает метрику LoC). Есть такая практика как аллокаторы (если заранее известно с каким количеством каких объектов мы будем работать, с памятью можно работать более вменяемо – повышает производительность). Ваше дёрганье new по поводу и без – это вообще полнейший ахтунг.
Вы бы ещё, для пущей C++ности std::cout использовали вместо printf(), чтобы показать как вывод на консоль медленно работает.
Не стоит Вам говорить о языках программирования, а тем более сравнивать их, если Вы не удосужились познакомиться с ними глубже if-then.
Ноябрь 11, 2010 - 17:48 -
Интересно, очень инетересно. Особенно интересен отрыв php – 1200
Теперь понятно, почему люди требуют знание ruby для высоко нагруженных проектов
Ноябрь 12, 2010 - 21:32 -
Для меня, как знающего языки C# и Java, Groovy гораздо выразительнее, понятнее и лаконичнее чем Python. Я с питоном мало знаком. В синтаксисе питона не понравились подчеркивания, особенно стрелочки >>> и много слов вместо символов. http://groovy.codehaus.org/Differences+from+Python
У меня правильно отформатированный код груви (убрал лишее) занял 50 строк. Так что питон в этом плане ни сколько не обгоняет груви. Я не критикую питон, скорее оправдываю груви.
После Java на груви писать раза в два короче, если не больше. Нравятся свойства, коллекции, замыкания. Про DSL вообще молчу. Минус груви как и плюс – это Java – потребляет много памяти.
Январь 3, 2011 - 1:19 -
Вообще, если рассматривать по чесноку, то код для groovy с минимальными модификациями и использованием groovy++ исполняется в ~40 раз быстрее
Код смотрите https://github.com/alextkachman/groovypp/blob/master/Examples/src/Iterations.groovy
У меня с закоментированным @Typed “Total time = 162.4111341 microseconds”, а если раскоментировать – получим “Total time = 4.3760887 microseconds”
Ощущаем разницу. Из всех приведенных в тесте Groovy самый быстрый
Январь 15, 2011 - 11:32 -
Perl и не надо включать.
Достаточно сложные программы, а не тесты уровня “Hello, world” на Perl программеру писать сложнее. А упомянутые языки тем и хороши, что позволяют создавать сложные проекты не шибко квалифицированными программистами.
Июль 24, 2011 - 6:04 -
Рекомендую посмотреть в сторону Rubinius. Еще одна имплементация руби.
Я прогнал тест на последнем development preview бранче, 2.0pre.
Результаты в среднем 53 микросекунд за итерацию. Думаю весьмы переспективная имплементация.
Ноябрь 28, 2011 - 16:33 -
Злые какие-то тут все программисты. Спасибо Сергей за статью и проделанный труд, я думаю в штатах где вы находитесь люди более приятные и ценят друг друга. У нас так и осталось как в СССР. Все делают друг другу гадости, вместо того, чтобы улыбнуться и похвалить. Читал на одном американском форуме наших эмигрантов. В штатах, радуется когда сосед прибавку получил, у нас же, когда у соседа машина сломалась. Ну лан. А теперь по теме.
Ваша статья, мне помогла немного разобраться в языках, посмотреть на них глазами обычного пользователя. Я начинающий программист. Раньше изучал С++, потом Java, сейчас учу С# и смотрю в область веб языков программирования. Заинтересовался Руби и Питоном больше всего. Есть также в приоритетах уехать в дальнейшем в штаты. Будьте добры, посоветуйте, какой язык из этих наиболее востребован, наиболее престижный и на каком из них написан какой-то крупный проект вроде фэйсбук, слышал что твитер написан на рубе, а ютуб на питоне, может только слухи.
Еще раз спасибо за статью, поставил ссылку в своем блоге. Удачи.