until nil {

Hyped Coderwall Widget

31 May 2011

Last week saw the arrival of two new geeky joys: Hype and Coderwall. The former, an HTML5 Animation tool, has lots of buttons, timelines, scenes and ways to make beautiful and interactive products. The later, has no buttons or interesting features, it is simply a merit system for open source coders, recognizing coding virtues by awarding each ones with specific badges.

An easy idea it was to put some Hype into Coderwall, animating the JSON data stream of each profile in an embeddable widget. I chose to implement JSON data fetching server side, making it a hosted widget on AppEngine. Dynamic JavaScripts get served from a Python profile fetcher, loadable from anywhere a script tag belongs, with some cosmetic options available at init time. You can view the result on the side of this blog, until nil, and can find installation informations HERE if you want this widget on your own blog.

Recursive Fibonacci Benchmark Sequel

07 Oct 2010

As a sequel to the previous benchmark, but now with an improved comparisons scheme taking total execution time into account. The bench rides again, adding some zizany to the established order by welcoming in some unusual suspects as well as some references languages I hadn’t included before.

With Node.js awesomeness factor in perpetual escalation, it seemed imperative to now include Javascript on v8, as well as its meta-sibling CoffeeScript. Then to add perspective to the interpreted camp, two old timers, Perl and Lua, have now also been challenged. Lua, having the non-official title of fastest scripting language, with bonus tail recursion skills, surely has potential in this benchmark. Falcon, a new multi-paradigm interpreted language, unknown to most programmers, seemed like an interesting guest to invite and so is now also part of the comparison.

One more Objective-C based language, F-Script, will allow some runtime implementation comparison with the other Objective-C languages, Nu and MacRuby. Finally, some more rubyist attention was put in by also including jRuby, as well as upgrading MacRuby to its newest 0.7 iteration and adding an Ahead-Of-Time compiled variant of MacRuby.

Todays contenders are:

As stated in the previous instalment: “All the benchmarks were implemented in a recursive fashion, and so highlight the languages runtime ability to deal with deep recursion. And as a side-show, when fibonacci is called on smaller numbers, the benchmark provide us with a comparison on runtime initialization delay.”, so while you evaluate the results, remember this benchmark doesn’t aim at absolute language power comparison.

Benching:

Case value: 5
                     ruby: | 0 failed |     0.007s overall | score:     1.000 |
                     objc: | 0 failed |     0.009s overall | score:     1.356 |
                     perl: | 0 failed |     0.009s overall | score:     1.390 |
                      lua: | 0 failed |     0.022s overall | score:     3.383 |
                nu w/objc: | 0 failed |     0.064s overall | score:     9.800 |
            macruby w/aot: | 0 failed |     0.082s overall | score:    12.674 |
          f-script w/objc: | 0 failed |     0.116s overall | score:    17.833 |
                       nu: | 0 failed |     0.122s overall | score:    18.810 |
                  macruby: | 0 failed |     0.134s overall | score:    20.541 |
                 f-script: | 0 failed |     0.139s overall | score:    21.433 |
            sbcl compiled: | 0 failed |     0.144s overall | score:    22.152 |
                   falcon: | 0 failed |     0.147s overall | score:    22.587 |
                 js on v8: | 0 failed |     0.147s overall | score:    22.605 |
                   coffee: | 0 failed |     0.275s overall | score:    42.268 |
                   python: | 0 failed |     0.276s overall | score:    42.481 |
           macruby w/objc: | 0 failed |     0.533s overall | score:    81.943 |
                    jruby: | 0 failed |     1.195s overall | score:   183.746 |
                     sbcl: | 0 failed |     2.735s overall | score:   420.637 |

Case value: 10
                      lua: | 0 failed |     0.004s overall | score:     1.000 |
                     ruby: | 0 failed |     0.006s overall | score:     1.370 |
                     objc: | 0 failed |     0.009s overall | score:     2.082 |
                     perl: | 0 failed |     0.009s overall | score:     2.168 |
                   falcon: | 0 failed |     0.013s overall | score:     3.175 |
                   python: | 0 failed |     0.031s overall | score:     7.650 |
                 js on v8: | 0 failed |     0.053s overall | score:    12.985 |
                       nu: | 0 failed |     0.063s overall | score:    15.402 |
                nu w/objc: | 0 failed |     0.064s overall | score:    15.538 |
            macruby w/aot: | 0 failed |     0.082s overall | score:    19.835 |
            sbcl compiled: | 0 failed |     0.090s overall | score:    21.916 |
          f-script w/objc: | 0 failed |     0.097s overall | score:    23.574 |
                 f-script: | 0 failed |     0.115s overall | score:    28.060 |
                     sbcl: | 0 failed |     0.117s overall | score:    28.502 |
                  macruby: | 0 failed |     0.125s overall | score:    30.357 |
                   coffee: | 0 failed |     0.160s overall | score:    38.912 |
           macruby w/objc: | 0 failed |     0.219s overall | score:    53.206 |
                    jruby: | 0 failed |     0.703s overall | score:   170.890 |

Case value: 15
                      lua: | 0 failed |     0.004s overall | score:     1.000 |
                     ruby: | 0 failed |     0.007s overall | score:     1.702 |
                     objc: | 0 failed |     0.009s overall | score:     2.087 |
                     perl: | 0 failed |     0.011s overall | score:     2.656 |
                   falcon: | 0 failed |     0.014s overall | score:     3.310 |
                   python: | 0 failed |     0.032s overall | score:     7.737 |
                 js on v8: | 0 failed |     0.054s overall | score:    12.874 |
                nu w/objc: | 0 failed |     0.065s overall | score:    15.735 |
            macruby w/aot: | 0 failed |     0.082s overall | score:    19.820 |
            sbcl compiled: | 0 failed |     0.090s overall | score:    21.743 |
                       nu: | 0 failed |     0.090s overall | score:    21.748 |
          f-script w/objc: | 0 failed |     0.095s overall | score:    22.805 |
                     sbcl: | 0 failed |     0.117s overall | score:    28.131 |
                  macruby: | 0 failed |     0.125s overall | score:    29.987 |
                   coffee: | 0 failed |     0.159s overall | score:    38.296 |
           macruby w/objc: | 0 failed |     0.217s overall | score:    52.272 |
                 f-script: | 0 failed |     0.299s overall | score:    71.919 |
                    jruby: | 0 failed |     0.701s overall | score:   168.601 |

Case value: 20
                      lua: | 0 failed |     0.008s overall | score:     1.000 |
                     objc: | 0 failed |     0.009s overall | score:     1.165 |
                     ruby: | 0 failed |     0.023s overall | score:     3.003 |
                   falcon: | 0 failed |     0.027s overall | score:     3.545 |
                     perl: | 0 failed |     0.033s overall | score:     4.274 |
                   python: | 0 failed |     0.047s overall | score:     6.164 |
                 js on v8: | 0 failed |     0.054s overall | score:     7.036 |
                nu w/objc: | 0 failed |     0.064s overall | score:     8.450 |
            macruby w/aot: | 0 failed |     0.082s overall | score:    10.828 |
            sbcl compiled: | 0 failed |     0.091s overall | score:    11.984 |
          f-script w/objc: | 0 failed |     0.093s overall | score:    12.244 |
                     sbcl: | 0 failed |     0.116s overall | score:    15.272 |
                  macruby: | 0 failed |     0.128s overall | score:    16.763 |
                   coffee: | 0 failed |     0.160s overall | score:    21.015 |
           macruby w/objc: | 0 failed |     0.219s overall | score:    28.771 |
                       nu: | 0 failed |     0.404s overall | score:    53.040 |
                    jruby: | 0 failed |     0.853s overall | score:   112.039 |
                 f-script: | 0 failed |     2.275s overall | score:   298.831 |

Case value: 25
                     objc: | 0 failed |     0.011s overall | score:     1.000 |
                      lua: | 0 failed |     0.046s overall | score:     4.210 |
                 js on v8: | 0 failed |     0.058s overall | score:     5.358 |
                nu w/objc: | 0 failed |     0.066s overall | score:     6.058 |
            macruby w/aot: | 0 failed |     0.089s overall | score:     8.127 |
            sbcl compiled: | 0 failed |     0.096s overall | score:     8.825 |
          f-script w/objc: | 0 failed |     0.100s overall | score:     9.183 |
                     sbcl: | 0 failed |     0.124s overall | score:    11.411 |
                   falcon: | 0 failed |     0.135s overall | score:    12.333 |
                  macruby: | 0 failed |     0.136s overall | score:    12.461 |
                   python: | 0 failed |     0.136s overall | score:    12.487 |
                   coffee: | 0 failed |     0.166s overall | score:    15.182 |
                     ruby: | 0 failed |     0.199s overall | score:    18.269 |
           macruby w/objc: | 0 failed |     0.219s overall | score:    20.065 |
                     perl: | 0 failed |     0.275s overall | score:    25.164 |
                    jruby: | 0 failed |     0.854s overall | score:    78.260 |
                       nu: | 0 failed |     3.824s overall | score:   350.476 |
                 f-script: | 0 failed |    24.284s overall | score:  2225.848 |

Case value: 30
                     objc: | 0 failed |     0.032s overall | score:     1.000 |
                nu w/objc: | 0 failed |     0.090s overall | score:     2.866 |
                 js on v8: | 0 failed |     0.092s overall | score:     2.927 |
          f-script w/objc: | 0 failed |     0.134s overall | score:     4.260 |
            macruby w/aot: | 0 failed |     0.146s overall | score:     4.624 |
            sbcl compiled: | 0 failed |     0.156s overall | score:     4.945 |
                     sbcl: | 0 failed |     0.184s overall | score:     5.845 |
                   coffee: | 0 failed |     0.225s overall | score:     7.153 |
                  macruby: | 0 failed |     0.234s overall | score:     7.441 |
           macruby w/objc: | 0 failed |     0.241s overall | score:     7.654 |
                      lua: | 0 failed |     0.465s overall | score:    14.769 |
                   falcon: | 0 failed |     1.071s overall | score:    33.993 |
                    jruby: | 0 failed |     1.103s overall | score:    35.010 |
                   python: | 0 failed |     1.107s overall | score:    35.132 |
                     ruby: | 0 failed |     2.125s overall | score:    67.452 |
                     perl: | 0 failed |     2.952s overall | score:    93.696 |
                       nu: | 0 failed |    42.562s overall | score:  1351.047 |
                 f-script: | 0 failed |   274.925s overall | score:  8726.948 |

Case value: 35
                     objc: | 0 failed |     0.257s overall | score:     1.000 |
                nu w/objc: | 0 failed |     0.318s overall | score:     1.235 |
          f-script w/objc: | 0 failed |     0.350s overall | score:     1.361 |
                 js on v8: | 0 failed |     0.454s overall | score:     1.763 |
           macruby w/objc: | 0 failed |     0.463s overall | score:     1.800 |
            macruby w/aot: | 0 failed |     0.784s overall | score:     3.046 |
            sbcl compiled: | 0 failed |     0.818s overall | score:     3.178 |
                     sbcl: | 0 failed |     0.842s overall | score:     3.274 |
                   coffee: | 0 failed |     0.853s overall | score:     3.314 |
                  macruby: | 0 failed |     1.380s overall | score:     5.364 |
                    jruby: | 0 failed |     3.956s overall | score:    15.372 |
                      lua: | 0 failed |     5.125s overall | score:    19.919 |
                   falcon: | 0 failed |    11.598s overall | score:    45.074 |
                   python: | 0 failed |    11.875s overall | score:    46.150 |
                     ruby: | 0 failed |    23.344s overall | score:    90.725 |
                     perl: | 0 failed |    32.544s overall | score:   126.477 |
                       nu: | 0 failed |   473.608s overall | score:  1840.603 |
                 f-script: | 0 failed |  3330.532s overall | score: 12943.599 |

Case value: 40
                     objc: | 0 failed |     2.785s overall | score:     1.000 |
           macruby w/objc: | 0 failed |     2.909s overall | score:     1.045 |
          f-script w/objc: | 0 failed |     3.217s overall | score:     1.155 |
                nu w/objc: | 0 failed |     4.319s overall | score:     1.551 |
                 js on v8: | 0 failed |     4.660s overall | score:     1.673 |
            macruby w/aot: | 0 failed |     7.818s overall | score:     2.807 |
                   coffee: | 0 failed |     8.102s overall | score:     2.909 |
            sbcl compiled: | 0 failed |     8.202s overall | score:     2.945 |
                     sbcl: | 0 failed |    10.506s overall | score:     3.772 |
                  macruby: | 0 failed |    13.866s overall | score:     4.979 |
                    jruby: | 0 failed |    35.971s overall | score:    12.916 |
                      lua: | 0 failed |    56.933s overall | score:    20.442 |
                   falcon: | 0 failed |   125.456s overall | score:    45.046 |
                   python: | 0 failed |   131.910s overall | score:    47.364 |
                     ruby: | 0 failed |   259.228s overall | score:    93.078 |
                     perl: | 0 failed |   360.102s overall | score:   129.298 |
                       nu: | 0 failed |  6375.526s overall | score:  2289.197 |
                 f-script: | 0 failed | 38450.093s overall | score: 13805.894 |
                 
Ranking by Total Times:
                     objc: |     3.120 seconds overall |
          f-script w/objc: |     4.202 seconds overall |
           macruby w/objc: |     5.021 seconds overall |
                nu w/objc: |     5.051 seconds overall |
                 js on v8: |     5.572 seconds overall |
            macruby w/aot: |     9.165 seconds overall |
            sbcl compiled: |     9.687 seconds overall |
                   coffee: |    10.100 seconds overall |
                     sbcl: |    14.743 seconds overall |
                  macruby: |    16.128 seconds overall |
                    jruby: |    45.336 seconds overall |
                      lua: |    62.607 seconds overall |
                   falcon: |   138.460 seconds overall |
                   python: |   145.415 seconds overall |
                     ruby: |   284.939 seconds overall |
                     perl: |   395.934 seconds overall |
                       nu: |  6896.199 seconds overall |
                 f-script: | 42082.663 seconds overall |



Ranking by Total Scores:
                     objc: | score:    10.691 |
                nu w/objc: | score:    61.231 |
                      lua: | score:    65.724 |
                 js on v8: | score:    67.221 |
            macruby w/aot: | score:    81.761 |
          f-script w/objc: | score:    92.415 |
            sbcl compiled: | score:    97.688 |
                  macruby: | score:   127.895 |
                   coffee: | score:   169.050 |
                   falcon: | score:   169.064 |
                   python: | score:   205.164 |
           macruby w/objc: | score:   246.756 |
                     ruby: | score:   276.599 |
                     perl: | score:   385.124 |
                     sbcl: | score:   516.843 |
                    jruby: | score:   776.835 |
                       nu: | score:  5940.324 |
                 f-script: | score: 38122.532 |

Combined Ranking:

           objc: |  2 |     3.120 seconds overall | score:    10.691 |
f-script w/objc: |  8 |     4.202 seconds overall | score:    92.415 |
       js on v8: |  9 |     5.572 seconds overall | score:    67.221 |
      nu w/objc: | 10 |     5.051 seconds overall | score:    61.231 |
  macruby w/aot: | 11 |     9.165 seconds overall | score:    81.761 |
  sbcl compiled: | 14 |     9.687 seconds overall | score:    97.688 |
            lua: | 15 |    62.607 seconds overall | score:    65.724 |
 macruby w/objc: | 15 |     5.021 seconds overall | score:   246.756 |
         coffee: | 17 |    10.100 seconds overall | score:   169.050 |
        macruby: | 18 |    16.128 seconds overall | score:   127.895 | 
         falcon: | 23 |   138.460 seconds overall | score:   169.064 |
           sbcl: | 24 |    14.743 seconds overall | score:   516.843 |
         python: | 25 |   145.415 seconds overall | score:   205.164 |
          jruby: | 27 |    45.336 seconds overall | score:   776.835 |
           ruby: | 28 |   284.939 seconds overall | score:   276.599 |
           perl: | 30 |   395.934 seconds overall | score:   385.124 |
             nu: | 34 |  6896.199 seconds overall | score:  5940.324 |
       f-script: | 36 | 42082.663 seconds overall | score: 38122.532 |

Interpretation:

What strikes first, is how Lua is as fast as can be when loading, while staying just above the waterline under which Perl, Ruby, Python and Falcon are crushed when recursion gets too deep, adding a remarkable note to its performance reputation. Objective-C really needed to stay first the list, while F-Script came to be worst than pure Nu at the very low bottom, surpassing it when combined with Objective-C functions at the top of the rankings.

From the new players list, we can emit a couple of novel statements:

  • v8 is awesome, endures deep recursion almost as fast as the Objective-C based routines, but does not load up as fast as some of its mates.
  • CoffeeScript features proportional awesomeness to pure v8, 2 times slower but more than 2 times funnier, plus it gets its speed back when pre-compiled to JavaScript.
  • jRuby really has a tough wake up but stays upright under the weight of deep recursion.
  • MacRuby is ruby impressive, no need for any extra Objective-C help, a big kid with its own tricks, like AOT compilation providing super easy, efficient optimization potential.
  • Falcon really stands high, scoring the highest in the fully loaded interpreted languages group composed of Perl, Python and Ruby. I voluntarily exclude Lua from this group because while its is an interpreted language, it is not fully loaded, or Batteries included as one might say and this fact surely accounts for its higher score. Falcon on the other end, almost overflows with base functionalities.
  • F-Script is really not meant for standalone scripting. One thing the bench does not show, is its light memory footprint: unlike Nu all you can eat appetite, F-Script will constrain itself to a very acceptable maximum. Making it an ideal choice as a scripting extension inside some Cocoa application.

In the end, no benchmark can show a true picture of what would be a real world use of these language technologies. This one truly has its limitations, but within them we were still able to get a rough feeling of how these languages matches up and which compromises they would imply.

Recursive Fibonacci Benchmark

23 Sep 2010

Now for something entirely different: lets have some recursive fun! The type of fun only a recursive Fibonacci benchmark could bring!
*Update! This benchmark has been extended here. *

The benchmark consist of 8 fibonacci sequences, from a short 5 numbers sequence to a long 40 numbers sequence, applied to 9 different language runtime configurations:

All the benchmarks were implemented in a recursive fashion, and so highlight the languages runtime ability to deal with deep recursion. And as a side-show, when fibonacci is called on smaller numbers, the benchmark provide us with a comparison on runtime initialization delay.

I chose to work with 2 yummy glue-code languages for Objective-C, Nu and MacRuby. The benchmark will nicely allow us to compare their respective performance but also their Objective-C runtime integration against Objective-C itself. To crank the fun factor up, add Ruby and Python, the coolest guys in town, and SBCL as a reference figure in recursion optimization, with and without compiled fibonacci function. All languages fibonacci implementations can be found on github.

now, The bench

Case value: 5
                     objc: | 0 failed |   0.009s overall | score:   1.000 |
                     ruby: | 0 failed |   0.010s overall | score:   1.177 |
                   python: | 0 failed |   0.032s overall | score:   3.647 |
                       nu: | 0 failed |   0.065s overall | score:   7.457 |
                nu w/objc: | 0 failed |   0.065s overall | score:   7.475 |
            sbcl compiled: | 0 failed |   0.082s overall | score:   9.371 |
                  macruby: | 0 failed |   0.105s overall | score:  12.072 |
                     sbcl: | 0 failed |   0.110s overall | score:  12.651 |
           macruby w/objc: | 0 failed |   0.198s overall | score:  22.704 |

Case value: 10
                     ruby: | 0 failed |   0.006s overall | score:   1.000 |
                     objc: | 0 failed |   0.009s overall | score:   1.565 |
                   python: | 0 failed |   0.032s overall | score:   5.680 |
                nu w/objc: | 0 failed |   0.065s overall | score:  11.739 |
                       nu: | 0 failed |   0.068s overall | score:  12.180 |
            sbcl compiled: | 0 failed |   0.082s overall | score:  14.804 |
                  macruby: | 0 failed |   0.096s overall | score:  17.251 |
                     sbcl: | 0 failed |   0.109s overall | score:  19.625 |
           macruby w/objc: | 0 failed |   0.201s overall | score:  36.150 |

Case value: 15
                     ruby: | 0 failed |   0.007s overall | score:   1.000 |
                     objc: | 0 failed |   0.009s overall | score:   1.267 |
                   python: | 0 failed |   0.033s overall | score:   4.803 |
                nu w/objc: | 0 failed |   0.065s overall | score:   9.381 |
            sbcl compiled: | 0 failed |   0.082s overall | score:  11.773 |
                       nu: | 0 failed |   0.094s overall | score:  13.571 |
                  macruby: | 0 failed |   0.098s overall | score:  14.032 |
                     sbcl: | 0 failed |   0.108s overall | score:  15.543 |
           macruby w/objc: | 0 failed |   0.200s overall | score:  28.778 |

Case value: 20
                     objc: | 0 failed |   0.009s overall | score:   1.000 |
                     ruby: | 0 failed |   0.023s overall | score:   2.630 |
                   python: | 0 failed |   0.041s overall | score:   4.602 |
                nu w/objc: | 0 failed |   0.065s overall | score:   7.243 |
            sbcl compiled: | 0 failed |   0.083s overall | score:   9.272 |
                  macruby: | 0 failed |   0.099s overall | score:  11.117 |
                     sbcl: | 0 failed |   0.108s overall | score:  12.065 |
           macruby w/objc: | 0 failed |   0.198s overall | score:  22.210 |
                       nu: | 0 failed |   0.428s overall | score:  47.885 |

Case value: 25
                     objc: | 0 failed |   0.011s overall | score:   1.000 |
                nu w/objc: | 0 failed |   0.065s overall | score:   6.083 |
            sbcl compiled: | 0 failed |   0.087s overall | score:   8.087 |
                  macruby: | 0 failed |   0.106s overall | score:   9.860 |
                     sbcl: | 0 failed |   0.113s overall | score:  10.569 |
                   python: | 0 failed |   0.130s overall | score:  12.113 |
                     ruby: | 0 failed |   0.196s overall | score:  18.290 |
           macruby w/objc: | 0 failed |   0.200s overall | score:  18.616 |
                       nu: | 0 failed |   4.196s overall | score: 390.787 |

Case value: 30
                     objc: | 0 failed |   0.032s overall | score:    1.000 |
                nu w/objc: | 0 failed |   0.094s overall | score:    2.980 |
            sbcl compiled: | 0 failed |   0.148s overall | score:    4.693 |
                  macruby: | 0 failed |   0.169s overall | score:    5.375 |
                     sbcl: | 0 failed |   0.172s overall | score:    5.470 |
           macruby w/objc: | 0 failed |   0.221s overall | score:    7.004 |
                   python: | 0 failed |   1.121s overall | score:   35.579 |
                     ruby: | 0 failed |   2.122s overall | score:   67.337 |
                       nu: | 0 failed |  41.131s overall | score: 1305.347 |

Case value: 35
                     objc: | 0 failed |   0.258s overall | score:    1.000 |
                nu w/objc: | 0 failed |   0.317s overall | score:    1.229 |
           macruby w/objc: | 0 failed |   0.441s overall | score:    1.712 |
            sbcl compiled: | 0 failed |   0.808s overall | score:    3.134 |
                     sbcl: | 0 failed |   0.834s overall | score:    3.233 |
                  macruby: | 0 failed |   0.885s overall | score:    3.433 |
                   python: | 0 failed |  11.875s overall | score:   46.056 |
                     ruby: | 0 failed |  23.474s overall | score:   91.042 |
                       nu: | 0 failed | 466.516s overall | score: 1809.332 |

Case value: 40
                     objc: | 0 failed |     2.798s overall | score:    1.000 |
           macruby w/objc: | 0 failed |     2.892s overall | score:    1.034 |
                nu w/objc: | 0 failed |     4.017s overall | score:    1.436 |
            sbcl compiled: | 0 failed |     8.251s overall | score:    2.949 |
                  macruby: | 0 failed |     8.815s overall | score:    3.150 |
                     sbcl: | 0 failed |    11.644s overall | score:    4.161 |
                   python: | 0 failed |   131.329s overall | score:   46.929 |
                     ruby: | 0 failed |   260.043s overall | score:   92.924 |
                       nu: | 0 failed | 10280.748s overall | score: 3673.710 |

Totals:
                     objc: |     3.134 seconds overall | score:    8.832 |
                nu w/objc: |     4.754 seconds overall | score:   47.565 |
            sbcl compiled: |     9.623 seconds overall | score:   64.082 |
                  macruby: |    10.373 seconds overall | score:   76.289 |
                     sbcl: |    13.198 seconds overall | score:   83.319 |
           macruby w/objc: |     4.551 seconds overall | score:  138.208 |
                   python: |   144.593 seconds overall | score:  159.409 |
                     ruby: |   285.882 seconds overall | score:  275.399 |
                       nu: | 10793.246 seconds overall | score: 7260.268 |

Interpretation

What does this tells us? First, as we may have forecasted, compiled code is the only sane way to survive under heavy loads. Objective-C is undoubtedly the first on the line, followed by Nu helped by Objective-C. From this, Nu shows its runtime implementation is a light efficient layer over Objective-C, while its performance without Objective-C proves any recursion should be avoided natively even if the interpreter seems fast enough for other purposes. Contrary to Nu, MacRuby seems to do better without any help (??!). We can see that MacRuby load times are not that great, this fact is confirmed when looking at the smaller numbers benchmarks, add the burden of loading an external framework and you kill its performance… It must be noted though that of all the pure interpreted implementations, MacRuby is the only one, with SBCL, which doesn’t fail exponentially under high recursive load. Then, Python surprisingly only scores average… While its startup time is possibly improved in a more recent version, it would probably not be enough to improve its heavy recursion performance. Finally, I can’t and won’t say much about SBCL, as it was more of a curiosity for me, but still, its compiled function performed noticeably better than the interpreted version which was not bad anyway.

Special Highlights:

As a final note, welcome my distinguished special purposes mentions:

  • Nu, for its handles and integration with Objective-C
  • MacRuby, for its ability to sustain deep recursions natively
  • Objective-C, being the reference case and the language in which both Nu and MacRuby are coded.

Some AppEngine Templates Benchmark Icing.

13 Sep 2010

I could’t hang up the call after the last 4 benchmarks (1, 2, 3, 4), but no, I am not back to add Django and Kay to the benchmarks… I was asked to include them and I should have done better than ditching them, seriously, I am sorry to all of those who would have liked them compared with the bunch included over here. Both are impeccable, well oiled, all-in-one monolithic frameworks, but far from what I am looking for: fast, elegant and modular web applications components. Besides, I would have liked to include them anyway, but their well planned nature makes them a bit uneasy to mount beside others apps inside a single AppEngine instance.

So, what I did instead, is welcome smaller request handlers, keeping the same templating engines in the front end. Now included is a very minimal bare metal WSGI handler implementation, as well as webob and a re-implementation of webapp in webapp2. I also took time to implement a variation on Tenjin, with a memcached cache combined.

The best part is that it didn’t turned out as expected, WSGI should have logically performed the best, but it didn’t! Maybe I did a bad implementation? Or could there be an unseen flaw in the benchmark? If you’d like, you can review the code in the benchmark’s repository on Github. Anyway, Here are the new compiled results, as always, take them with a grain of salt.

Low Overhead:

         WSGI w/Templator: | 250x, 0 failed |   8.263s overall, 0.033s each | score:   1.000 |
       web.py w/Templator: | 250x, 0 failed |   8.284s overall, 0.033s each | score:   1.003 |
            WSGI w/Tenjin: | 250x, 0 failed |   8.289s overall, 0.033s each | score:   1.003 |
            tipfy w/Jinja: | 250x, 0 failed |   8.297s overall, 0.033s each | score:   1.004 |
           webob w/Tenjin: | 250x, 0 failed |   8.297s overall, 0.033s each | score:   1.004 |
        tipfy w/Templator: | 250x, 0 failed |   8.309s overall, 0.033s each | score:   1.006 |
  webapp w/SimpleTemplate: | 250x, 0 failed |   8.311s overall, 0.033s each | score:   1.006 |
       webapp w/Templator: | 250x, 0 failed |   8.313s overall, 0.033s each | score:   1.006 |
    WSGI w/SimpleTemplate: | 250x, 0 failed |   8.334s overall, 0.033s each | score:   1.009 |
          web.py w/Tenjin: | 250x, 0 failed |   8.336s overall, 0.033s each | score:   1.009 |
           tipfy w/Tenjin: | 250x, 0 failed |   8.358s overall, 0.033s each | score:   1.011 |
        flask w/Templator: | 250x, 0 failed |   8.361s overall, 0.033s each | score:   1.012 |
   webob w/SimpleTemplate: | 250x, 0 failed |   8.365s overall, 0.033s each | score:   1.012 |
          bottle w/Tenjin: | 250x, 0 failed |   8.366s overall, 0.033s each | score:   1.012 |
        webob w/Templator: | 250x, 0 failed |   8.384s overall, 0.034s each | score:   1.015 |
 webapp2 w/SimpleTemplate: | 250x, 0 failed |   8.394s overall, 0.034s each | score:   1.016 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |   8.404s overall, 0.034s each | score:   1.017 |
  bottle w/SimpleTemplate: | 250x, 0 failed |   8.408s overall, 0.034s each | score:   1.017 |
           flask w/Tenjin: | 250x, 0 failed |   8.417s overall, 0.034s each | score:   1.019 |
       bottle w/Templator: | 250x, 0 failed |   8.419s overall, 0.034s each | score:   1.019 |
   flask w/SimpleTemplate: | 250x, 0 failed |   8.435s overall, 0.034s each | score:   1.021 |
          webapp w/Django: | 250x, 0 failed |   8.437s overall, 0.034s each | score:   1.021 |
     webapp w/TenjinCache: | 250x, 0 failed |   8.451s overall, 0.034s each | score:   1.023 |
    webapp2 w/TenjinCache: | 250x, 0 failed |   8.458s overall, 0.034s each | score:   1.024 |
     web.py w/TenjinCache: | 250x, 0 failed |   8.461s overall, 0.034s each | score:   1.024 |
            flask w/Jinja: | 250x, 0 failed |   8.471s overall, 0.034s each | score:   1.025 |
         webapp2 w/Django: | 250x, 0 failed |   8.495s overall, 0.034s each | score:   1.028 |
      webob w/TenjinCache: | 250x, 0 failed |   8.503s overall, 0.034s each | score:   1.029 |
  web.py w/SimpleTemplate: | 250x, 0 failed |   8.512s overall, 0.034s each | score:   1.030 |
            WSGI w/Django: | 250x, 0 failed |   8.535s overall, 0.034s each | score:   1.033 |
           webob w/Django: | 250x, 0 failed |   8.537s overall, 0.034s each | score:   1.033 |
           webapp w/Breve: | 250x, 0 failed |   8.550s overall, 0.034s each | score:   1.035 |
           flask w/Django: | 250x, 0 failed |   8.571s overall, 0.034s each | score:   1.037 |
          webapp w/Tenjin: | 250x, 0 failed |   8.581s overall, 0.034s each | score:   1.038 |
          webapp2 w/Breve: | 250x, 0 failed |   8.596s overall, 0.034s each | score:   1.040 |
      tipfy w/TenjinCache: | 250x, 0 failed |   8.600s overall, 0.034s each | score:   1.041 |
            webob w/Breve: | 250x, 0 failed |   8.618s overall, 0.034s each | score:   1.043 |
            tipfy w/Breve: | 250x, 0 failed |   8.630s overall, 0.035s each | score:   1.044 |
          bottle w/Django: | 250x, 0 failed |   8.640s overall, 0.035s each | score:   1.046 |
         webapp2 w/Tenjin: | 250x, 0 failed |   8.673s overall, 0.035s each | score:   1.050 |
           bottle w/Breve: | 250x, 0 failed |   8.679s overall, 0.035s each | score:   1.050 |
           web.py w/Breve: | 250x, 0 failed |   8.696s overall, 0.035s each | score:   1.052 |
      webapp2 w/Templator: | 250x, 0 failed |   8.707s overall, 0.035s each | score:   1.054 |
           tipfy w/Django: | 250x, 0 failed |   8.789s overall, 0.035s each | score:   1.064 |
      flask w/TenjinCache: | 250x, 0 failed |   8.816s overall, 0.035s each | score:   1.067 |
     bottle w/TenjinCache: | 250x, 0 failed |   8.863s overall, 0.035s each | score:   1.073 |
          web.py w/Django: | 250x, 0 failed |   8.905s overall, 0.036s each | score:   1.078 |
       WSGI w/TenjinCache: | 250x, 0 failed |   8.977s overall, 0.036s each | score:   1.086 |
             WSGI w/Breve: | 250x, 0 failed |   9.002s overall, 0.036s each | score:   1.089 |
            webob w/Jinja: | 250x, 0 failed |   9.156s overall, 0.037s each | score:   1.108 |
          webapp2 w/Jinja: | 250x, 0 failed |   9.211s overall, 0.037s each | score:   1.115 |
            flask w/Breve: | 250x, 0 failed |   9.217s overall, 0.037s each | score:   1.115 |
           webapp2 w/Mako: | 250x, 0 failed |   9.228s overall, 0.037s each | score:   1.117 |
            bottle w/Mako: | 250x, 0 failed |   9.244s overall, 0.037s each | score:   1.119 |
             tipfy w/Mako: | 250x, 0 failed |   9.268s overall, 0.037s each | score:   1.122 |
           webapp w/Jinja: | 250x, 0 failed |   9.269s overall, 0.037s each | score:   1.122 |
            web.py w/Mako: | 250x, 0 failed |   9.307s overall, 0.037s each | score:   1.126 |
           bottle w/Jinja: | 250x, 0 failed |   9.328s overall, 0.037s each | score:   1.129 |
             flask w/Mako: | 250x, 0 failed |   9.412s overall, 0.038s each | score:   1.139 |
             WSGI w/Jinja: | 250x, 0 failed |   9.471s overall, 0.038s each | score:   1.146 |
             webob w/Mako: | 250x, 0 failed |   9.517s overall, 0.038s each | score:   1.152 |
            webapp w/Mako: | 250x, 0 failed |   9.589s overall, 0.038s each | score:   1.160 |
           web.py w/Jinja: | 250x, 0 failed |   9.763s overall, 0.039s each | score:   1.181 |
              WSGI w/Mako: | 250x, 0 failed |   9.970s overall, 0.040s each | score:   1.207 |

Medium Overhead:

       webapp w/Templator: | 250x, 0 failed |   8.464s overall, 0.034s each | score:   1.000 |
            tipfy w/Jinja: | 250x, 0 failed |   8.470s overall, 0.034s each | score:   1.001 |
        webob w/Templator: | 250x, 0 failed |   8.511s overall, 0.034s each | score:   1.006 |
      webapp2 w/Templator: | 250x, 0 failed |   8.582s overall, 0.034s each | score:   1.014 |
        flask w/Templator: | 250x, 0 failed |   8.608s overall, 0.034s each | score:   1.017 |
          web.py w/Tenjin: | 250x, 0 failed |   8.620s overall, 0.034s each | score:   1.018 |
       bottle w/Templator: | 250x, 0 failed |   8.657s overall, 0.035s each | score:   1.023 |
          webapp w/Tenjin: | 250x, 0 failed |   8.672s overall, 0.035s each | score:   1.025 |
       web.py w/Templator: | 250x, 0 failed |   8.673s overall, 0.035s each | score:   1.025 |
         webapp2 w/Tenjin: | 250x, 0 failed |   8.689s overall, 0.035s each | score:   1.027 |
           flask w/Tenjin: | 250x, 0 failed |   8.713s overall, 0.035s each | score:   1.029 |
      webob w/TenjinCache: | 250x, 0 failed |   8.722s overall, 0.035s each | score:   1.030 |
     webapp w/TenjinCache: | 250x, 0 failed |   8.735s overall, 0.035s each | score:   1.032 |
    webapp2 w/TenjinCache: | 250x, 0 failed |   8.761s overall, 0.035s each | score:   1.035 |
     web.py w/TenjinCache: | 250x, 0 failed |   8.771s overall, 0.035s each | score:   1.036 |
      tipfy w/TenjinCache: | 250x, 0 failed |   8.773s overall, 0.035s each | score:   1.036 |
      flask w/TenjinCache: | 250x, 0 failed |   8.829s overall, 0.035s each | score:   1.043 |
        tipfy w/Templator: | 250x, 0 failed |   8.852s overall, 0.035s each | score:   1.046 |
  webapp w/SimpleTemplate: | 250x, 0 failed |   8.895s overall, 0.036s each | score:   1.051 |
           webob w/Tenjin: | 250x, 0 failed |   8.912s overall, 0.036s each | score:   1.053 |
          bottle w/Tenjin: | 250x, 0 failed |   8.919s overall, 0.036s each | score:   1.054 |
           tipfy w/Tenjin: | 250x, 0 failed |   8.930s overall, 0.036s each | score:   1.055 |
         WSGI w/Templator: | 250x, 0 failed |   8.953s overall, 0.036s each | score:   1.058 |
 webapp2 w/SimpleTemplate: | 250x, 0 failed |   8.995s overall, 0.036s each | score:   1.063 |
   webob w/SimpleTemplate: | 250x, 0 failed |   8.998s overall, 0.036s each | score:   1.063 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |   9.035s overall, 0.036s each | score:   1.067 |
       WSGI w/TenjinCache: | 250x, 0 failed |   9.081s overall, 0.036s each | score:   1.073 |
   flask w/SimpleTemplate: | 250x, 0 failed |   9.157s overall, 0.037s each | score:   1.082 |
     bottle w/TenjinCache: | 250x, 0 failed |   9.199s overall, 0.037s each | score:   1.087 |
             webob w/Mako: | 250x, 0 failed |   9.210s overall, 0.037s each | score:   1.088 |
            WSGI w/Tenjin: | 250x, 0 failed |   9.236s overall, 0.037s each | score:   1.091 |
            flask w/Jinja: | 250x, 0 failed |   9.308s overall, 0.037s each | score:   1.100 |
            web.py w/Mako: | 250x, 0 failed |   9.350s overall, 0.037s each | score:   1.105 |
          webapp w/Django: | 250x, 0 failed |   9.351s overall, 0.037s each | score:   1.105 |
           webapp w/Jinja: | 250x, 0 failed |   9.353s overall, 0.037s each | score:   1.105 |
           webob w/Django: | 250x, 0 failed |   9.363s overall, 0.037s each | score:   1.106 |
           bottle w/Jinja: | 250x, 0 failed |   9.383s overall, 0.038s each | score:   1.109 |
            bottle w/Mako: | 250x, 0 failed |   9.400s overall, 0.038s each | score:   1.111 |
  bottle w/SimpleTemplate: | 250x, 0 failed |   9.411s overall, 0.038s each | score:   1.112 |
           webapp2 w/Mako: | 250x, 0 failed |   9.427s overall, 0.038s each | score:   1.114 |
           tipfy w/Django: | 250x, 0 failed |   9.442s overall, 0.038s each | score:   1.116 |
          webapp2 w/Jinja: | 250x, 0 failed |   9.520s overall, 0.038s each | score:   1.125 |
           web.py w/Jinja: | 250x, 0 failed |   9.539s overall, 0.038s each | score:   1.127 |
          web.py w/Django: | 250x, 0 failed |   9.601s overall, 0.038s each | score:   1.134 |
         webapp2 w/Django: | 250x, 0 failed |   9.615s overall, 0.038s each | score:   1.136 |
            webob w/Jinja: | 250x, 0 failed |   9.652s overall, 0.039s each | score:   1.140 |
           flask w/Django: | 250x, 0 failed |   9.667s overall, 0.039s each | score:   1.142 |
             tipfy w/Mako: | 250x, 0 failed |   9.670s overall, 0.039s each | score:   1.142 |
            webapp w/Mako: | 250x, 0 failed |   9.673s overall, 0.039s each | score:   1.143 |
    WSGI w/SimpleTemplate: | 250x, 0 failed |   9.700s overall, 0.039s each | score:   1.146 |
             WSGI w/Jinja: | 250x, 0 failed |   9.742s overall, 0.039s each | score:   1.151 |
            WSGI w/Django: | 250x, 0 failed |   9.871s overall, 0.039s each | score:   1.166 |
          bottle w/Django: | 250x, 0 failed |   9.974s overall, 0.040s each | score:   1.178 |
              WSGI w/Mako: | 250x, 0 failed |   9.990s overall, 0.040s each | score:   1.180 |
             flask w/Mako: | 250x, 0 failed |  10.011s overall, 0.040s each | score:   1.183 |
          webapp2 w/Breve: | 250x, 0 failed |  10.519s overall, 0.042s each | score:   1.243 |
  web.py w/SimpleTemplate: | 250x, 0 failed |  10.618s overall, 0.042s each | score:   1.255 |
           web.py w/Breve: | 250x, 0 failed |  10.767s overall, 0.043s each | score:   1.272 |
           webapp w/Breve: | 250x, 0 failed |  10.786s overall, 0.043s each | score:   1.274 |
            tipfy w/Breve: | 250x, 0 failed |  10.832s overall, 0.043s each | score:   1.280 |
            flask w/Breve: | 250x, 0 failed |  10.840s overall, 0.043s each | score:   1.281 |
            webob w/Breve: | 250x, 0 failed |  10.999s overall, 0.044s each | score:   1.299 |
           bottle w/Breve: | 250x, 0 failed |  11.004s overall, 0.044s each | score:   1.300 |
             WSGI w/Breve: | 250x, 0 failed |  11.078s overall, 0.044s each | score:   1.309 |

High Overhead:

            tipfy w/Jinja: | 250x, 0 failed |   9.273s overall, 0.037s each | score:   1.000 |
           webapp w/Jinja: | 250x, 0 failed |   9.974s overall, 0.040s each | score:   1.076 |
           web.py w/Jinja: | 250x, 0 failed |  10.060s overall, 0.040s each | score:   1.085 |
          webapp2 w/Jinja: | 250x, 0 failed |  10.164s overall, 0.041s each | score:   1.096 |
           bottle w/Jinja: | 250x, 0 failed |  10.168s overall, 0.041s each | score:   1.097 |
             webob w/Mako: | 250x, 0 failed |  10.189s overall, 0.041s each | score:   1.099 |
            bottle w/Mako: | 250x, 0 failed |  10.230s overall, 0.041s each | score:   1.103 |
            webapp w/Mako: | 250x, 0 failed |  10.255s overall, 0.041s each | score:   1.106 |
           webapp2 w/Mako: | 250x, 0 failed |  10.257s overall, 0.041s each | score:   1.106 |
             tipfy w/Mako: | 250x, 0 failed |  10.260s overall, 0.041s each | score:   1.106 |
            web.py w/Mako: | 250x, 0 failed |  10.269s overall, 0.041s each | score:   1.107 |
             flask w/Mako: | 250x, 0 failed |  10.466s overall, 0.042s each | score:   1.129 |
        webob w/Templator: | 250x, 0 failed |  10.570s overall, 0.042s each | score:   1.140 |
       bottle w/Templator: | 250x, 0 failed |  10.608s overall, 0.042s each | score:   1.144 |
       web.py w/Templator: | 250x, 0 failed |  10.609s overall, 0.042s each | score:   1.144 |
      webapp2 w/Templator: | 250x, 0 failed |  10.650s overall, 0.043s each | score:   1.149 |
        tipfy w/Templator: | 250x, 0 failed |  10.783s overall, 0.043s each | score:   1.163 |
       webapp w/Templator: | 250x, 0 failed |  10.787s overall, 0.043s each | score:   1.163 |
        flask w/Templator: | 250x, 0 failed |  10.849s overall, 0.043s each | score:   1.170 |
           webob w/Tenjin: | 250x, 0 failed |  10.883s overall, 0.044s each | score:   1.174 |
           tipfy w/Tenjin: | 250x, 0 failed |  10.892s overall, 0.044s each | score:   1.175 |
           flask w/Tenjin: | 250x, 0 failed |  10.981s overall, 0.044s each | score:   1.184 |
          web.py w/Tenjin: | 250x, 0 failed |  10.995s overall, 0.044s each | score:   1.186 |
          bottle w/Tenjin: | 250x, 0 failed |  11.013s overall, 0.044s each | score:   1.188 |
     bottle w/TenjinCache: | 250x, 0 failed |  11.030s overall, 0.044s each | score:   1.190 |
      flask w/TenjinCache: | 250x, 0 failed |  11.055s overall, 0.044s each | score:   1.192 |
    webapp2 w/TenjinCache: | 250x, 0 failed |  11.082s overall, 0.044s each | score:   1.195 |
     web.py w/TenjinCache: | 250x, 0 failed |  11.116s overall, 0.044s each | score:   1.199 |
      tipfy w/TenjinCache: | 250x, 0 failed |  11.139s overall, 0.045s each | score:   1.201 |
            webob w/Jinja: | 250x, 0 failed |  11.146s overall, 0.045s each | score:   1.202 |
         webapp2 w/Tenjin: | 250x, 0 failed |  11.154s overall, 0.045s each | score:   1.203 |
          webapp w/Tenjin: | 250x, 0 failed |  11.179s overall, 0.045s each | score:   1.206 |
      webob w/TenjinCache: | 250x, 0 failed |  11.235s overall, 0.045s each | score:   1.212 |
     webapp w/TenjinCache: | 250x, 0 failed |  11.347s overall, 0.045s each | score:   1.224 |
            flask w/Jinja: | 250x, 0 failed |  14.164s overall, 0.057s each | score:   1.528 |
  web.py w/SimpleTemplate: | 250x, 0 failed |  14.352s overall, 0.057s each | score:   1.548 |
  webapp w/SimpleTemplate: | 250x, 0 failed |  14.380s overall, 0.058s each | score:   1.551 |
 webapp2 w/SimpleTemplate: | 250x, 0 failed |  14.458s overall, 0.058s each | score:   1.559 |
  bottle w/SimpleTemplate: | 250x, 0 failed |  14.485s overall, 0.058s each | score:   1.562 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |  14.489s overall, 0.058s each | score:   1.563 |
   flask w/SimpleTemplate: | 250x, 0 failed |  14.969s overall, 0.060s each | score:   1.614 |
              WSGI w/Mako: | 250x, 0 failed |  15.353s overall, 0.061s each | score:   1.656 |
   webob w/SimpleTemplate: | 250x, 0 failed |  15.422s overall, 0.062s each | score:   1.663 |
            WSGI w/Tenjin: | 250x, 0 failed |  15.503s overall, 0.062s each | score:   1.672 |
         WSGI w/Templator: | 250x, 0 failed |  15.520s overall, 0.062s each | score:   1.674 |
       WSGI w/TenjinCache: | 250x, 0 failed |  15.595s overall, 0.062s each | score:   1.682 |
             WSGI w/Jinja: | 250x, 0 failed |  16.165s overall, 0.065s each | score:   1.743 |
          webapp w/Django: | 250x, 0 failed |  18.026s overall, 0.072s each | score:   1.944 |
          bottle w/Django: | 250x, 0 failed |  18.323s overall, 0.073s each | score:   1.976 |
         webapp2 w/Django: | 250x, 0 failed |  18.414s overall, 0.074s each | score:   1.986 |
           webob w/Django: | 250x, 0 failed |  18.492s overall, 0.074s each | score:   1.994 |
           tipfy w/Django: | 250x, 0 failed |  18.506s overall, 0.074s each | score:   1.996 |
          web.py w/Django: | 250x, 0 failed |  19.595s overall, 0.078s each | score:   2.113 |
           flask w/Django: | 250x, 0 failed |  19.596s overall, 0.078s each | score:   2.113 |
    WSGI w/SimpleTemplate: | 250x, 0 failed |  19.851s overall, 0.079s each | score:   2.141 |
            WSGI w/Django: | 250x, 0 failed |  23.853s overall, 0.095s each | score:   2.572 |
           webapp w/Breve: | 250x, 0 failed |  32.844s overall, 0.131s each | score:   3.542 |
            flask w/Breve: | 250x, 0 failed |  33.422s overall, 0.134s each | score:   3.604 |
            tipfy w/Breve: | 250x, 0 failed |  33.513s overall, 0.134s each | score:   3.614 |
            webob w/Breve: | 250x, 0 failed |  34.124s overall, 0.136s each | score:   3.680 |
           web.py w/Breve: | 250x, 0 failed |  34.308s overall, 0.137s each | score:   3.700 |
           bottle w/Breve: | 250x, 0 failed |  34.383s overall, 0.138s each | score:   3.708 |
          webapp2 w/Breve: | 250x, 0 failed |  34.689s overall, 0.139s each | score:   3.741 |
             WSGI w/Breve: | 250x, 0 failed |  37.234s overall, 0.149s each | score:   4.015 |

Very High Overhead:

            tipfy w/Jinja: | 250x, 0 failed |  17.782s overall, 0.071s each | score:   1.000 |
           web.py w/Jinja: | 250x, 0 failed |  18.418s overall, 0.074s each | score:   1.036 |
           webapp w/Jinja: | 250x, 0 failed |  18.489s overall, 0.074s each | score:   1.040 |
          webapp2 w/Jinja: | 250x, 0 failed |  18.902s overall, 0.076s each | score:   1.063 |
           bottle w/Jinja: | 250x, 0 failed |  18.999s overall, 0.076s each | score:   1.068 |
            webob w/Jinja: | 250x, 0 failed |  19.019s overall, 0.076s each | score:   1.070 |
             flask w/Mako: | 250x, 0 failed |  19.782s overall, 0.079s each | score:   1.112 |
             webob w/Mako: | 250x, 0 failed |  19.986s overall, 0.080s each | score:   1.124 |
           webapp2 w/Mako: | 250x, 0 failed |  20.043s overall, 0.080s each | score:   1.127 |
            webapp w/Mako: | 250x, 0 failed |  20.383s overall, 0.082s each | score:   1.146 |
             tipfy w/Mako: | 250x, 0 failed |  20.414s overall, 0.082s each | score:   1.148 |
            bottle w/Mako: | 250x, 0 failed |  20.428s overall, 0.082s each | score:   1.149 |
            web.py w/Mako: | 250x, 0 failed |  20.994s overall, 0.084s each | score:   1.181 |
        webob w/Templator: | 250x, 0 failed |  30.853s overall, 0.123s each | score:   1.735 |
       webapp w/Templator: | 250x, 0 failed |  31.001s overall, 0.124s each | score:   1.743 |
       web.py w/Templator: | 250x, 0 failed |  31.178s overall, 0.125s each | score:   1.753 |
       bottle w/Templator: | 250x, 0 failed |  31.207s overall, 0.125s each | score:   1.755 |
        flask w/Templator: | 250x, 0 failed |  31.234s overall, 0.125s each | score:   1.756 |
        tipfy w/Templator: | 250x, 0 failed |  31.607s overall, 0.126s each | score:   1.777 |
      webapp2 w/Templator: | 250x, 0 failed |  31.678s overall, 0.127s each | score:   1.781 |
           webob w/Tenjin: | 250x, 0 failed |  34.365s overall, 0.137s each | score:   1.933 |
           tipfy w/Tenjin: | 250x, 0 failed |  34.410s overall, 0.138s each | score:   1.935 |
           flask w/Tenjin: | 250x, 0 failed |  34.449s overall, 0.138s each | score:   1.937 |
     bottle w/TenjinCache: | 250x, 0 failed |  34.532s overall, 0.138s each | score:   1.942 |
      tipfy w/TenjinCache: | 250x, 0 failed |  34.551s overall, 0.138s each | score:   1.943 |
     webapp w/TenjinCache: | 250x, 0 failed |  34.552s overall, 0.138s each | score:   1.943 |
         webapp2 w/Tenjin: | 250x, 0 failed |  34.622s overall, 0.138s each | score:   1.947 |
      flask w/TenjinCache: | 250x, 0 failed |  34.669s overall, 0.139s each | score:   1.950 |
          webapp w/Tenjin: | 250x, 0 failed |  34.735s overall, 0.139s each | score:   1.953 |
    webapp2 w/TenjinCache: | 250x, 0 failed |  34.884s overall, 0.140s each | score:   1.962 |
     web.py w/TenjinCache: | 250x, 0 failed |  34.939s overall, 0.140s each | score:   1.965 |
          bottle w/Tenjin: | 250x, 0 failed |  34.964s overall, 0.140s each | score:   1.966 |
          web.py w/Tenjin: | 250x, 0 failed |  35.041s overall, 0.140s each | score:   1.971 |
      webob w/TenjinCache: | 250x, 0 failed |  35.501s overall, 0.142s each | score:   1.996 |
            flask w/Jinja: | 250x, 0 failed |  63.612s overall, 0.254s each | score:   3.577 |
  web.py w/SimpleTemplate: | 250x, 0 failed |  68.376s overall, 0.274s each | score:   3.845 |
  webapp w/SimpleTemplate: | 250x, 0 failed |  68.784s overall, 0.275s each | score:   3.868 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |  69.553s overall, 0.278s each | score:   3.911 |
   flask w/SimpleTemplate: | 250x, 0 failed |  69.728s overall, 0.279s each | score:   3.921 |
 webapp2 w/SimpleTemplate: | 250x, 0 failed |  69.781s overall, 0.279s each | score:   3.924 |
   webob w/SimpleTemplate: | 250x, 0 failed |  70.051s overall, 0.280s each | score:   3.940 |
  bottle w/SimpleTemplate: | 250x, 0 failed |  70.541s overall, 0.282s each | score:   3.967 |
              WSGI w/Mako: | 250x, 0 failed |  73.786s overall, 0.295s each | score:   4.150 |
             WSGI w/Jinja: | 250x, 0 failed |  82.504s overall, 0.330s each | score:   4.640 |
            WSGI w/Tenjin: | 250x, 0 failed |  82.949s overall, 0.332s each | score:   4.665 |
       WSGI w/TenjinCache: | 250x, 0 failed |  83.182s overall, 0.333s each | score:   4.678 |
         WSGI w/Templator: | 250x, 0 failed |  84.371s overall, 0.337s each | score:   4.745 |
          webapp w/Django: | 250x, 0 failed | 103.542s overall, 0.414s each | score:   5.823 |
           webob w/Django: | 250x, 0 failed | 103.625s overall, 0.414s each | score:   5.828 |
           tipfy w/Django: | 250x, 0 failed | 103.984s overall, 0.416s each | score:   5.848 |
          bottle w/Django: | 250x, 0 failed | 104.424s overall, 0.418s each | score:   5.873 |
         webapp2 w/Django: | 250x, 0 failed | 105.751s overall, 0.423s each | score:   5.947 |
           flask w/Django: | 250x, 0 failed | 120.109s overall, 0.480s each | score:   6.755 |
          web.py w/Django: | 250x, 0 failed | 121.021s overall, 0.484s each | score:   6.806 |
    WSGI w/SimpleTemplate: | 250x, 0 failed | 123.487s overall, 0.494s each | score:   6.945 |
            WSGI w/Django: | 250x, 0 failed | 165.765s overall, 0.663s each | score:   9.322 |
           bottle w/Breve: | 250x, 0 failed | 282.629s overall, 1.131s each | score:  15.894 |
            webob w/Breve: | 250x, 0 failed | 283.554s overall, 1.134s each | score:  15.946 |
           web.py w/Breve: | 250x, 0 failed | 284.162s overall, 1.137s each | score:  15.981 |
            flask w/Breve: | 250x, 0 failed | 285.764s overall, 1.143s each | score:  16.071 |
            tipfy w/Breve: | 250x, 0 failed | 286.220s overall, 1.145s each | score:  16.096 |
           webapp w/Breve: | 250x, 0 failed | 286.600s overall, 1.146s each | score:  16.118 |
          webapp2 w/Breve: | 250x, 0 failed | 286.636s overall, 1.147s each | score:  16.120 |
             WSGI w/Breve: | 250x, 0 failed | 330.554s overall, 1.322s each | score:  18.589 |

Overall classification based on cumulative scores:

            tipfy w/Jinja: |  43.821 seconds overall | score:   4.005 |
           webapp w/Jinja: |  47.085 seconds overall | score:   4.342 |
          webapp2 w/Jinja: |  47.797 seconds overall | score:   4.399 |
           bottle w/Jinja: |  47.878 seconds overall | score:   4.402 |
           web.py w/Jinja: |  47.779 seconds overall | score:   4.429 |
             webob w/Mako: |  48.902 seconds overall | score:   4.463 |
           webapp2 w/Mako: |  48.956 seconds overall | score:   4.464 |
            bottle w/Mako: |  49.302 seconds overall | score:   4.481 |
             tipfy w/Mako: |  49.612 seconds overall | score:   4.519 |
            web.py w/Mako: |  49.920 seconds overall | score:   4.519 |
            webob w/Jinja: |  48.973 seconds overall | score:   4.520 |
            webapp w/Mako: |  49.900 seconds overall | score:   4.556 |
             flask w/Mako: |  49.671 seconds overall | score:   4.563 |
        webob w/Templator: |  58.318 seconds overall | score:   4.895 |
       webapp w/Templator: |  58.565 seconds overall | score:   4.913 |
       web.py w/Templator: |  58.744 seconds overall | score:   4.925 |
       bottle w/Templator: |  58.891 seconds overall | score:   4.941 |
        flask w/Templator: |  59.052 seconds overall | score:   4.955 |
        tipfy w/Templator: |  59.551 seconds overall | score:   4.992 |
      webapp2 w/Templator: |  59.617 seconds overall | score:   4.998 |
           webob w/Tenjin: |  62.458 seconds overall | score:   5.163 |
           flask w/Tenjin: |  62.560 seconds overall | score:   5.170 |
           tipfy w/Tenjin: |  62.590 seconds overall | score:   5.176 |
          web.py w/Tenjin: |  62.992 seconds overall | score:   5.184 |
    webapp2 w/TenjinCache: |  63.184 seconds overall | score:   5.216 |
          bottle w/Tenjin: |  63.262 seconds overall | score:   5.220 |
     webapp w/TenjinCache: |  63.085 seconds overall | score:   5.222 |
      tipfy w/TenjinCache: |  63.063 seconds overall | score:   5.222 |
          webapp w/Tenjin: |  63.167 seconds overall | score:   5.222 |
     web.py w/TenjinCache: |  63.287 seconds overall | score:   5.224 |
         webapp2 w/Tenjin: |  63.139 seconds overall | score:   5.226 |
      flask w/TenjinCache: |  63.369 seconds overall | score:   5.252 |
      webob w/TenjinCache: |  63.961 seconds overall | score:   5.268 |
     bottle w/TenjinCache: |  63.624 seconds overall | score:   5.291 |
            flask w/Jinja: |  95.555 seconds overall | score:   7.230 |
  webapp w/SimpleTemplate: | 100.370 seconds overall | score:   7.476 |
   tipfy w/SimpleTemplate: | 101.480 seconds overall | score:   7.558 |
 webapp2 w/SimpleTemplate: | 101.629 seconds overall | score:   7.562 |
   flask w/SimpleTemplate: | 102.289 seconds overall | score:   7.638 |
  bottle w/SimpleTemplate: | 102.844 seconds overall | score:   7.658 |
  web.py w/SimpleTemplate: | 101.858 seconds overall | score:   7.678 |
   webob w/SimpleTemplate: | 102.836 seconds overall | score:   7.678 |
              WSGI w/Mako: | 109.099 seconds overall | score:   8.192 |
            WSGI w/Tenjin: | 115.977 seconds overall | score:   8.431 |
         WSGI w/Templator: | 117.107 seconds overall | score:   8.476 |
       WSGI w/TenjinCache: | 116.834 seconds overall | score:   8.519 |
             WSGI w/Jinja: | 117.882 seconds overall | score:   8.680 |
          webapp w/Django: | 139.356 seconds overall | score:   9.893 |
           webob w/Django: | 140.017 seconds overall | score:   9.961 |
           tipfy w/Django: | 140.722 seconds overall | score:  10.023 |
          bottle w/Django: | 141.361 seconds overall | score:  10.073 |
         webapp2 w/Django: | 142.275 seconds overall | score:  10.097 |
           flask w/Django: | 157.943 seconds overall | score:  11.047 |
          web.py w/Django: | 159.121 seconds overall | score:  11.131 |
    WSGI w/SimpleTemplate: | 161.372 seconds overall | score:  11.240 |
            WSGI w/Django: | 208.025 seconds overall | score:  14.094 |
           bottle w/Breve: | 336.695 seconds overall | score:  21.953 |
           webapp w/Breve: | 338.780 seconds overall | score:  21.969 |
            webob w/Breve: | 337.294 seconds overall | score:  21.969 |
           web.py w/Breve: | 337.934 seconds overall | score:  22.005 |
            tipfy w/Breve: | 339.195 seconds overall | score:  22.035 |
            flask w/Breve: | 339.243 seconds overall | score:  22.071 |
          webapp2 w/Breve: | 340.441 seconds overall | score:  22.144 |
             WSGI w/Breve: | 387.868 seconds overall | score:  25.003 |

Templating Engines classification:

           Mako: |  455.363s overall | score:   1.000 |
          Jinja: |  496.770s overall | score:   1.091 |
      Templator: |  529.844s overall | score:   1.164 |
         Tenjin: |  556.145s overall | score:   1.221 |
    TenjinCache: |  560.408s overall | score:   1.231 |
 SimpleTemplate: |  874.678s overall | score:   1.921 |
         Django: | 1228.821s overall | score:   2.699 |
          Breve: | 2757.450s overall | score:   6.055 |

Web Application Frameworks classification:

          tipfy: |  860.035s overall | score:   1.000 |
         webapp: |  860.308s overall | score:   1.000 |
          webob: |  862.759s overall | score:   1.003 |
         Bottle: |  863.857s overall | score:   1.004 |
        webapp2: |  867.038s overall | score:   1.008 |
         wep.py: |  881.636s overall | score:   1.025 |
          Flask: |  929.682s overall | score:   1.081 |
           WSGI: | 1334.164s overall | score:   1.551 |

Batteries Included Frameworks comparison:

            tipfy w/Jinja: |  43.821s overall | score:   1.000 |
       web.py w/Templator: |  58.744s overall | score:   1.341 |
            flask w/Jinja: |  95.555s overall | score:   2.181 |
  bottle w/SimpleTemplate: | 102.844s overall | score:   2.347 |
          webapp w/Django: | 139.356s overall | score:   3.180 |

Straight away, you may notice that the overall classification order is not entirely coherent with the previous benchmarks run, but the Framework and Templating Engine comparisons stays in line with the previous ordering. I would really like to know why WSGI scores so badly… the problem must be in my implementation, because its technology is the foundation for all the others. What did I do to make it that bad? What about Tenjin with cache? This one is explicable… the benchmark is built to engender the more possible unique queries, so that is why the caching suffer and becomes more of a wasted cycle than a clever speed trick.

Technicolor Terminal

10 Sep 2010

What about getting colouring in your SnowLeopard Terminal? You looked around the web and found sparse informations on setting your Terminal with TerminalColours, here is the place to look to get your rainbow colour Terminal going.

First, you need to install SIMBL 64bit. This allows to plug the colours into the Terminal core.

Then, download TerminalColours and place it either in /Library/Application\ Support/SIMBL/Plugins/ (for all users) or in ~/Library/Application\ Support/SIMBL/Plugins/.

Activate colouring for your bash profile, in the shell type:

printf "\n%s\n" 'export CLICOLOR=1' >> ~/.bash_profile

Restart the Terminal and visit Terminal→Preferences, under Settings/Text, make sure Display ANSI colors is checked and click the More… button to set your colour palette. Voilà!

As an extra, you can now alias your popular shell commands so that they default to show up dressed up in marvellous pigments. For example, for tree, you can set it to colouring by typing:

printf "\n%s\n" "alias tree='tree -C'" >> ~/.bash_profile

Visit each of your commands man page to get more information on which flag enables their respective colouring.

AppEngine's Python Templates Benchmark Conclusion

27 Aug 2010

After the incremental improvements made to the benchmarks in the 3 last posts (1, 2, 3), what more can we do? First, not all possible case combination of Frameworks versus Engines have been tested. Django Templates, SimpleTemplate and Templator were still tested only in their default configuration. Implementing those new combinations will give us symmetry of templates and frameworks, useful later for cross-comparisons benchmarks. We now have:

5 web application frameworks:

Crossed with 7 templating engine:

For a total of 35 possible combinations.

This renewed benchmark will compare these 35 combinations, with a new focus on classifying the frameworks and the engines separately. The same test algorithm will be used in this benchmark, implementing a short controller action coupled with a long view compilation. This type of implementation allow us to pinpoint:

  • in web application frameworks:
    • a good figure confirms the controller to be unobtrusive
    • a bad figure reveals a controller’s heavy processing overhead
  • in templating engines:
    • a good result shows the templates have tolerance for pressure
    • a bad result let us believe the engine can’t withstand heavy load

The last comparison step will involve a simple classification of default configurations, as many developers will more likely choose an integrated batteries included solution, rather than a DIY. This comparison will highlight the best all in one in terms of performance. These default configurations are:

  • Bottle with SimpleTemplate
  • Flask with Jinja
  • tipfy with Jinja
  • webapp with Django
  • web.py with Templator

Lets again proceed with the benchmarks. This time, I will skip pasting all new code implementations and will instead redirect you to the benchmark repository, where you can find all about the implementations details. The execution was also dropped from 1000 loop counts to 250, as it seems to be enough to represent the average performance of the systems.

Low Overhead:

       webapp w/Templator: | 250x, 0 failed |   7.316s overall, 0.029s each | score:  1.000 |
  webapp w/SimpleTemplate: | 250x, 0 failed |   7.386s overall, 0.030s each | score:  1.010 |
            tipfy w/Jinja: | 250x, 0 failed |   7.386s overall, 0.030s each | score:  1.010 |
          bottle w/Tenjin: | 250x, 0 failed |   7.388s overall, 0.030s each | score:  1.010 |
          webapp w/Tenjin: | 250x, 0 failed |   7.391s overall, 0.030s each | score:  1.010 |
        tipfy w/Templator: | 250x, 0 failed |   7.392s overall, 0.030s each | score:  1.010 |
          web.py w/Tenjin: | 250x, 0 failed |   7.401s overall, 0.030s each | score:  1.012 |
       web.py w/Templator: | 250x, 0 failed |   7.413s overall, 0.030s each | score:  1.013 |
            flask w/Jinja: | 250x, 0 failed |   7.430s overall, 0.030s each | score:  1.016 |
  bottle w/SimpleTemplate: | 250x, 0 failed |   7.435s overall, 0.030s each | score:  1.016 |
  web.py w/SimpleTemplate: | 250x, 0 failed |   7.450s overall, 0.030s each | score:  1.018 |
        flask w/Templator: | 250x, 0 failed |   7.460s overall, 0.030s each | score:  1.020 |
           tipfy w/Tenjin: | 250x, 0 failed |   7.467s overall, 0.030s each | score:  1.021 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |   7.474s overall, 0.030s each | score:  1.022 |
   flask w/SimpleTemplate: | 250x, 0 failed |   7.489s overall, 0.030s each | score:  1.024 |
       bottle w/Templator: | 250x, 0 failed |   7.500s overall, 0.030s each | score:  1.025 |
          webapp w/Django: | 250x, 0 failed |   7.533s overall, 0.030s each | score:  1.030 |
          bottle w/Django: | 250x, 0 failed |   7.570s overall, 0.030s each | score:  1.035 |
           flask w/Django: | 250x, 0 failed |   7.588s overall, 0.030s each | score:  1.037 |
          web.py w/Django: | 250x, 0 failed |   7.606s overall, 0.030s each | score:  1.040 |
            flask w/Breve: | 250x, 0 failed |   7.616s overall, 0.030s each | score:  1.041 |
           tipfy w/Django: | 250x, 0 failed |   7.620s overall, 0.030s each | score:  1.041 |
           bottle w/Breve: | 250x, 0 failed |   7.708s overall, 0.031s each | score:  1.054 |
           flask w/Tenjin: | 250x, 0 failed |   7.814s overall, 0.031s each | score:  1.068 |
           webapp w/Breve: | 250x, 0 failed |   7.841s overall, 0.031s each | score:  1.072 |
           webapp w/Jinja: | 250x, 0 failed |   8.137s overall, 0.033s each | score:  1.112 |
            webapp w/Mako: | 250x, 0 failed |   8.156s overall, 0.033s each | score:  1.115 |
            tipfy w/Breve: | 250x, 0 failed |   8.177s overall, 0.033s each | score:  1.118 |
           bottle w/Jinja: | 250x, 0 failed |   8.187s overall, 0.033s each | score:  1.119 |
             tipfy w/Mako: | 250x, 0 failed |   8.227s overall, 0.033s each | score:  1.124 |
            bottle w/Mako: | 250x, 0 failed |   8.256s overall, 0.033s each | score:  1.128 |
            web.py w/Mako: | 250x, 0 failed |   8.290s overall, 0.033s each | score:  1.133 |
           web.py w/Jinja: | 250x, 0 failed |   8.375s overall, 0.033s each | score:  1.145 |
           web.py w/Breve: | 250x, 0 failed |   8.477s overall, 0.034s each | score:  1.159 |
             flask w/Mako: | 250x, 0 failed |   8.679s overall, 0.035s each | score:  1.186 |

Medium Overhead

       webapp w/Templator: | 250x, 0 failed |   7.508s overall, 0.030s each | score:  1.000 |
          webapp w/Tenjin: | 250x, 0 failed |   7.546s overall, 0.030s each | score:  1.005 |
        tipfy w/Templator: | 250x, 0 failed |   7.603s overall, 0.030s each | score:  1.013 |
          bottle w/Tenjin: | 250x, 0 failed |   7.642s overall, 0.031s each | score:  1.018 |
       bottle w/Templator: | 250x, 0 failed |   7.649s overall, 0.031s each | score:  1.019 |
           flask w/Tenjin: | 250x, 0 failed |   7.673s overall, 0.031s each | score:  1.022 |
        flask w/Templator: | 250x, 0 failed |   7.676s overall, 0.031s each | score:  1.022 |
            tipfy w/Jinja: | 250x, 0 failed |   7.727s overall, 0.031s each | score:  1.029 |
          web.py w/Tenjin: | 250x, 0 failed |   7.728s overall, 0.031s each | score:  1.029 |
           tipfy w/Tenjin: | 250x, 0 failed |   7.736s overall, 0.031s each | score:  1.030 |
       web.py w/Templator: | 250x, 0 failed |   7.861s overall, 0.031s each | score:  1.047 |
  webapp w/SimpleTemplate: | 250x, 0 failed |   7.905s overall, 0.032s each | score:  1.053 |
            flask w/Jinja: | 250x, 0 failed |   7.928s overall, 0.032s each | score:  1.056 |
   flask w/SimpleTemplate: | 250x, 0 failed |   7.990s overall, 0.032s each | score:  1.064 |
  web.py w/SimpleTemplate: | 250x, 0 failed |   7.997s overall, 0.032s each | score:  1.065 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |   8.023s overall, 0.032s each | score:  1.069 |
           web.py w/Jinja: | 250x, 0 failed |   8.253s overall, 0.033s each | score:  1.099 |
  bottle w/SimpleTemplate: | 250x, 0 failed |   8.273s overall, 0.033s each | score:  1.102 |
             flask w/Mako: | 250x, 0 failed |   8.296s overall, 0.033s each | score:  1.105 |
            webapp w/Mako: | 250x, 0 failed |   8.299s overall, 0.033s each | score:  1.105 |
          webapp w/Django: | 250x, 0 failed |   8.310s overall, 0.033s each | score:  1.107 |
           bottle w/Jinja: | 250x, 0 failed |   8.314s overall, 0.033s each | score:  1.107 |
             tipfy w/Mako: | 250x, 0 failed |   8.322s overall, 0.033s each | score:  1.108 |
           webapp w/Jinja: | 250x, 0 failed |   8.322s overall, 0.033s each | score:  1.108 |
            web.py w/Mako: | 250x, 0 failed |   8.341s overall, 0.033s each | score:  1.111 |
            bottle w/Mako: | 250x, 0 failed |   8.358s overall, 0.033s each | score:  1.113 |
          bottle w/Django: | 250x, 0 failed |   8.450s overall, 0.034s each | score:  1.125 |
           tipfy w/Django: | 250x, 0 failed |   8.471s overall, 0.034s each | score:  1.128 |
          web.py w/Django: | 250x, 0 failed |   8.572s overall, 0.034s each | score:  1.142 |
           flask w/Django: | 250x, 0 failed |   8.603s overall, 0.034s each | score:  1.146 |
            tipfy w/Breve: | 250x, 0 failed |   9.475s overall, 0.038s each | score:  1.262 |
           webapp w/Breve: | 250x, 0 failed |   9.687s overall, 0.039s each | score:  1.290 |
           bottle w/Breve: | 250x, 0 failed |   9.714s overall, 0.039s each | score:  1.294 |
            flask w/Breve: | 250x, 0 failed |   9.729s overall, 0.039s each | score:  1.296 |
           web.py w/Breve: | 250x, 0 failed |  10.434s overall, 0.042s each | score:  1.390 |

High Overhead

            tipfy w/Jinja: | 250x, 0 failed |   8.306s overall, 0.033s each | score:  1.000 |
             flask w/Mako: | 250x, 0 failed |   9.181s overall, 0.037s each | score:  1.105 |
            bottle w/Mako: | 250x, 0 failed |   9.213s overall, 0.037s each | score:  1.109 |
            webapp w/Mako: | 250x, 0 failed |   9.216s overall, 0.037s each | score:  1.109 |
             tipfy w/Mako: | 250x, 0 failed |   9.277s overall, 0.037s each | score:  1.117 |
           web.py w/Jinja: | 250x, 0 failed |   9.393s overall, 0.038s each | score:  1.131 |
            web.py w/Mako: | 250x, 0 failed |   9.478s overall, 0.038s each | score:  1.141 |
           bottle w/Jinja: | 250x, 0 failed |   9.505s overall, 0.038s each | score:  1.144 |
       webapp w/Templator: | 250x, 0 failed |   9.513s overall, 0.038s each | score:  1.145 |
       bottle w/Templator: | 250x, 0 failed |   9.562s overall, 0.038s each | score:  1.151 |
        flask w/Templator: | 250x, 0 failed |   9.691s overall, 0.039s each | score:  1.167 |
          webapp w/Tenjin: | 250x, 0 failed |   9.871s overall, 0.039s each | score:  1.188 |
        tipfy w/Templator: | 250x, 0 failed |   9.886s overall, 0.040s each | score:  1.190 |
          bottle w/Tenjin: | 250x, 0 failed |   9.930s overall, 0.040s each | score:  1.196 |
       web.py w/Templator: | 250x, 0 failed |   9.997s overall, 0.040s each | score:  1.204 |
          web.py w/Tenjin: | 250x, 0 failed |  10.015s overall, 0.040s each | score:  1.206 |
           tipfy w/Tenjin: | 250x, 0 failed |  10.018s overall, 0.040s each | score:  1.206 |
           flask w/Tenjin: | 250x, 0 failed |  10.229s overall, 0.041s each | score:  1.231 |
           webapp w/Jinja: | 250x, 0 failed |  10.254s overall, 0.041s each | score:  1.234 |
            flask w/Jinja: | 250x, 0 failed |  12.856s overall, 0.051s each | score:  1.548 |
  webapp w/SimpleTemplate: | 250x, 0 failed |  13.352s overall, 0.053s each | score:  1.607 |
  bottle w/SimpleTemplate: | 250x, 0 failed |  13.426s overall, 0.054s each | score:  1.616 |
   flask w/SimpleTemplate: | 250x, 0 failed |  13.434s overall, 0.054s each | score:  1.617 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |  13.454s overall, 0.054s each | score:  1.620 |
  web.py w/SimpleTemplate: | 250x, 0 failed |  13.959s overall, 0.056s each | score:  1.680 |
          bottle w/Django: | 250x, 0 failed |  16.898s overall, 0.068s each | score:  2.034 |
          webapp w/Django: | 250x, 0 failed |  16.973s overall, 0.068s each | score:  2.043 |
           tipfy w/Django: | 250x, 0 failed |  17.392s overall, 0.070s each | score:  2.094 |
          web.py w/Django: | 250x, 0 failed |  18.585s overall, 0.074s each | score:  2.237 |
           flask w/Django: | 250x, 0 failed |  18.731s overall, 0.075s each | score:  2.255 |
           webapp w/Breve: | 250x, 0 failed |  30.264s overall, 0.121s each | score:  3.644 |
           web.py w/Breve: | 250x, 0 failed |  30.776s overall, 0.123s each | score:  3.705 |
            tipfy w/Breve: | 250x, 0 failed |  31.302s overall, 0.125s each | score:  3.768 |
            flask w/Breve: | 250x, 0 failed |  31.461s overall, 0.126s each | score:  3.788 |
           bottle w/Breve: | 250x, 0 failed |  31.860s overall, 0.127s each | score:  3.836 |

Very High Overhead

            tipfy w/Jinja: | 250x, 0 failed |  16.239s overall, 0.065s each | score:  1.000 |
           webapp w/Jinja: | 250x, 0 failed |  17.114s overall, 0.068s each | score:  1.054 |
           web.py w/Jinja: | 250x, 0 failed |  17.162s overall, 0.069s each | score:  1.057 |
           bottle w/Jinja: | 250x, 0 failed |  17.356s overall, 0.069s each | score:  1.069 |
            bottle w/Mako: | 250x, 0 failed |  18.707s overall, 0.075s each | score:  1.152 |
            web.py w/Mako: | 250x, 0 failed |  18.845s overall, 0.075s each | score:  1.160 |
             tipfy w/Mako: | 250x, 0 failed |  19.193s overall, 0.077s each | score:  1.182 |
            webapp w/Mako: | 250x, 0 failed |  19.392s overall, 0.078s each | score:  1.194 |
             flask w/Mako: | 250x, 0 failed |  19.393s overall, 0.078s each | score:  1.194 |
       bottle w/Templator: | 250x, 0 failed |  29.569s overall, 0.118s each | score:  1.821 |
       webapp w/Templator: | 250x, 0 failed |  29.582s overall, 0.118s each | score:  1.822 |
        tipfy w/Templator: | 250x, 0 failed |  29.795s overall, 0.119s each | score:  1.835 |
        flask w/Templator: | 250x, 0 failed |  30.181s overall, 0.121s each | score:  1.859 |
       web.py w/Templator: | 250x, 0 failed |  30.306s overall, 0.121s each | score:  1.866 |
          bottle w/Tenjin: | 250x, 0 failed |  32.968s overall, 0.132s each | score:  2.030 |
          webapp w/Tenjin: | 250x, 0 failed |  33.843s overall, 0.135s each | score:  2.084 |
           tipfy w/Tenjin: | 250x, 0 failed |  33.910s overall, 0.136s each | score:  2.088 |
          web.py w/Tenjin: | 250x, 0 failed |  34.397s overall, 0.138s each | score:  2.118 |
           flask w/Tenjin: | 250x, 0 failed |  34.558s overall, 0.138s each | score:  2.128 |
            flask w/Jinja: | 250x, 0 failed |  62.129s overall, 0.249s each | score:  3.826 |
  webapp w/SimpleTemplate: | 250x, 0 failed |  67.637s overall, 0.271s each | score:  4.165 |
  web.py w/SimpleTemplate: | 250x, 0 failed |  67.807s overall, 0.271s each | score:  4.176 |
   flask w/SimpleTemplate: | 250x, 0 failed |  67.834s overall, 0.271s each | score:  4.177 |
   tipfy w/SimpleTemplate: | 250x, 0 failed |  68.286s overall, 0.273s each | score:  4.205 |
  bottle w/SimpleTemplate: | 250x, 0 failed |  68.810s overall, 0.275s each | score:  4.237 |
          bottle w/Django: | 250x, 0 failed | 102.666s overall, 0.411s each | score:  6.322 |
          webapp w/Django: | 250x, 0 failed | 103.002s overall, 0.412s each | score:  6.343 |
           tipfy w/Django: | 250x, 0 failed | 103.221s overall, 0.413s each | score:  6.356 |
          web.py w/Django: | 250x, 0 failed | 119.719s overall, 0.479s each | score:  7.372 |
           flask w/Django: | 250x, 0 failed | 119.746s overall, 0.479s each | score:  7.374 |
            tipfy w/Breve: | 250x, 0 failed | 253.913s overall, 1.016s each | score: 15.636 |
           bottle w/Breve: | 250x, 0 failed | 254.588s overall, 1.018s each | score: 15.678 |
           web.py w/Breve: | 250x, 0 failed | 254.943s overall, 1.020s each | score: 15.700 |
           webapp w/Breve: | 250x, 0 failed | 255.114s overall, 1.020s each | score: 15.710 |
            flask w/Breve: | 250x, 0 failed | 256.383s overall, 1.026s each | score: 15.788 |

Overall classification based on cumulative scores:

            tipfy w/Jinja: |  39.658 seconds overall | score:   4.039 |
           web.py w/Jinja: |  43.183 seconds overall | score:   4.432 |
           bottle w/Jinja: |  43.362 seconds overall | score:   4.439 |
            bottle w/Mako: |  44.533 seconds overall | score:   4.503 |
           webapp w/Jinja: |  43.827 seconds overall | score:   4.509 |
            webapp w/Mako: |  45.063 seconds overall | score:   4.524 |
             tipfy w/Mako: |  45.018 seconds overall | score:   4.532 |
            web.py w/Mako: |  44.954 seconds overall | score:   4.546 |
             flask w/Mako: |  45.549 seconds overall | score:   4.591 |
       webapp w/Templator: |  53.918 seconds overall | score:   4.967 |
       bottle w/Templator: |  54.280 seconds overall | score:   5.016 |
        tipfy w/Templator: |  54.676 seconds overall | score:   5.048 |
        flask w/Templator: |  55.007 seconds overall | score:   5.067 |
       web.py w/Templator: |  55.577 seconds overall | score:   5.130 |
          bottle w/Tenjin: |  57.928 seconds overall | score:   5.253 |
          webapp w/Tenjin: |  58.651 seconds overall | score:   5.288 |
           tipfy w/Tenjin: |  59.130 seconds overall | score:   5.345 |
          web.py w/Tenjin: |  59.540 seconds overall | score:   5.365 |
           flask w/Tenjin: |  60.274 seconds overall | score:   5.450 |
            flask w/Jinja: |  90.343 seconds overall | score:   7.445 |
  webapp w/SimpleTemplate: |  96.280 seconds overall | score:   7.835 |
   flask w/SimpleTemplate: |  96.746 seconds overall | score:   7.882 |
   tipfy w/SimpleTemplate: |  97.237 seconds overall | score:   7.915 |
  web.py w/SimpleTemplate: |  97.213 seconds overall | score:   7.940 |
  bottle w/SimpleTemplate: |  97.945 seconds overall | score:   7.972 |
          bottle w/Django: | 135.583 seconds overall | score:  10.517 |
          webapp w/Django: | 135.819 seconds overall | score:  10.523 |
           tipfy w/Django: | 136.704 seconds overall | score:  10.620 |
          web.py w/Django: | 154.482 seconds overall | score:  11.791 |
           flask w/Django: | 154.667 seconds overall | score:  11.812 |
           webapp w/Breve: | 302.906 seconds overall | score:  21.716 |
            tipfy w/Breve: | 302.867 seconds overall | score:  21.784 |
           bottle w/Breve: | 303.870 seconds overall | score:  21.861 |
            flask w/Breve: | 305.190 seconds overall | score:  21.913 |
           web.py w/Breve: | 304.630 seconds overall | score:  21.953 |

Templating Engines classification:

           Mako: |  225.118s overall | score:   1.000 |
          Jinja: |  260.373s overall | score:   1.157 |
      Templator: |  273.459s overall | score:   1.215 |
         Tenjin: |  295.523s overall | score:   1.313 |
 SimpleTemplate: |  485.420s overall | score:   2.156 |
         Django: |  717.255s overall | score:   3.186 |
          Breve: | 1519.463s overall | score:   6.750 |

Web Application Frameworks classification:

          tipfy: | 735.291s overall | score:   1.000 |
         webapp: | 736.464s overall | score:   1.002 |
         Bottle: | 737.502s overall | score:   1.003 |
         wep.py: | 759.578s overall | score:   1.033 |
          Flask: | 807.776s overall | score:   1.099 |

Batteries Included Frameworks comparison:

            tipfy w/Jinja: |  39.658s overall | score:   1.000 |
       web.py w/Templator: |  55.577s overall | score:   1.401 |
            flask w/Jinja: |  90.343s overall | score:   2.278 |
  bottle w/SimpleTemplate: |  97.945s overall | score:   2.470 |
          webapp w/Django: | 135.819s overall | score:   3.425 |

What can we see in there? First, tipfy w/Jinja rocks! It is made for AppEngine, and has the tweeks to make a difference. If you look at it a bit more, you can see that tipfy’s performance in general wins big. Second, as a Templating engine in general, mako rocks! Its speed ranking validates its philosophical position ‘…your templates can handle it !’. Templator and Tenjin clearly stay better than others all along and surpass all, even Mako and Jinja, in simple scenarios. Stepping outside the views, webapp, pre-installed on the AppEngine, shows us it knows its ways around and step second, preceding Bottle which by many standards, might be considered the real connoisseur framework. In term of default configurations, the benchmarks shows tipfy and web.py to have some kick, but the rest would be better coupled with one of the other engines, as their built-in engines seems to be disadvantageous after all.

All in all, we can say every framework and engine positively behaved and proved to be apt at delivering the job at hand. In other words, these are all Class A projects deserving attention and respect. Your end verdict shouldn’t be so much influenced by these performance figures after all. You need to also like them! As inspiration is likely to be the primary tool bringing your projects to a successful conclusion.

AppEngine's Python templates Benchmark sequel3

26 Aug 2010

So after the 2 last articles(1, 2) on Python’s templating engines, here comes the best part. I gave Tenjin and Brevé a try. Tenjin has a reputation to be the fastest engine around and Brevé is interesting being a corner case engine with a refreshing templating philosophy. Adding 2 more engines made the unsuspected happen: performance positions started to polarize, yielding clearer conclusions from the results than previous benchmarks.

Here are, for completeness sake, the new contenders implementations:

webapp with Tenjin:

import os, tenjin, tenjin.gae
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from tenjin.helpers import *

tenjin.gae.init()

class MainHandler(webapp.RequestHandler):
    def get(self,numA,numB):
        context = { 'numA' : numA, 'numB' : numB}
        self.response.out.write(tenjin.Engine(path=[
          os.path.join(os.path.dirname(__file__),"templates")]
          ).render('gTenjinTestTemplate.pyhtml',context))

application = webapp.WSGIApplication(
  [(r'/gtenjin/(.*)/(.*)', MainHandler)],debug=False)

def main():
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()
<html>
<body>
	<table>
	<?py for i in range(int(numA)): ?>
	<?py    rez = int(i) * int(numB) ?>
	    <tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
	<?py #endfor ?>
	</table>
</body>
</html>

webapp with Breve:

import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from breve import Template
from breve.tags.html import tags

class MainHandler(webapp.RequestHandler):
    def get(self,numA,numB):
        bvars = dict ( numA = numA, numB = numB )
        self.response.out.write(
          Template(tags,root=os.path.dirname(__file__)
        ).render('gBreveTestTemplate',bvars))

application = webapp.WSGIApplication(
  [(r'/gbreve/(.*)/(.*)', MainHandler)],debug=False)

def main():
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()
html [
    body [
        table [
          [ tr [
              td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
            ] for i in range(int(numA)) ]
        ]
    ]
]

Bottle with Tenjin

import os, bottle, tenjin, tenjin.gae
from bottle import get
from google.appengine.ext.webapp import util
from tenjin.helpers import *

bottle.debug(False)
app = bottle.default_app()
tenjin.gae.init()

@get('/bottletenjin/:numA/:numB')
def index(numA,numB):
  context = { 'numA' : numA, 'numB' : numB}
  return tenjin.Engine(
    path=[os.path.join(os.path.dirname(__file__),"templates")]
    ).render('bottleTenjinTestTemplate.pyhtml',context)

def main():
  util.run_wsgi_app(app)

if __name__ == "__main__":
  main()
<html>
<body>
	<table>
	<?py for i in range(int(numA)): ?>
	<?py    rez = int(i) * int(numB) ?>
	    <tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
	<?py #endfor ?>
	</table>
</body>
</html>

Bottle with Breve:

import os, bottle
from bottle import get
from google.appengine.ext.webapp import util
from breve import Template
from breve.tags.html import tags

bottle.debug(False)
app = bottle.default_app()

@get('/bottlebreve/:numA/:numB')
def index(numA,numB):
  bvars = dict ( numA = numA, numB = numB )
  return Template(tags,root=os.path.dirname(__file__)
    ).render('bottleBreveTestTemplate',bvars)

def main():
  util.run_wsgi_app(app)

if __name__ == "__main__":
  main()
html [
    body [
        table [
          [ tr [
              td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
            ] for i in range(int(numA)) ]
        ]
    ]
]

flask with Tenjin:

import os, tenjin, tenjin.gae
from google.appengine.ext.webapp import util
from flask import Flask
from tenjin.helpers import *

tenjin.gae.init()
app = Flask(__name__)

@app.route("/flasktenjin/<numA>/<numB>")
def test(numA,numB):
    context = { 'numA' : numA, 'numB' : numB}
    return tenjin.Engine(
      path=[os.path.join(os.path.dirname(__file__),"templates")]
      ).render('flaskTenjinTestTemplate.pyhtml',context)
    
def main():
    util.run_wsgi_app(app)

if __name__ == "__main__":
    main()
<html>
<body>
	<table>
	<?py for i in range(int(numA)): ?>
	<?py    rez = int(i) * int(numB) ?>
	    <tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
	<?py #endfor ?>
	</table>
</body>
</html>

flask with Breve:

import os
from google.appengine.ext.webapp import util
from flask import Flask
from breve import Template
from breve.tags.html import tags

app = Flask(__name__)

@app.route("/flaskbreve/<numA>/<numB>")
def test(numA,numB):
    bvars = dict ( numA = numA, numB = numB )
    return Template(tags,root=os.path.dirname(__file__)
      ).render('flaskBreveTestTemplate',bvars)
    
def main():
    util.run_wsgi_app(app)

if __name__ == "__main__":
    main()
html [
    body [
        table [
          [ tr [
              td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
            ] for i in range(int(numA)) ]
        ]
    ]
]

tipfy with Tenjin:

import os, tipfy, tenjin, tenjin.gae
from tipfy import RequestHandler, Response, Rule
from tenjin.helpers import *

tenjin.gae.init()

class TipfyTenjinBenchmarkHandler(RequestHandler):
    def get(self,numA,numB):
        context = { 'numA' : numA, 'numB' : numB}
        return Response(tenjin.Engine(
          path=[os.path.join(os.path.dirname(__file__),"templates")]
          ).render('tipfyTenjinTestTemplate.pyhtml',context))

app = tipfy.Tipfy(rules=[
  Rule('/tipfytenjin/<int:numA>/<int:numB>',
  endpoint='tipfy',
  handler=TipfyTenjinBenchmarkHandler)
])

def main():
    app.run()
    
if __name__ == '__main__':
    main()
<html>
<body>
	<table>
	<?py for i in range(int(numA)): ?>
	<?py    rez = int(i) * int(numB) ?>
	    <tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
	<?py #endfor ?>
	</table>
</body>
</html>

tipfy with Breve:

import os, tipfy
from tipfy import RequestHandler, Response, Rule
from breve import Template
from breve.tags.html import tags

class TipfyMakoBenchmarkHandler(RequestHandler):
    def get(self,numA,numB):
        bvars = dict ( numA = numA, numB = numB )
        return Response(
          Template(tags,root=os.path.dirname(__file__)
            ).render('tipfyBreveTestTemplate',bvars))

app = tipfy.Tipfy(rules=[
  Rule('/tipfybreve/<int:numA>/<int:numB>',
  endpoint='tipfy',
  handler=TipfyMakoBenchmarkHandler)
])

def main():
    app.run()
    
if __name__ == '__main__':
    main()
html [
    body [
        table [
          [ tr [
              td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
            ] for i in range(int(numA)) ]
        ]
    ]
]

web.py with Tenjin:

import os, web, tenjin, tenjin.gae
from google.appengine.ext.webapp import util
from tenjin.helpers import *

tenjin.gae.init()
web.config.debug = False
urls = (
	'/webpytenjin/(.*)/(.*)',	'index'
)
app = web.application(urls, globals())

class index:
  def GET(self,numA,numB):
    context = { 'numA' : numA, 'numB' : numB}
    return tenjin.Engine(path=[
    os.path.join(os.path.dirname(__file__),"templates")]
      ).render('webpyTenjinTestTemplate.pyhtml',context)
    
def main():
  util.run_wsgi_app(app.wsgifunc())

if __name__ == "__main__":
  main()
<html>
<body>
	<table>
	<?py for i in range(int(numA)): ?>
	<?py    rez = int(i) * int(numB) ?>
	    <tr><td>${i}</td><td>x ${numB}</td><td>= ${rez}</td></tr>
	<?py #endfor ?>
	</table>
</body>
</html>

web.py with Breve:

import os
from google.appengine.ext.webapp import util
import web
from breve import Template
from breve.tags.html import tags

web.config.debug = False
urls = (
	'/webpybreve/(.*)/(.*)',	'index'
)
app = web.application(urls, globals())

class index:
  def GET(self,numA,numB):
    bvars = dict ( numA = numA, numB = numB )
    return Template(tags,root=os.path.dirname(__file__)
      ).render('webpyBreveTestTemplate',bvars)

def main():
  util.run_wsgi_app(app.wsgifunc())

if __name__ == "__main__":
  main()
html [
    body [
        table [
          [ tr [
              td [i], td ["x ",numB], td ["= ", (int(i) * int(numB))]
            ] for i in range(int(numA)) ]
        ]
    ]
]

And here is where the benchmarking happens:

Overhead 10:
 webapp w/Tenjin: | 250 total, 0 failed |   7.018s overall, 0.028s each | score: 1.000 |
          web.py: | 250 total, 0 failed |   7.062s overall, 0.028s each | score: 1.006 |
  flask w/Tenjin: | 250 total, 0 failed |   7.072s overall, 0.028s each | score: 1.008 |
   tipfy w/Jinja: | 250 total, 0 failed |   7.074s overall, 0.028s each | score: 1.008 |
  tipfy w/Tenjin: | 250 total, 0 failed |   7.088s overall, 0.028s each | score: 1.010 |
          bottle: | 250 total, 0 failed |   7.102s overall, 0.028s each | score: 1.012 |
 web.py w/Tenjin: | 250 total, 0 failed |   7.104s overall, 0.028s each | score: 1.012 |
 bottle w/Tenjin: | 250 total, 0 failed |   7.138s overall, 0.029s each | score: 1.017 |
 webapp w/Django: | 250 total, 0 failed |   7.162s overall, 0.029s each | score: 1.020 |
   flask w/Jinja: | 250 total, 0 failed |   7.207s overall, 0.029s each | score: 1.027 |
   flask w/Breve: | 250 total, 0 failed |   7.342s overall, 0.029s each | score: 1.046 |
  web.py w/Breve: | 250 total, 0 failed |   7.352s overall, 0.029s each | score: 1.048 |
  bottle w/Breve: | 250 total, 0 failed |   7.416s overall, 0.030s each | score: 1.057 |
  webapp w/Breve: | 250 total, 0 failed |   7.616s overall, 0.030s each | score: 1.085 |
   tipfy w/Breve: | 250 total, 0 failed |   7.632s overall, 0.031s each | score: 1.087 |
   webapp w/Mako: | 250 total, 0 failed |   7.849s overall, 0.031s each | score: 1.118 |
  webapp w/Jinja: | 250 total, 0 failed |   7.937s overall, 0.032s each | score: 1.131 |
  web.py w/Jinja: | 250 total, 0 failed |   8.011s overall, 0.032s each | score: 1.141 |
  bottle w/Jinja: | 250 total, 0 failed |   8.064s overall, 0.032s each | score: 1.149 |
   bottle w/Mako: | 250 total, 0 failed |   8.252s overall, 0.033s each | score: 1.176 |
    flask w/Mako: | 250 total, 0 failed |   8.253s overall, 0.033s each | score: 1.176 |
   web.py w/Mako: | 250 total, 0 failed |   8.277s overall, 0.033s each | score: 1.179 |
    tipfy w/Mako: | 250 total, 0 failed |   8.320s overall, 0.033s each | score: 1.185 |
                               
                               
Overhead 100:                  
   tipfy w/Jinja: | 250 total, 0 failed |   7.059s overall, 0.028s each | score: 1.000 |
          web.py: | 250 total, 0 failed |   7.250s overall, 0.029s each | score: 1.027 |
 bottle w/Tenjin: | 250 total, 0 failed |   7.366s overall, 0.029s each | score: 1.044 |
 web.py w/Tenjin: | 250 total, 0 failed |   7.384s overall, 0.030s each | score: 1.046 |
 webapp w/Tenjin: | 250 total, 0 failed |   7.521s overall, 0.030s each | score: 1.065 |
   flask w/Jinja: | 250 total, 0 failed |   7.635s overall, 0.031s each | score: 1.082 |
  flask w/Tenjin: | 250 total, 0 failed |   7.692s overall, 0.031s each | score: 1.090 |
          bottle: | 250 total, 0 failed |   7.702s overall, 0.031s each | score: 1.091 |
  tipfy w/Tenjin: | 250 total, 0 failed |   7.760s overall, 0.031s each | score: 1.099 |
   webapp w/Mako: | 250 total, 0 failed |   7.984s overall, 0.032s each | score: 1.131 |
   web.py w/Mako: | 250 total, 0 failed |   8.005s overall, 0.032s each | score: 1.134 |
   bottle w/Mako: | 250 total, 0 failed |   8.068s overall, 0.032s each | score: 1.143 |
  web.py w/Jinja: | 250 total, 0 failed |   8.079s overall, 0.032s each | score: 1.145 |
  bottle w/Jinja: | 250 total, 0 failed |   8.129s overall, 0.033s each | score: 1.152 |
  webapp w/Jinja: | 250 total, 0 failed |   8.260s overall, 0.033s each | score: 1.170 |
    flask w/Mako: | 250 total, 0 failed |   8.279s overall, 0.033s each | score: 1.173 |
 webapp w/Django: | 250 total, 0 failed |   8.316s overall, 0.033s each | score: 1.178 |
    tipfy w/Mako: | 250 total, 0 failed |   8.419s overall, 0.034s each | score: 1.193 |
   flask w/Breve: | 250 total, 0 failed |   9.245s overall, 0.037s each | score: 1.310 |
   tipfy w/Breve: | 250 total, 0 failed |   9.310s overall, 0.037s each | score: 1.319 |
  bottle w/Breve: | 250 total, 0 failed |   9.311s overall, 0.037s each | score: 1.319 |
  web.py w/Breve: | 250 total, 0 failed |   9.314s overall, 0.037s each | score: 1.320 |
  webapp w/Breve: | 250 total, 0 failed |   9.356s overall, 0.037s each | score: 1.325 |
                               
                               
Overhead 1000:                 
   tipfy w/Jinja: | 250 total, 0 failed |   7.900s overall, 0.032s each | score: 1.000 |
    tipfy w/Mako: | 250 total, 0 failed |   9.061s overall, 0.036s each | score: 1.147 |
    flask w/Mako: | 250 total, 0 failed |   9.100s overall, 0.036s each | score: 1.152 |
   web.py w/Mako: | 250 total, 0 failed |   9.109s overall, 0.036s each | score: 1.153 |
   bottle w/Mako: | 250 total, 0 failed |   9.136s overall, 0.037s each | score: 1.156 |
   webapp w/Mako: | 250 total, 0 failed |   9.144s overall, 0.037s each | score: 1.158 |
  webapp w/Jinja: | 250 total, 0 failed |   9.268s overall, 0.037s each | score: 1.173 |
  web.py w/Jinja: | 250 total, 0 failed |   9.355s overall, 0.037s each | score: 1.184 |
          web.py: | 250 total, 0 failed |   9.359s overall, 0.037s each | score: 1.185 |
  bottle w/Jinja: | 250 total, 0 failed |   9.364s overall, 0.037s each | score: 1.185 |
 webapp w/Tenjin: | 250 total, 0 failed |   9.688s overall, 0.039s each | score: 1.226 |
 bottle w/Tenjin: | 250 total, 0 failed |   9.746s overall, 0.039s each | score: 1.234 |
  tipfy w/Tenjin: | 250 total, 0 failed |   9.899s overall, 0.040s each | score: 1.253 |
 web.py w/Tenjin: | 250 total, 0 failed |  10.525s overall, 0.042s each | score: 1.332 |
  flask w/Tenjin: | 250 total, 0 failed |  10.742s overall, 0.043s each | score: 1.360 |
   flask w/Jinja: | 250 total, 0 failed |  13.494s overall, 0.054s each | score: 1.708 |
          bottle: | 250 total, 0 failed |  13.561s overall, 0.054s each | score: 1.717 |
 webapp w/Django: | 250 total, 0 failed |  16.773s overall, 0.067s each | score: 2.123 |
   flask w/Breve: | 250 total, 0 failed |  31.060s overall, 0.124s each | score: 3.932 |
  webapp w/Breve: | 250 total, 0 failed |  31.300s overall, 0.125s each | score: 3.962 |
  web.py w/Breve: | 250 total, 0 failed |  31.341s overall, 0.125s each | score: 3.967 |
   tipfy w/Breve: | 250 total, 0 failed |  31.420s overall, 0.126s each | score: 3.977 |
  bottle w/Breve: | 250 total, 0 failed |  31.428s overall, 0.126s each | score: 3.978 |
                               
                               
Overhead 10000:                
   tipfy w/Jinja: | 250 total, 0 failed |  16.181s overall, 0.065s each | score: 1.000 |
    tipfy w/Mako: | 250 total, 0 failed |  18.838s overall, 0.075s each | score: 1.164 |
   web.py w/Mako: | 250 total, 0 failed |  20.317s overall, 0.081s each | score: 1.256 |
    flask w/Mako: | 250 total, 0 failed |  20.408s overall, 0.082s each | score: 1.261 |
   bottle w/Mako: | 250 total, 0 failed |  20.704s overall, 0.083s each | score: 1.280 |
   webapp w/Mako: | 250 total, 0 failed |  21.314s overall, 0.085s each | score: 1.317 |
  webapp w/Jinja: | 250 total, 0 failed |  21.567s overall, 0.086s each | score: 1.333 |
  bottle w/Jinja: | 250 total, 0 failed |  21.729s overall, 0.087s each | score: 1.343 |
  web.py w/Jinja: | 250 total, 0 failed |  22.290s overall, 0.089s each | score: 1.378 |
          web.py: | 250 total, 0 failed |  30.913s overall, 0.124s each | score: 1.910 |
 bottle w/Tenjin: | 250 total, 0 failed |  34.131s overall, 0.137s each | score: 2.109 |
  tipfy w/Tenjin: | 250 total, 0 failed |  34.571s overall, 0.138s each | score: 2.137 |
 webapp w/Tenjin: | 250 total, 0 failed |  35.038s overall, 0.140s each | score: 2.165 |
  flask w/Tenjin: | 250 total, 0 failed |  41.125s overall, 0.164s each | score: 2.542 |
 web.py w/Tenjin: | 250 total, 0 failed |  41.612s overall, 0.166s each | score: 2.572 |
   flask w/Jinja: | 250 total, 0 failed |  67.461s overall, 0.270s each | score: 4.169 |
          bottle: | 250 total, 0 failed |  72.529s overall, 0.290s each | score: 4.482 |
 webapp w/Django: | 250 total, 0 failed | 104.344s overall, 0.417s each | score: 6.448 |
  bottle w/Breve: | 250 total, 0 failed | 254.673s overall, 1.019s each | score: 15.739 |
  web.py w/Breve: | 250 total, 0 failed | 254.768s overall, 1.019s each | score: 15.745 |
   flask w/Breve: | 250 total, 0 failed | 255.902s overall, 1.024s each | score: 15.815 |
  webapp w/Breve: | 250 total, 0 failed | 256.810s overall, 1.027s each | score: 15.871 |
   tipfy w/Breve: | 250 total, 0 failed | 259.073s overall, 1.036s each | score: 16.011 |


Totals:
   tipfy w/Jinja: |  38.214 seconds total | score:   4.008 |
    tipfy w/Mako: |  44.638 seconds total | score:   4.689 |
   web.py w/Mako: |  45.708 seconds total | score:   4.722 |
   webapp w/Mako: |  46.292 seconds total | score:   4.724 |
   bottle w/Mako: |  46.161 seconds total | score:   4.755 |
    flask w/Mako: |  46.040 seconds total | score:   4.762 |
  webapp w/Jinja: |  47.032 seconds total | score:   4.807 |
  bottle w/Jinja: |  47.286 seconds total | score:   4.829 |
  web.py w/Jinja: |  47.735 seconds total | score:   4.848 |
          web.py: |  54.585 seconds total | score:   5.129 |
 bottle w/Tenjin: |  58.381 seconds total | score:   5.404 |
 webapp w/Tenjin: |  59.265 seconds total | score:   5.457 |
  tipfy w/Tenjin: |  59.318 seconds total | score:   5.499 |
 web.py w/Tenjin: |  66.625 seconds total | score:   5.962 |
  flask w/Tenjin: |  66.630 seconds total | score:   5.998 |
   flask w/Jinja: |  95.796 seconds total | score:   7.986 |
          bottle: | 100.894 seconds total | score:   8.302 |
 webapp w/Django: | 136.596 seconds total | score:  10.770 |
  web.py w/Breve: | 302.775 seconds total | score:  22.079 |
  bottle w/Breve: | 302.829 seconds total | score:  22.093 |
   flask w/Breve: | 303.550 seconds total | score:  22.102 |
  webapp w/Breve: | 305.081 seconds total | score:  22.243 |
   tipfy w/Breve: | 307.435 seconds total | score:  22.394 |

Thats all for today! Stay tuned for tomorrow’s conclusion.

Python templates on AppEngine Follow Ups

25 Aug 2010

Following yesterday’s experiments, some asked for a more comprehensive test including some more frameworks. Flask, Tipfy and the templating engine Jinja2.
It was my first experience with these, Flask got my attention, it seems like a slick microframework. Jinja2 was deceptive because of its limited template syntax way too far from Python. Tipfy didn’t really please me, but looking at its performance figures, I might now take a second look at this one.

Lets first take time to look at the new implementations. Getting a feeling on how they differs from the previous.

Flask with Jinja2 (Flask built-in):

from google.appengine.ext.webapp import util
from flask import Flask, render_template
app = Flask(__name__)

@app.route("/flask/<numA>/<numB>")
def hello(numA,numB):
    return render_template('flaskTestTemplate.html', nums=range(int(numA)), numB=numB)
    
def main():
    util.run_wsgi_app(app)

if __name__ == "__main__":
    main()
<html>
<body>
    <table>
        {% for i in nums %}
            <tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
        {% endfor %}
    </table>
</body>
</html>

Flask with Mako:

import os
from google.appengine.ext.webapp import util
from flask import Flask
from mako.template import Template
app = Flask(__name__)

@app.route("/flaskmako/<numA>/<numB>")
def hello(numA,numB):
    return Template(filename=os.path.join(
      os.path.dirname(__file__),"flaskTestMakoTemplate.html")).render(numA=numA,numB=numB)
    
def main():
    util.run_wsgi_app(app)

if __name__ == "__main__":
    main()
<html>
<body>
    <table>
        % for i in range(int(numA)):
            <tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
        % endfor
    </table>
</body>
</html>

webapp with Jinja2

import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from jinja2 import Environment, FileSystemLoader

class MainHandler(webapp.RequestHandler):
    def get(self,numA,numB): 
        self.response.out.write(
            Environment(loader=FileSystemLoader(
                os.path.join(
                    os.path.dirname(__file__),"templates/"))).get_template(
                        "gjinjaTestTemplate.html").render(nums=range(int(numA)),numB=numB))

application = webapp.WSGIApplication(
  [(r'/gjinja/(.*)/(.*)', MainHandler)],debug=False)

def main():
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()
<html>
<body>
    <table>
        {% for i in nums %}
            <tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
        {% endfor %}
    </table>
</body>
</html>

Bottle with Jinja2:

import os, bottle
from bottle import get
from google.appengine.ext.webapp import util
from jinja2 import Environment, FileSystemLoader

bottle.debug(False)
app = bottle.default_app()

@get('/bottlejinja/:numA/:numB')
def index(numA,numB):
  return Environment(loader=FileSystemLoader(
              os.path.join(
                  os.path.dirname(__file__),"templates/"))).get_template(
                      "bottleJinjaTestTemplate.html").render(nums=range(int(numA)),numB=numB)

def main():
  util.run_wsgi_app(app)

if __name__ == "__main__":
  main()
<html>
<body>
    <table>
        {% for i in nums %}
            <tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
        {% endfor %}
    </table>
</body>
</html>

web.py with Jinja2:

import os
from google.appengine.ext.webapp import util
import web
from jinja2 import Environment, FileSystemLoader

web.config.debug = False
urls = (
	'/webpyjinja/(.*)/(.*)',	'index'
)
app = web.application(urls, globals())

class index:
  def GET(self,numA,numB):
    return Environment(loader=FileSystemLoader(
                os.path.join(
                    os.path.dirname(__file__),"templates/"))).get_template(
                        "webpyJinjaTestTemplate.html").render(nums=range(int(numA)),numB=numB)

def main():
  util.run_wsgi_app(app.wsgifunc())

if __name__ == "__main__":
  main()
<html>
<body>
    <table>
        {% for i in nums %}
            <tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
        {% endfor %}
    </table>
</body>
</html>

tipfy with Jinja2:

import tipfy
from tipfy import RequestHandler, Rule
from tipfy.ext.jinja2 import render_response, default_config

default_config['templates_dir']='app/tipfy/templates'

class TipfyBenchmarkHandler(RequestHandler):
    def get(self,numA,numB):
        return render_response('tipfyJinjaTestTemplate.html', nums=range(numA), numB=numB)

app = tipfy.Tipfy(rules=[
  Rule('/tipfy/<int:numA>/<int:numB>', endpoint='tipfy', handler=TipfyBenchmarkHandler)])

def main():
    app.run()
    
if __name__ == '__main__':
    main()
<html>
<body>
    <table>
        {% for i in nums %}
            <tr><td>{{i}}</td><td>x {{numB}}</td><td>= {{i|int * numB|int}}</td></tr>
        {% endfor %}
    </table>
</body>
</html>

tipfy with Mako:

import os, tipfy
from tipfy import RequestHandler, Response, Rule
from mako.template import Template

class TipfyMakoBenchmarkHandler(RequestHandler):
    def get(self,numA,numB):
        return Response(
          Template(
            filename=os.path.join(
              os.path.dirname(__file__),
                "tipfyTestMakoTemplate.html")).render(numA=numA,numB=numB))

app = tipfy.Tipfy(rules=[
  Rule('/tipfymako/<int:numA>/<int:numB>', endpoint='tipfy', handler=TipfyMakoBenchmarkHandler)])

def main():
    app.run()
    
if __name__ == '__main__':
    main()
<html>
<body>
    <table>
        % for i in range(int(numA)):
            <tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
        % endfor
    </table>
</body>
</html>

And here is the test update wrapped inside a new consolidated benchmark. Also note that I modified all handlers to take advantage of AppEngine caching through the main() function.

Overhead 10:
   tipfy w/Jinja: | 1000 total, 0 failed |  88.376s overall, 0.088s each | score: 1.000 |
   flask w/Jinja: | 1000 total, 0 failed |  88.928s overall, 0.089s each | score: 1.006 |
   webapp w/Mako: | 1000 total, 0 failed |  91.340s overall, 0.091s each | score: 1.034 |
    tipfy w/Mako: | 1000 total, 0 failed |  91.624s overall, 0.092s each | score: 1.037 |
  webapp w/Jinja: | 1000 total, 0 failed |  91.951s overall, 0.092s each | score: 1.040 |
          bottle: | 1000 total, 0 failed |  98.956s overall, 0.099s each | score: 1.120 |
          web.py: | 1000 total, 0 failed |  99.725s overall, 0.100s each | score: 1.128 |
 webapp w/Django: | 1000 total, 0 failed | 100.619s overall, 0.101s each | score: 1.139 |
    flask w/Mako: | 1000 total, 0 failed | 103.797s overall, 0.104s each | score: 1.175 |
  web.py w/Jinja: | 1000 total, 0 failed | 103.985s overall, 0.104s each | score: 1.177 |
  bottle w/Jinja: | 1000 total, 0 failed | 104.074s overall, 0.104s each | score: 1.178 |
   bottle w/Mako: | 1000 total, 0 failed | 111.494s overall, 0.111s each | score: 1.262 |
   web.py w/Mako: | 1000 total, 0 failed | 115.740s overall, 0.116s each | score: 1.310 |


Overhead 100:
   tipfy w/Jinja: | 1000 total, 0 failed |  91.816s overall, 0.092s each | score: 1.000 |
   webapp w/Mako: | 1000 total, 0 failed |  94.623s overall, 0.095s each | score: 1.031 |
   bottle w/Mako: | 1000 total, 0 failed |  94.637s overall, 0.095s each | score: 1.031 |
    flask w/Mako: | 1000 total, 0 failed |  94.700s overall, 0.095s each | score: 1.031 |
  web.py w/Jinja: | 1000 total, 0 failed |  95.087s overall, 0.095s each | score: 1.036 |
          web.py: | 1000 total, 0 failed | 103.446s overall, 0.103s each | score: 1.127 |
   flask w/Jinja: | 1000 total, 0 failed | 104.981s overall, 0.105s each | score: 1.143 |
          bottle: | 1000 total, 0 failed | 105.155s overall, 0.105s each | score: 1.145 |
 webapp w/Django: | 1000 total, 0 failed | 107.200s overall, 0.107s each | score: 1.168 |
  webapp w/Jinja: | 1000 total, 0 failed | 107.458s overall, 0.107s each | score: 1.170 |
  bottle w/Jinja: | 1000 total, 0 failed | 107.681s overall, 0.108s each | score: 1.173 |
   web.py w/Mako: | 1000 total, 0 failed | 119.635s overall, 0.120s each | score: 1.303 |
    tipfy w/Mako: | 1000 total, 0 failed | 120.473s overall, 0.120s each | score: 1.312 |


Overhead 1000:
   tipfy w/Jinja: | 1000 total, 0 failed |  89.339s overall, 0.089s each | score: 1.000 |
          web.py: | 1000 total, 0 failed |  95.478s overall, 0.095s each | score: 1.069 |
  bottle w/Jinja: | 1000 total, 0 failed |  95.611s overall, 0.096s each | score: 1.070 |
  webapp w/Jinja: | 1000 total, 0 failed | 105.175s overall, 0.105s each | score: 1.177 |
  web.py w/Jinja: | 1000 total, 0 failed | 106.210s overall, 0.106s each | score: 1.189 |
   webapp w/Mako: | 1000 total, 0 failed | 106.407s overall, 0.106s each | score: 1.191 |
    flask w/Mako: | 1000 total, 0 failed | 106.724s overall, 0.107s each | score: 1.195 |
    tipfy w/Mako: | 1000 total, 0 failed | 107.081s overall, 0.107s each | score: 1.199 |
   web.py w/Mako: | 1000 total, 0 failed | 107.688s overall, 0.108s each | score: 1.205 |
          bottle: | 1000 total, 0 failed | 111.233s overall, 0.111s each | score: 1.245 |
   bottle w/Mako: | 1000 total, 0 failed | 117.832s overall, 0.118s each | score: 1.319 |
   flask w/Jinja: | 1000 total, 0 failed | 132.961s overall, 0.133s each | score: 1.488 |
 webapp w/Django: | 1000 total, 0 failed | 138.364s overall, 0.138s each | score: 1.549 |


Overhead 10000:
   tipfy w/Jinja: | 1000 total, 0 failed |  61.592s overall, 0.062s each | score: 1.000 |
    tipfy w/Mako: | 1000 total, 0 failed |  75.728s overall, 0.076s each | score: 1.230 |
   bottle w/Mako: | 1000 total, 0 failed |  81.219s overall, 0.081s each | score: 1.319 |
    flask w/Mako: | 1000 total, 0 failed |  81.409s overall, 0.081s each | score: 1.322 |
   web.py w/Mako: | 1000 total, 0 failed |  81.785s overall, 0.082s each | score: 1.328 |
   webapp w/Mako: | 1000 total, 0 failed |  82.661s overall, 0.083s each | score: 1.342 |
  webapp w/Jinja: | 1000 total, 0 failed |  84.205s overall, 0.084s each | score: 1.367 |
  bottle w/Jinja: | 1000 total, 0 failed |  84.636s overall, 0.085s each | score: 1.374 |
  web.py w/Jinja: | 1000 total, 0 failed |  85.994s overall, 0.086s each | score: 1.396 |
          web.py: | 1000 total, 0 failed | 126.019s overall, 0.126s each | score: 2.046 |
   flask w/Jinja: | 1000 total, 0 failed | 268.666s overall, 0.269s each | score: 4.362 |
          bottle: | 1000 total, 0 failed | 315.039s overall, 0.315s each | score: 5.115 |
 webapp w/Django: | 1000 total, 0 failed | 419.316s overall, 0.419s each | score: 6.808 |


Totals:
   tipfy w/Jinja: | 331.123 seconds total | score:   4.000 |
   webapp w/Mako: | 375.031 seconds total | score:   4.597 |
    flask w/Mako: | 386.630 seconds total | score:   4.722 |
  webapp w/Jinja: | 388.789 seconds total | score:   4.755 |
    tipfy w/Mako: | 394.907 seconds total | score:   4.777 |
  bottle w/Jinja: | 392.002 seconds total | score:   4.795 |
  web.py w/Jinja: | 391.276 seconds total | score:   4.797 |
   bottle w/Mako: | 405.181 seconds total | score:   4.930 |
   web.py w/Mako: | 424.849 seconds total | score:   5.146 |
          web.py: | 424.668 seconds total | score:   5.370 |
   flask w/Jinja: | 595.536 seconds total | score:   8.000 |
          bottle: | 630.383 seconds total | score:   8.625 |
 webapp w/Django: | 765.499 seconds total | score:  10.663 |

Take these results with a grain of salt… Especially since the difference margin is sow low. It’s in no way a full featured comparison!!!

UPDATE!! (2010-08-26) Tenjin and Brevé are also added in the next benchmark

Fast Python templates on AppEngine

23 Aug 2010

Faced with the task of building a fast xml api server with Google’s AppEngine I dived into the cloud service runtime options for web application construction. I generally have a bad feeling about interfacing with Java technologies, and benchmarks are there to prove that the engine’s Python API are at least on par with Java in term of speed and features, so I opted to follow the Pythonic way for the first time in a web application project.

Lets first make a small detour on my first impressions with Python as a server side scripting language. What stroked me first, is that Python mostly always stay Python, as opposed to Ruby’s capacity to bend into different DSL focussed on the application composition tasks. If I had done the job in Ruby, I would have used a light framework like Sinatra instead of Ruby On Rails. So it felt normal to look for some Sinatra equivalent in the Python world. Django, Turbogears, web2py and Pylons were all ditched out in the first round, I might have been wrong but they all seamed nice but overkill for my project. Bottle, web.py and webapp were all chosen for exactly the opposite reasons. Bottle being the the closest to Sinatra, it won a slot at the top of my list. I also hooked with Mako when looking for templating system options. I liked the way it seamed to put no limits into its templating power and its figures in page rendering benchmarks were impressives1.

Before diving deep into learning anyone of those frameworks, I decided to get a basic impression of them by building a small benchmark comparison. Three frameworks, each with their default templating module, plus these three same in combination with Mako, making for six comparison cases:

The benchmark was run on AppEngine local development server, because otherways the benchmark would have measured my internet bandwidth and possibly also run into internet bottleneck inconsistencies. I implemented the same simple api using the six systems, testing them alternatively, instead of successively like most benchmarks do. The server algorithms basically serve templates with calculations embedded. In only one instance of those I could not implement the calculation in the templating engine2. I know that most of the readers might think it is good not to be doing those things in the views anyway, I totally agree, but for the sake of evaluating the raw performance of the views, it’s worth it. Here comes the code for the different servers implementations:

webapp with Django Templates:

import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template

class MainHandler(webapp.RequestHandler):
    def get(self,numA,numB):
        nums = []
        for i in range(int(numA)):
          rmult = i * int(numB) 
          nums.append(str(rmult))
        templateValues = {
          "nums" : nums,
          "numB" : numB
        }
        self.response.out.write(template.render(os.path.join(
          os.path.dirname(__file__),"gnativeTestTemplate.html"),templateValues))

def main():
    application = webapp.WSGIApplication([(r'/gnative/(.*)/(.*)', MainHandler)],debug=False)
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()
<html>
<body>
    <table>
        {% for i in nums %}
            <tr><td>{{ forloop.counter }}</td><td>x {{ numB }}</td><td>= {{ i }}</td></tr>
        {% endfor %}
    </table>
</body>
</html>

webapp with Mako:

import os
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from mako.template import Template

class MainHandler(webapp.RequestHandler):
    def get(self,numA,numB):
        self.response.out.write(Template(filename=os.path.join(
          os.path.dirname(__file__),"gTestMakoTemplate.html")).render(numA=numA,numB=numB))

def main():
    application = webapp.WSGIApplication([(r'/gmako/(.*)/(.*)', MainHandler)],debug=False)
    util.run_wsgi_app(application)

if __name__ == '__main__':
    main()
<html>
<body>
    <table>
        % for i in range(int(numA)):
            <tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
        % endfor
    </table>
</body>
</html>

web.py with Templator:

from google.appengine.ext.webapp import util
import web

web.config.debug = False
render = web.template.render('app/webpy/templates')

class index:
  def GET(self,numA,numB):
    return render.index(numA,numB)
    
def main():
  urls = (
  	'/webpy/(.*)/(.*)',	'index'
  )
  app = web.application(urls, globals())
  util.run_wsgi_app(app.wsgifunc())

if __name__ == "__main__":
  main()
$def with (numA,numB)
<html>
<body>
    <table>
        $for i in range(int(numA)):
            <tr><td>$i</td><td>x $numB</td><td>= ${ i * int(numB) }</td></tr>
    </table>
</body>
</html>

web.py with Mako:

import os, web
from google.appengine.ext.webapp import util
from mako.template import Template

web.config.debug = False

class index:
  def GET(self,numA,numB):
    return Template(filename=os.path.join(
      os.path.dirname(__file__),"webpyTestMakoTemplate.html")).render(numA=numA,numB=numB)
    
def main():
  urls = (
  	'/webpymako/(.*)/(.*)',	'index'
  )
  app = web.application(urls, globals())
  util.run_wsgi_app(app.wsgifunc())

if __name__ == "__main__":
  main()
<html>
<body>
    <table>
        % for i in range(int(numA)):
            <tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
        % endfor
    </table>
</body>
</html>

Bottle with SimpleTemplate:

import os, bottle
from bottle import get, template
from google.appengine.ext.webapp import util

bottle.debug(False)

@get('/bottle/:numA/:numB')
def index(numA,numB):
  return template(os.path.join(
    os.path.dirname(__file__),'bottleTestTemplate.tpl'),numA=numA,numB=numB)

def main():
  app = bottle.default_app()
  util.run_wsgi_app(app)

if __name__ == "__main__":
  main()
<html>
<body>
    <table>
    %for i in range(int(numA)):
        <tr><td></td><td>x </td><td>= </td></tr>
    %end
    </table>
</body>
</html>

Bottle with Mako:

import os, bottle
from bottle import get
from google.appengine.ext.webapp import util
from mako.template import Template

bottle.debug(False)

@get('/bottlemako/:numA/:numB')
def index(numA,numB):
  return Template(filename=os.path.join(
    os.path.dirname(__file__),"bottleTestMakoTemplate.html")).render(numA=numA,numB=numB)

def main():
  app = bottle.default_app()
  util.run_wsgi_app(app)

if __name__ == "__main__":
  main()
<html>
<body>
    <table>
        % for i in range(int(numA)):
            <tr><td>${i}</td><td>x ${numB}</td><td>= ${int(i) * int(numB)}</td></tr>
        % endfor
    </table>
</body>
</html>

The benchmark is implemented in the following Ruby script:

#!/usr/bin/env ruby

require 'benchmark'
require 'net/http'
require 'uri'

baseURI = 'localhost:8082'
results = {
  'bottle' => {:time => 0.0, :success => 0, :failed => 0, :name => "bottle"},
  'bottlemako' => {:time => 0.0, :success => 0, :failed => 0, :name => "bottle w/Mako"},
  'gmako' => {:time => 0.0, :success => 0, :failed => 0, :name => "webapp w/Mako"},
  'gnative' => {:time => 0.0, :success => 0, :failed => 0, :name => "webapp w/Django"},
  'webpy' => {:time => 0.0, :success => 0, :failed => 0, :name => "web.py"},
  'webpymako' => {:time => 0.0, :success => 0, :failed => 0, :name => "web.py w/Mako"}
}

def callURI(baseurl,name,rows,index)
  uri = sprintf("http://%s/%s/%d/%d",baseurl,name,rows,index)
  url = URI.parse(uri)
  req = Net::HTTP::Get.new(url.path)
  res = Net::HTTP.start(url.host,url.port) do |http|
    http.request(req)
  end
  return res.kind_of? Net::HTTPOK
end

n = ARGV[0].to_i
rows = ARGV[1].to_i

1.upto(n) do |index|
  results.each_pair do |k,v|
    success = nil
    rez = Benchmark.realtime { success = callURI(baseURI,k,rows,index) }
    if success
      results[k][:time] += rez
      results[k][:success] += 1 
    else
      results[k][:failed] += 1
    end
    results[k][:average] = results[k][:time]/results[k][:success]
  end
end


best = 1
results.each_value.sort_by { |v| v[:average] }.each_with_index do |v,i|
  rank = 0
  if i == 0
    rank = 1
    best = v[:average]
  else
    rank = v[:average] / best
  end
  printf(
  "%16s: | %d total, %d OK, %d failed | %.3f secs overall, %.3f secs each | relative score: %.3f |\n",
  v[:name], v[:success]+v[:failed], v[:success], v[:failed], v[:time], v[:average], rank)
end

To further the comparison cases, the script is run with command line arguments assigned to the number of test iterations and the processing overhead in each cycles. After a bit of experimentation, I proceeded to benchmark with a high number of iterations, for better averaging, successively incrementing the processing overhead parameter to evaluate different rendering scenarios. Here is what comes out when testing.

no overhead:

ruby testScript.rb 1000 1
          bottle: | 1000 total, 1000 OK, 0 failed | 23.362s overall, 0.023s each | score: 1.000 |
   bottle w/Mako: | 1000 total, 1000 OK, 0 failed | 27.434s overall, 0.027s each | score: 1.174 |
   webapp w/Mako: | 1000 total, 1000 OK, 0 failed | 28.670s overall, 0.029s each | score: 1.227 |
          web.py: | 1000 total, 1000 OK, 0 failed | 79.013s overall, 0.079s each | score: 3.382 |
 webapp w/Django: | 1000 total, 1000 OK, 0 failed | 82.196s overall, 0.082s each | score: 3.518 |
   web.py w/Mako: | 1000 total, 1000 OK, 0 failed | 98.712s overall, 0.099s each | score: 4.225 |

low overhead:

ruby testScript.rb 1000 10
          bottle: | 1000 total, 1000 OK, 0 failed | 24.038s overall, 0.024s each | score: 1.000 |
   bottle w/Mako: | 1000 total, 1000 OK, 0 failed | 28.012s overall, 0.028s each | score: 1.165 |
   webapp w/Mako: | 1000 total, 1000 OK, 0 failed | 28.851s overall, 0.029s each | score: 1.200 |
 webapp w/Django: | 1000 total, 1000 OK, 0 failed | 89.095s overall, 0.089s each | score: 3.706 |
   web.py w/Mako: | 1000 total, 1000 OK, 0 failed | 94.399s overall, 0.094s each | score: 3.927 |
          web.py: | 1000 total, 1000 OK, 0 failed | 95.307s overall, 0.095s each | score: 3.965 |

medium overhead:

ruby testScript.rb 1000 100
          bottle: | 1000 total, 1000 OK, 0 failed | 26.813s overall, 0.027s each | score: 1.000 |
   bottle w/Mako: | 1000 total, 1000 OK, 0 failed | 27.378s overall, 0.027s each | score: 1.021 |
   webapp w/Mako: | 1000 total, 1000 OK, 0 failed | 28.920s overall, 0.029s each | score: 1.079 |
          web.py: | 1000 total, 1000 OK, 0 failed | 30.882s overall, 0.031s each | score: 1.152 |
 webapp w/Django: | 1000 total, 1000 OK, 0 failed | 34.118s overall, 0.034s each | score: 1.272 |
   web.py w/Mako: | 1000 total, 1000 OK, 0 failed | 224.659s overall, 0.225s each | score: 8.379 |

high overhead:

ruby testScript.rb 1000 1000
          web.py: | 1000 total, 1000 OK, 0 failed | 33.115s overall, 0.033s each | score: 1.000 |
   web.py w/Mako: | 1000 total, 1000 OK, 0 failed | 45.490s overall, 0.045s each | score: 1.374 |
 webapp w/Django: | 1000 total, 1000 OK, 0 failed | 62.941s overall, 0.063s each | score: 1.901 |
   bottle w/Mako: | 1000 total, 1000 OK, 0 failed | 74.669s overall, 0.075s each | score: 2.255 |
   webapp w/Mako: | 1000 total, 1000 OK, 0 failed | 81.663s overall, 0.082s each | score: 2.466 |
          bottle: | 1000 total, 1000 OK, 0 failed | 94.596s overall, 0.095s each | score: 2.857 |

very high overhead:

ruby testScript.rb 1000 10000
   bottle w/Mako: | 1000 total, 1000 OK, 0 failed | 78.331s overall, 0.078s each | score: 1.000 |
   webapp w/Mako: | 1000 total, 1000 OK, 0 failed | 80.489s overall, 0.080s each | score: 1.028 |
   web.py w/Mako: | 1000 total, 1000 OK, 0 failed | 97.224s overall, 0.097s each | score: 1.241 |
          web.py: | 1000 total, 1000 OK, 0 failed | 121.340s overall, 0.121s each | score: 1.549 |
          bottle: | 1000 total, 1000 OK, 0 failed | 288.903s overall, 0.289s each | score: 3.688 |
 webapp w/Django: | 1000 total, 1000 OK, 0 failed | 416.255s overall, 0.416s each | score: 5.314 |

As much as these benchmarks seems full of inexplicable corner cases, they can easily be reproduced with the scored average staying in a consistent range. No framework stayed on top of the list in all cases, adding the benchmark totals still reveals an average winner.

totals:

  bottle w/Mako: | 1.174 + 1.165 + 1.021 + 2.255 + 1.000 | total score: 6.615
  webapp w/Mako: | 1.227 + 1.200 + 1.079 + 2.466 + 1.028 | total score: 7.000
         bottle: | 1.000 + 1.000 + 1.000 + 2.857 + 3.688 | total score: 9.545
         web.py: | 3.382 + 3.965 + 1.152 + 1.000 + 1.549 | total score: 11.048
webapp w/Django: | 3.518 + 3.706 + 1.272 + 1.901 + 5.314 | total score: 15.711
  web.py w/Mako: | 4.225 + 3.927 + 8.379 + 1.374 + 1.241 | total score: 19.146

Finding absolutes insights through benchmarking frameworks performance, isn’t really possible. One can still use the results as a general guideline, knowing what the odds may look like, to make a more educated choice. As it turns out, the benchmark proves my choice of configuration has good speed potential. It also shows that both Bottle and web.py, with their built-in templating engine, could show better performance than AppEngine’s generic webapp with Django templates. None of those systems will surpass the others in every situations, so it is imperative not to rely only on performance figures when choosing companions components for your project. Aspects worth considering also includes features, stability, design, documentation, community and your own gut feelings about the component.

UPDATE!! (2010-08-25) Flask and Jinja are also added in the next benchmark

1 According to data from Genshi’s bench.

2 Django Templates (AppEngine’s built-in)

So Now With Jekyll

22 Aug 2010

Why would you move your blog from a fantastic dynamic blogging engine like Typo to a static site generator like Jekyll? Or maybe the question should be, when can you move your blog… because, if you look at it long enough, you might find it has many enviable advantages. Here is what made the move feel right for me.

First, your server will like it and will be speedier. Blog post are static and having a dynamic runtime to serve them only slows down the viewer’s experience. What still need to be dynamic, like embedding realtime Twitter updates, can mostly be implemented using client side javascript. Ready made solutions already exists for all sorts of realtime services, like Disqus viewers comments and Facebook status updates.

It will also reduce administrative chores while augmenting the designer’s freedom. No more esoteric server setup to maintain, risking compatibility breakages with every updates to libraries and runtime dependencies. Jekyll is easily installed on the editor’s system with powerful templating facilities using Liquid, elegant syntax highlighting using Pygments, eloquent markdown using RedCloth and a local auto reloading web server setup for fast dynamic development. You locally own your blog and its dependencies. No database backend to backup and maintain. No remote configuration using administrative login. It’s all there in your text editor. You compose, see results right away, tweek to your tastes, and sync your local updates with your remote server.

Jekyll is lovely and lively. It is blog aware, making it easy to include content partials within cascading layouts and it implements clever features, for easy cross components integration. It is under constant development and its community includes, amongst others, the notable Github. It feels light, flexible and transparent. Statically generated with dynamic content is versatile blog editing beamed through fast page rendering.

YAML::dump != Marshal.dump

04 Jan 2009

People! Using Yaml for serialising and quick and dirty DB projects is fun... but not optimal.

If you can serialise with Marshal.dump instead of YAML::dump, then go for it!

A > 30x dump and > 2x load, is not what I would call a marginal difference.

See for yourself by running this test script: (git://gist.github.com/43177.git )

#!/usr/bin/env ruby

require 'yaml'

marshal_file = 'marshal_dump.marshal'
yaml_file = 'yaml_dump.yml'

def line; puts "\n"; end; line

# building a immense array of hashes
print "building the bully data object..."; $stdout.flush
bully = Array.new
indexes = 5000; keys = 100; rand_value = keys * 100; entry_count = 0
indexes.times do |index|
  bully[index] = Hash.new
  keys.times do
    bully[index][rand(rand_value)] = rand(rand_value)
  end
  entry_count += bully[index].size
  print '.' if index % 10 == 0; $stdout.flush
end
line
puts "bully data structure containing #{entry_count} entry built"
line

# marshall test
puts "Testing Marshal dump..."
start = Time.now

f = File.new(marshal_file, 'w')
f.puts( Marshal.dump(bully) )
f.close

marshal_dump = Time.now - start
puts "marshall dump time: #{marshal_dump} sec"; line

puts "Testing Marshal load..."
start = Time.now

bully_marshaled = Marshal.load(File.open(marshal_file))

marshal_load = Time.now - start
puts "marshall load time: #{marshal_load} sec"
marshal_size = File.size(marshal_file)
puts "for #{marshal_size} bytes"; line

if bully.first == bully_marshaled.first &&
  bully.last == bully_marshaled.last &&
  bully.length == bully_marshaled.length then
  puts "and both bully match!!!"
else
  puts "and they don't match!!!"
end
line

# yaml test
puts "Testing YAML dump..."
start = Time.now

f = File.new(yaml_file, 'w')
f.puts( YAML::dump(bully) )
f.close

yaml_dump = Time.now - start
puts "yaml dump time: #{yaml_dump} sec"; line


puts "Testing YAML load..."
start = Time.now

bully_yaml = YAML::load(File.open(yaml_file))

yaml_load = Time.now - start
puts "yaml load time: #{yaml_load} sec"
yaml_size = File.size(yaml_file)
puts "for #{yaml_size} bytes"; line

if bully.first == bully_yaml.first &&
  bully.last == bully_yaml.last &&
  bully.length == bully_yaml.length then
  puts "and both bully match!!!"
else
  puts "and they don't match!!!"
end
line


# Final Stats:
dump_stat = marshal_dump / yaml_dump
load_stat = marshal_load / yaml_load
size_stat = marshal_size.to_f / yaml_size

printf "STATS: Marshal dump finishes in %.4f of YAML time\n", dump_stat
printf "STATS: Marshal load finishes in %.4f of YAML time\n", load_stat
printf "STATS: Marshal file is %.4f of YAML file size\n", size_stat
line

Forward Thinking

31 Dec 2008

Let me end the year on a pompous tone, making 3 bold statements about next year tech world transformations.

The Browsers Year

2009 will be the year where people which are still using Microsoft Internet Explorer, of any version, will wake up to the fact that you can actually choose your internet browser. The times of the Netscape vs IE monopoly are far behind, we now get the benefits of competition for browser features and collaboration for rendering and scripting engines. One of the fastest improving ground in the programming world today seems to be the JavaScript engine of all browsers but IE. When Google, Apple and the Mozilla Foundation all get on a speed race developing a better JavaScript engine for their browsers, one cannot be really far away from reality thinking JavaScript is the next big programming thing.

Make that iPhone bigger, let me throw away my mouse

What do you think is the next insanely great product Apple will launch? No kidding, the iPhone and iPodTouch are killer products, and its likely along this line of success that Apple will plan their future: the Mac elegant interface coupled with multi-touch screen wonders. Multi-touch screen interfaces have been prototiped for at least 10 years, but they never made it through to the consumer market, mainly because of the unavailability of any willing desktop OS company, ready to develop multi-touch pointer devices support for their systems. Now that the market has been seduced by the idea, Apple has all the cards in its game to launch that same beautiful iPhone interface paradigm for its desktop computers. Do you think they'll miss that chance? Many will say Microsoft already did it when they tried to launch their own, bulky, Surface computer. But again, who wants this 10k$ coffee table to grab dust in their living room. No mind blowing "Only this device allow me such and such" has been reclaimed for the Surface, at least nothing compared to how the iPhone has hit the market. The Surface is ready made to be thrown to the weird technologies junkyard. Consumers want useful, unobtrusive, invisible technologies and this is exactly what Apple will provide, they control their hardware and software platforms in a way no other manufacturers can, and will use this unique power to leverage the next mainstream household computer technology.

All for One

In the current failing world economy, taking into consideration a possible, maybe inevitable, society paradigm shift, solidarity is key. Joint Open Source component development is an already widespread mechanism of technology adoption, and will become the de facto approach embraced by every trendsetting company. The tendency will be "Do it or miss it". Changing for the good, the way competition and collaboration operates on innovation. Entrepreneurial strategy will evolve from being the one owning a technology to being the best using a technology. You'll need to be fast, intelligent and creative to excel at these new business games, and hopefully these tendencies will allow for a quick, positive restructuration, of the now failing world economy.

SkypeIn For The Rest of Us

30 Oct 2008

Since Skype launched its SkypeIn service, somewhere in 2005, I keep going back to their website, always in dismay, facing the absence of a Canadian flag on the supported countries page. Last time I went though, I kept looking a bit more, thats when I found about the DIDs...

A much better hack

DID, stands for Direct Inward Dialing. It can be seen as a way to buy yourself a phone number, which doesn't come attached to any telco with their usual lock-in contracts. DID providers usually offers this service at very low monthly fees, and they can even supplement it with many flexible routing services. The one I choosed, DIDww, charges you around 5$ a month for the number, and of course it offers support for Skype!

But wait!!! the Skype service its only beta...

I would have liked it to work at first sight... but I'll have to wait a little! I must tell though its really no good reason to stop, because there is lots of possibilities in there! Versatility!! It works with most VOIP systems, with MSN and GTalk, with traditional PSTN lines, and also allows you to mix and match them to your taste. With all those possibilities there is no way to get locked in there like in the old-telecom-days!

My cute little bug

At present, there seem to be a couple of bugs with the Skype mapping in there. The first one I noticed, gives the symptom of only sometimes sending the caller a ringtone... a long blank pause at the other end of the line. Then it gets to Skype, but cuts the line when voicemail jumps in.... ARGGHHH!!!! I take the deep breath Skype has told me to... then mmm, what can I do? I really want to stick with Skype, having a dedicated Belkin Skypephone, a beautifull wi-fi pet I like to bring everywhere with me.

What I DID

Conviced that it would work, I decided to try a few things before totally losing faith... Grouping a few mappings together seemed like the best strategy. I tried with GTalk, the voicemail did work, but then GTalk seems a bit too beta to me, and most of all only offerered a Windows client, shame on you Google! I looked at a couple of VOIP providers, most made me feel like going into yet another wanabe telecom, and the rests makes you doubt they will survive in the long run. There was also this alien, but very creative and with lots of potential PhoneGnome, very very very interesting, but not at all what I need... and Gizmo, this humble project feels very much like Skype, has clients for the 3 OSes and guess what, the voicemail not only works, it works good! You are allowed to record your own welcome message, it sends the received message as wave files to the email of your choice, all for free!!

A good starting point

What's fun getting into this DID based setup is that it will allow me to evolve my communications assets as technology grows, all while keeping the same phone number, or even to keep the same assets while moving to a different area code. The recipe which works for me now is to chain Gizmo after Skype with deactivated voicemail. What it does is it tries to call me on Skype for 15 seconds (sometimes with no rings, ARGHH!! but people get used to it), if I don't answer then it jumps to Gizmo, for which a client is not open anywhere, giving the caller a voicemail service straight away. Cheep phone service that serves you better than those from the all-encompassing you-will-never-get-away-from-us Telecoms!

Stand Up For Haml!

05 Oct 2008

All of you, web programmers and designers, unlucky victims of old international standards, come and take refuge in the new revelation. Allow yourself to enter the wind of change which will free your brain from closing tags, free your spirit from depressive mechanic language, free your soul from angle bracket prison. The time has come to transform old robot habits in new fractalicious and somptually novels ways.

the new creation is transformation

In this age where resources become scarce, the changes in technology happens by recycling the old paradigm, into new spinoffs from the same old concepts. So keep your 'div', 'p', 'a', 'span', 'h1' and else, but forget about the:

<tag id="idname" class="classname1 classname2">  
    This is my
    Content
</tag>

and discover the new novel effective extraordinaire:

%tag#idname.classname1.classname2 
    This is my
    Content

New technology adoption is only viable when it lowers the carbon footprint of its users

So is HAML!! The old xhtml programatic statement needs
about 7 symbols to define a tag, that is a lot of energy!!.

1 <
2 p
3 >
4 <
5 /
6 p
7 >

The same tag definition in HAML need only 2 symbols.
This is 71% savings!!

1 %
2 p

haml as in templating

If you are a bit aware of how the web work, you know your can only get as far as the browsers can... and to this day browsers do not render HAML, its not the way it works: HAML is a templating language, built in the first place to replace the old-style Rails templating system, ERB. Some folks from the PHP world also got it to work as a templating language for PHP, but it end around there... Making it a risky option for any serious web design firm, probably worried about the future maintainers of their web applications, who won't be able to understand this marginal templating language.

be the change you wish to see

Languages are the containers of cultures. Since the beginning of the web, HTML and XHTML are the languages we use to display informations inside browsers. What seemed permissive 15 years ago in the early web days, has become angle bracket jail your information can't get away from. Sooner or later, the technology catching transparents monolitics nets, will transform into poly-layered, complex and opaque, networking tissues for our really tunnels, becomming its terminal instance.

we need a new revolution

Each revolution starts from previous tentatives. Keeping what works allows to stand on the shoulders of giants. Pushing the limits makes for the new tentative. So EVERY STEP WE INDIVIDUALLY TAKE FORWARD IS A NEW POSSIBILITY FOR THE COLLECTIVITY, and evolution being a collective process, we better step where we want the collectium accomplishment to happen. We can't use tomorrow's technology today, but we can choose which of todays technology, will be the basis for tomorrow's technicological undertakings.

CodeRay textfilter for Typo

18 Sep 2008

( Update! 2010-08-20 ). This blog is not powered by Typo anymore, while Typo has since evolved for the better, I choose now to work with statically generated html with the help of Jekyll. I leave this post here anyway, for those who might need it for references.

How could I build a tech blog without proper code block highlighting?

Thats what I realised when I saw Typo coming a bit short in term of syntax highlighting. Not that there isn't one, but the Syntax textfilter included with the app is missing some languages definition in its arsenal to make it a decent choice, unless someone would only write Ruby, XML and YAML blocks in its blog. There is alway the prospect of writing a language module for Syntax but lets not rely on this for now.

The Odds:

I did a bit of research, and found three other syntax highlighting systems that suited Typo, with no coding or a minimum of coding required:

  • tmcode, already built into Typo, assure the loading of the css necessary to display code html generated by the wonderfull Textmate OSX text editor.
    Pros: very esthetic result, fast load time, support lots of languages
    Cons: you need a Mac with Textmate, not a true "Markup and Publish" solution.
  • Ultraviolet, syntax highlighting library built on top of Textpow, which is built on top of Oniguruma-Ruby, which is built on top of Oniguruma. You get the option of using either Radiograph or tm_syntax_hightlighting as your Rails interface for Ultraviolet.
    Pros: uses Textmate highlighting making it an esthetic choice, decent language support
    Cons: dependency chain has many obstacles, not very fast,
  • CodeRay, syntax highlighting gem built entirely in Ruby
    Pros: easy gem installation, esthetically ok
    Cons: language support not that extensive.

The Test:

I tried hard to make Ultraviolet work... no problem on my development machine... but the hosted server never got to compile the first dependency, Oniguruma, adequately. So I jumped off the UV boat a bit depressed after a couple of days of hard labor. It was my first choice mainly for the large language palette, ignoring the speed issue which could have backlashed after regular usage.

So I turned to tmcode and Coderay. tmcode wasn't a bad option after all, giving the speed gain allowed by not having to process anything on the server at display time... and I happen to be both a Mac lover and a Textmate lover so there wouldn't be much pain in using this solution. But still, I wanted something I could use from any computer, so I embarked the CodeRay installation anyway... my language support of choice is not there at the moment, but the developper has a new version coming late september, which could change that consideration, and at worst, I could end up coding the languages support I need myself.

CodeRay has hit the place!

Installation is straightforward:

gem install coderay

Then what?
Typo has an ideal mechanism to integrate with CodeRay, called textfilters.
You can code one yourself by reading the Typo documentation on the subject: http://scottstuff.net/blog/articles/2005/08/23/introduction-to-typo-filters

which I did...
so you also have the option of using my implementation of CodeRay as a textfilter called CodeView.
Here it is: http://software.spiralix.org/typo/textfilter/typo_textfilter_codeview.zip
place this whole folder, unzipped, inside the vendor/plugins/ directory of your Typo installation and you are ready to go.
To get usage information, loggin to your Typo admin panel, under Settings->TextFilters you will find your CodeView macro with its help link to the far right of the page.

Typo Blog on Dreamhost

17 Sep 2008

( Update! 2010-08-20 ). This blog is not powered by Typo anymore, while Typo has since evolved for the better, I choose now to work with statically generated html with the help of Jekyll. I leave this post here anyway, for those who might need it for references.

Typo, is a blogging system built with Ruby on Rails. I found this gem at a moment where I was thinking about building my own blog engine, and as it turned out, I liked it enough to use it to build this blog.

The overall Typo on Dreamhost installation is pretty straightforward. Dreamhost does a very good job at running Rails web application, mostly since the arrival of mod_passenger, the Apache module for running Ruby web applications. Think about it, from all web application languages you can run on Dreamhost, only Ruby has its own module for running on the web server. Everything else gets to run on CGI or Fast-CGI (yes, even PHP run as CGI on Dreamhost as on many other shared hosting, mainly for security purposes) giving Ruby a noticeable performance advantage. For this reason I expect to see a rise in the number of Ruby CMS, which are almost inexistent at this point compared to the plethora of PHP CMS out there. Also in the favour of this Ruby uprise, we could mention the sexyness of the language and the ease of development of the Rails framework as well as other Ruby frameworks.

One thing to keep in mind when running Ruby webapps on Dreamhost under Passenger, is that you don't have acces to the Apache .conf file. This is really to protect you from messing with critical web server settings, a subject you probably don't want to delve into considering the amount of possibility/flexibility/complexity it offers, especially if you want some free time to blog on your own afterward. This lack of access to the .conf file won't stop you from running a Ruby blog on Dreamhost though, will it? It could... the main consequence being that the Ruby environment (Ruby and its gems) that Passenger will use for serving your apps is not configurable. (Goshh!!) But, there is a but! You can still install your own Ruby environment under your own Dreamhost user account, some generator gems will be usefull right away, like the Typo gem, which creates the code to run the app and then let Passenger serve is as a normal Ruby webapp, and for most of the other gems there is a neat packaging gem, called gemsonrails, that allows to pack gems inside your Rails apps with no more external-custom-installed gem dependencies left.

Preparation:

First, you must configure your Dreamhost user environment to allow local installations to happen. You will do this by setting a couple of environment variables to point to a local install path. I recommend doing all these operations using SSH/SFTP for security considerations, but you may also choose to use telnet/ftp as it is somewhat easier to setup and in most occasions faster.

Start by creating directories for your installation on top of your user's home folder:

cd $HOME
mkdir .gem bin include lib src

Edit .bash_profile, adding this to the end of the file:

. .bashrc

Then edit .bashrc, putting those lines in it:

export PATH="$HOME/bin:$HOME/.gems/bin:$PATH"
export RUBYLIB="$HOME/lib:$RUBYLIB"
export GEM_HOME="$HOME/.gem"
export GEM_PATH="/usr/lib/ruby/gems/1.8:$GEM_HOME"
alias gem="nice -n19 ~/bin/gem"

Then comes the installations:

Proceed to the download the sources for Ruby and Rubygems, place them inside the src directory you created earlier in this tutorial. I choosed Ruby Enterprise Edition from Phusion, the creators of mod_passenger, mainly because of its ease of installation and in hope I could benefit from its improved memory management (garbage collection), but unfotunately at this point since we cannot modify the Apache .conf file we are stuck with the Dreamhost installed Ruby... but we will be ready for the day we get that configuration option!

You can download Ruby from: www.rubyenterpriseedition.com/

And Rubygem from: http://rubyforge.org/frs/download.php/45905/rubygems-1.3.1.tgz

The Rubygem and Ruby installations goes as follow:

cd ~/src
tar xzvf rubygems-1.3.1.tgz
cd rubygems-1.3.1
ruby setup.rb --prefix=$HOME

cd ~/src
tar xzvf ruby-enterprise-X.X.X.tar.gz
./ruby-enterprise-X.X.X/installer

When asked by the Ruby Enterprise installer where the installation should take place, answer with the full path to your user home directory. And by fullpath I don't only mean /home/YOURUSERNAME/, what I really mean is something like /home/.HIDDENFOLDERNAME/YOURUSERNAME/ . If you don't know that path, try connecting to your users directory with a SFTP client and the information will leak for you somewhere in there.

If you also choosed to install Ruby Enterprise Edition , then you will already have Rails and friends installed, if not then go for it, install them! So the last thing left to install is the Typo gem itself, and that is an easy one:

gem install typo

and thats all!
To get started with Typo refer to typosphere's documentation:
http://typosphere.org/wiki/typo

wait! wait! wait!

One last detail... If you follow the typosphere documentation exactly you'll get into troubles with the Dreamhost Police, because the installer defaults to using Mongrel... and starts it automatically at the end of the installation process, which goes against the host policy on permanently running processes. So don't forget to include a "web-server=external" in your install options... and if its already too late... they usually let you correct your mistake without sending you to the Dreamhost Jail, so you can use the "config" typo terminal command to finally set your options properly.

for more info on Typo's options visit:
http://scottstuff.net/blog/articles/2006/07/23/typo-installer

}

Older Posts... Blog powered by Jekyll.
Built using Liquid, RedCloth, Pygments and Blueprint.

Copyright © 2008-2010 Louis-Philippe Perron