Montag, 27. Juli 2009

Szenario Greenfields

Die Lotka-Volterra-Gleichung beschreibt die Wechselwirkung von Räuber- und Beutepopulationen, wobei sich die Räuber- von der Beutepopulation ernährt. Interessant dabei ist, dass die gegenseitige Wechselwirkung der beiden Populationen das System stabil hält und sich dadurch selbst reguliert.
In meinem neusten Projekt, einer Simulation, habe ich dieses Szenario nachgebildet. Die Beutepopulation, bei mir einfach Resource genannt, hat im Gegensatz zur Obigen ein begrenztes Wachstum. Dadurch schwanken die beiden Populationen nicht mehr, in einer sinusförmigen Kurve, sondern stabilisieren sich auf einem Niveau.
Zudem wollte ich, die Resource auch unabhängig von der Population, meinen Räubern, simulieren können. Um dies zu erreichen musste ich die Resource von der Populationen entkoppeln. In meiner Simulation ist nur noch die Population an die Resource gekoppelt, hat aber auf diese Einfluss, indem sie sich von dieser ernährt und direkt deren Grösse ändert. Die Grösse der Resource auf der anderen Seite ist ausschlaggebend, wie viel Nahrung für die Population verfügbar ist und steuert damit deren Sterberate. Dadurch ist die Wechselwirkung wieder gegeben.

Dieses erste Szenario, das ich vorläufig Greenfields getauft habe, soll nun die Basis für verschiedene KI Agenten werden. Sie sollen die Populationen steuern und sich in diesem System zurechtfinden.

Sonntag, 5. April 2009

Reading Binary Files with Ruby

For my current project I need to read some binary data with Ruby. The data file is formatted as 16-bit BINARY INTEGERS, hi-byte-first order with 2160 x 4320 data values.

From my Java days I knew the DataInputStream class. With it it's every easy to read the data, just use io.readShort().
Therefore I was looking everywhere in IO and File for some methods to read the data in an appropriate format. But I couldn't find anything really useful as all methods are working with strings and characters.

But then I found the pack method in Array and its corresponding method unpack in String. With this two methods it's easy to read and write binary files in various formats.

Here the code snippet where I read the gzip compressed file. @size is in my case 4320 and @height an array of 2160. After reading the file I can access the data with @data[line][row].
Zlib::GzipReader.open(filename) do |gz|
  x = 0
  # read one line (16-Bit-Integer = 2 bytes)
  while line = gz.read(2 * @size) 
    @data[x] = line.unpack('s' * @size)
    x += 1
  end
end
If you need more, have a look at BitStruct. It provides a nice DSL for handling binaries.

Donnerstag, 26. Februar 2009

Using multiple Ruby versions with Textmate

I have currently Ruby 1.8.6 and 1.9.1 on my machine. To test my libraries against both versions, it would be very convenient to switch between versions within your IDE . With Netbeans there's no problem as you can define different ruby environments (Tools/Ruby Platforms). In Textmate I couldn't find anything like that. But there's an easy way too: just define the path to your ruby version in the start script, just like you would do it for a shell script.
#!/usr/bin/ruby
puts RUBY_VERSION # => 1.8.6
#!/usr/local/bin/ruby1.9
puts RUBY_VERSION # => 1.9.1

Freitag, 6. Februar 2009

2 Wochen CakePHP

Nach 2 Wochen arbeiten mit CakePHP hier eine kurze Bilanz. Im Grossen und Ganzen versucht CakePHP die Ideen und Konzepte von Rails zu übernehmen, was auch in vielen Teilen des Frameworks gelingt.
Der grösste Kompromiss den CakePHP einging, ist auf PHP4 statt gleich auf PHP5 zu setzten. Dies hat leider weitreichende Konsequenzen:

Das beginnt schont bei ganz einfachen Sachen, wie etwa dass CakePHP kein Exception Handling kennt. Stattdessen werden E_USER_WARININGs und dergleichen ausgelöst.

Der grösste Unterschied zu Rails besteht jedoch in der Implementierung von ActiveReocrd. Das AR-Objekt wird in erster Linie als SQL Generator eingesetzt, die 'find' und 'read' Mehtoden geben keine Objekte zurück, sondern tief verschachtelte Arrays. Die Implementierung von CakePHP erinnert daher mehr an ein Table Data Gateway Pattern. Auch in CakePHP kennt Active Record die bekannten Callbacks wie 'beforeSave', aber auch hier wird mit Arrays gearbeitet, nicht mit Objekten.
Eine weitere Konsequenz daraus ist das CakePHP kein Single Table Inheritance oder deren polymorphe Variante kennt.

Zusammendfassend lässt sich aus meinen ersten Erfahrungen sagen, dass das Erarbeiten von Business Domain Objects in CakePHP wesentlich schwieriger fällt als in Rails. Das Active Record von CakePHP ist darauf ausgerichtet aus der Datenbank zu lesen und schreiben und die gewonnen Daten gleich auszugeben. Die Business Logik sollte gemäss dem Motto 'fat model, skinny controller' in die Model-Objekte abwandern, dies ist aber genau die Schwierigkeit, wenn diese mehrheitlich nur als Arrays daher kommen.
Trotzdem ist CakePHP eine gute Alternative, wenn man an PHP gebunden ist (oder wird). Zudem bieten heute alle Provider PHP, wer für ein einfaches Projekt einen billigen Host braucht ist CakePHP sicherlich eine Option. Für komplexere Projekte lohnt sich Ruby zu erlernen und auf ein durchgehend objekt-orientiertes Framework wie Rails zu setzten.

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)