Donnerstag, 15. Januar 2009

Small Performance Test Rails vs. CakePHP

At my company we're currently evaluating a Web-Framework. Today we had a look at CakePHP and got some simple tutorial examples running. Because CakePHP is very close to Rails, I wrote one example also in Rails to see differences.
Just to get an impression about the performance of the two frameworks, I run ApacheBench to get some benchmarks.

But first things first, here some figures about the test environement:

  • iMac OS X 10.5.6, 2 GHz PowerPC G5, 1 GB RAM
  • PHP 5.2.6
  • CakePHP 1.2.0.7962
  • Ruby 1.8.6
  • Rails 2.2.2
  • MySQL 5.0.67
I wanted a very simple scenario, but one that effects all MVC components. I chosen to take an overview page, that displays a list of objects stored in the database. With Rails this task was accomplished by just using the scaffold generator. With Cake I could have also used the scaffold variable, but then I would have compared a static scaffolded page against a dynamic one. So I wrote the index function:
function index(){
  $jobs = $this->Job->find('all');
  $this->set('jobs', $jobs);
}
I put only 3 rows in the database table, so that reading from it takes virtually no time and we can better see the time consumed by the framework itself.
As one Mongrel can only handle one request at the time, I fired 50 requests in a row.
ab -n 50 -c 1 http://127.0.0.1:3000/jobs
Last thing I made, was a very small and straight forward PHP script, that does the same job. This way I had a benchmark of a script using only plain vanilla PHP with no framework overhead.
Here finally the results (see full reports below):
CakePHP                       Time per request:       365.572 [ms] (mean)
Rails (RAILS_ENV=development) Time per request:       105.029 [ms] (mean)
Rails (RAILS_ENV=production)  Time per request:        30.318 [ms] (mean)
Pure PHP                      Time per request:         9.026 [ms] (mean)
Honestly I was quite surprised. There's so much talk about Ruby being slow, which is surely true when compared to Java. Also PHP is faster in microbenchmarks. But it seems to me that PHP is only really fast when used as much as possible straight forward, this is what PHP was designed for. When it comes to build frameworks and OOP there're better tools.
Also the other known PHP Frameworks CodeIgniter and Symfony are not that different, CakePHP lies somewhere between them.
On the other hand Ruby really was designed as a pure OO language and it seems that this turns to be an advantage when building frameworks, also from a performance perspective.

The full results:
Rails (RAILS_ENV=development)
=============================

ab -n 50 -c 1 http://127.0.0.1:3000/jobs
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:        Mongrel
Server Hostname:        127.0.0.1
Server Port:            3000

Document Path:          /jobs
Document Length:        3043 bytes

Concurrency Level:      1
Time taken for tests:   5.251 seconds
Complete requests:      50
Failed requests:        0
Write errors:           0
Total transferred:      178251 bytes
HTML transferred:       152150 bytes
Requests per second:    9.52 [#/sec] (mean)
Time per request:       105.029 [ms] (mean)
Time per request:       105.029 [ms] (mean, across all concurrent requests)
Transfer rate:          33.15 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.8      0       6
Processing:    74  105  64.5     83     318
Waiting:       73  103  63.6     83     317
Total:         74  105  64.7     84     318

Percentage of the requests served within a certain time (ms)
  50%     84
  66%     86
  75%     88
  80%     91
  90%    237
  95%    304
  98%    318
  99%    318
 100%    318 (longest request)

RAILS (RAILS_ENV=production) 
============================

ab -n 50 -c 1 http://127.0.0.1:3000/jobs
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 127.0.0.1 (be patient).....done


Server Software:        Mongrel
Server Hostname:        127.0.0.1
Server Port:            3000

Document Path:          /jobs
Document Length:        3043 bytes

Concurrency Level:      1
Time taken for tests:   1.516 seconds
Complete requests:      50
Failed requests:        0
Write errors:           0
Total transferred:      178236 bytes
HTML transferred:       152150 bytes
Requests per second:    32.98 [#/sec] (mean)
Time per request:       30.318 [ms] (mean)
Time per request:       30.318 [ms] (mean, across all concurrent requests)
Transfer rate:          114.82 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.3      0       3
Processing:    17   30  32.8     21     187
Waiting:       16   29  32.8     20     186
Total:         17   30  32.8     21     187

Percentage of the requests served within a certain time (ms)
  50%     21
  66%     24
  75%     25
  80%     26
  90%     34
  95%    138
  98%    187
  99%    187
 100%    187 (longest request)

CAKEPHP
=======

ab -n 50 -c 1 http://jobscake/jobs
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking jobscake (be patient).....done


Server Software:        Apache/2.2.9
Server Hostname:        jobscake
Server Port:            80

Document Path:          /jobs
Document Length:        4176 bytes

Concurrency Level:      1
Time taken for tests:   18.279 seconds
Complete requests:      50
Failed requests:        49
   (Connect: 0, Receive: 0, Length: 49, Exceptions: 0)
Write errors:           0
Total transferred:      217781 bytes
HTML transferred:       200781 bytes
Requests per second:    2.74 [#/sec] (mean)
Time per request:       365.572 [ms] (mean)
Time per request:       365.572 [ms] (mean, across all concurrent requests)
Transfer rate:          11.64 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.4      0       3
Processing:   329  365  42.2    355     570
Waiting:      329  364  39.6    355     543
Total:        329  365  42.4    356     572

Percentage of the requests served within a certain time (ms)
  50%    356
  66%    364
  75%    372
  80%    374
  90%    390
  95%    409
  98%    572
  99%    572
 100%    572 (longest request)

PURE PHP
========

ab -n 50 -c 1 http://localhost/develop/projects/Test/pure_php_jobs/
This is ApacheBench, Version 2.3 <$Revision: 655654 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient).....done


Server Software:        Apache/2.2.9
Server Hostname:        localhost
Server Port:            80

Document Path:          /develop/projects/Test/pure_php_jobs/
Document Length:        1923 bytes

Concurrency Level:      1
Time taken for tests:   0.451 seconds
Complete requests:      50
Failed requests:        0
Write errors:           0
Total transferred:      107200 bytes
HTML transferred:       96150 bytes
Requests per second:    110.80 [#/sec] (mean)
Time per request:       9.026 [ms] (mean)
Time per request:       9.026 [ms] (mean, across all concurrent requests)
Transfer rate:          231.98 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.2      0       1
Processing:     5    9   7.3      6      53
Waiting:        3    7   7.7      5      53
Total:          5    9   7.3      6      53

Percentage of the requests served within a certain time (ms)
  50%      6
  66%      8
  75%     10
  80%     12
  90%     13
  95%     16
  98%     53
  99%     53
 100%     53 (longest request)

Sonntag, 4. Januar 2009

Implementing if in Ruby the Smalltalk Way

In Smalltalk the if statement is not a keyword, but also a normal message sending to an object. In this case the object receiving the message is a boolean. When a condition is resolved it is either true or false and therefore we can send the messages ifTrue and ifFalse to it.
condition ifTrue: [ doSomething ] ifFalse: [ doSomethingElse ]
If the condition is true the first block will be executed, if false the second block will executed.
This can easly be implemented in Ruby by patching the corresponding True- and FalseClass:
class TrueClass
  
  def if_true
    yield
    self
  end
  
  def if_false
    self
  end
  
  alias if if_true
  alias else if_false
  
end

class FalseClass
  
  def if_true
    self
  end
  
  def if_false
    yield 
    self
  end
  
  alias if if_true
  alias else if_false
  
end
As you can see I always return self, so that we chain the methods. Now we can write:
i = 5
(i > 3).if_true {puts 'true'}.if_false {puts 'false'}
Using the alias methods we can write it in the more common form:
i = 5
(i > 3).if {puts 'true'}.else {puts 'false'}
or
i = 5
(i > 3).if do 
  puts 'true'
end.else do 
  puts 'false'
end

Montag, 22. Dezember 2008

Nokogiri and Webrat

Today I made some more steps using RSpec. My new steps involved Webrat, a handy library for acceptance tests, which depends on Nokogiri. The installation guide of webrat only mentioned to require webrat in your helper. But I got the following error
NameError: uninitialized constant Nokogiri::CSS::XML
After a I while figured out, that you first have to require nokogiri explicitly. So my steps for webrat are now beginning with:
require 'nokogiri'
require "webrat/rails"

steps_for :webrat do
  
  When "visits '$link'" do |link|
    visits link
  end
  ...
To check your gem versions against mine, here an extract of my gem list:
Rails (2.2.2)
webrat (0.3.2)
nokogiri (1.1.0)
libxml-ruby (0.9.7)
libxslt-ruby (0.9.1)

Dienstag, 4. November 2008

Compiling Ruby 1.9.1 on Leopard

Update
With the new Ruby 1.9.1 stable version, I only used the following configure (--with-readline-dir is not recognized anymore)
./configure --prefix=/usr/local --program-suffix=1.9
make
sudo make install


The new Ruby 1.9 Preview is out, time for a first impression. But already step one, compiling readline 5.2 failed. Thanx to Han's Hint, I was able to patch the responsible shobj-conf file.

So after patching readline's conf file, in the readline-5.2 directory:
./configure --prefix=/usr/local
make
sudo make install
Now compiling ruby should work as expected, don't forget to point to your new compilied readline library. In your ruby directory:
./configure --prefix=/usr/local --program-suffix=1.9 --with-readline-dir=/usr/local
make
sudo make install
Now you have both ruby versions in parallel, try ruby -v and ruby1.9 -v

Montag, 1. September 2008

Kurzpräsentation über Cassandra

Beim nächsten Treffen der Ruby on Rails User Group Schweiz am 3. September habe ich die Gelegenheit eine Kurzpräsentation zu halten. Ich werde die letzte Version von Cassandra vorstellen, meiner Prolog inspierten Ruby Library.

Seit der letzten Version hat sich wieder einiges getan: So werden Variablen nicht mehr mit Symbolen gekennzeichnet, sondern sie beginnen einfach mit einem Underscore.
Mit der neuen Version können jetzt ganz einfach 'Kategorien' erstellt werden, indem eine Klasse Cassandra einfach erweitert (extend). Damit können Prädikate nun auch Klassen zugeordnet werden, Cassandra löst diese beim suchen auf und gibt die entsprechenden Instanzen zurück.
Dies funktioniert auch über Vererbungen oder Mixins hinweg.

Bin jedenfalls gespannt auf das Feedback ...

Dienstag, 3. Juni 2008

Ruby meets Smalltalk

Avi Bryant , der Autor des Smalltalk Webframework Seaside, hat dieses Wochenende an der RailsConf 2008 MagLev vorgestellt, eine VirtualMachine für Ruby. Gemstone, die Firma hinter MagLev, hat schon jahrzentelange Erfahrung mit Smalltalk VMs und hat dieses Knowhow nun für Ruby umgemünzt. Anders ist auch kaum zu erklären wie sie in so kurzer Zeit, die Rede ist von 3 Monaten, eine eigene VM auf die Beine stellen konnten.

Erste Demos und Vergleiche mit MRI scheinen den alten Intepreter schlicht und einfach stehen zu lassen, MagLev soll je nach Benchmarktest zwischen 6 und 111 mal schneller sein.
Allzu oft haben wir schon gehört, dass eigentlich nichts dagegenspricht, dass Ruby so schnell wie Smalltalk sein könnte. MagLev hat nun offenbar den Beweis angetreten.

Noch ist MagLev nicht fähig Rails laufen zu lassen, aber Avi Bryant scheint keine Zweifel daran zu haben, dass auch dies in Kürze erreicht werden kann.

Die Kehrseite an der neuen VM ist wohl, dass sie nicht OpenSource sein wird. Der Ruby Sourcecode wird zwar öffentlich sein, der C Code für den Compiler wird aber geschlossen bleiben. Voraussichtlich wird es verschiedene Lizenzen je nach Grösse und Umfang geben. Ist zu hoffen, dass MagLev auch in einer Version für kleinere Budgets zu haben ist. Denn viel wird genau davon abhängen, ob sich MagLev in der Rails Community durchsetzten kann.

Update: Der erste Teil von Avi's Präsentation:
MagLev presentation at RailsConf 2008 - part 1 from Monty Williams on Vimeo.

Mittwoch, 19. März 2008

Ruby People

Für den zweiten Teil meiner Präsentation habe ich mich auf die Suche nach einigen bekannten Köpfen in der Ruby Gemeinschaft gemacht. Dabei war ich doch etwas zu eifrig, so dass meine Folie bald überladen war und ich sie aus der Präsentation kippen musste. Stattdessen stelle ich das "Gruppenbild" hier auf den Blog: