On 04/11/2012 03:03 PM, jos...@anwu.org wrote:

Racketeers,

I'm new to the language, and loving it.  I'm having trouble getting
good performance out of my little toy web app, however.

I wrote this fake billing app to play with some new languages (like
Racket) and some other webdev ideas I've had (like client-side
templating with jQuery and mustache).  As such, I have the same JSON
api written in Perl, Ruby, and Racket (working on node.js and
Haskell).  Perl runs under Dancer and Starman, ruby under Sinatra and
Unicorn, and Racket under nohup and its own included webserver. All
are running on the same machine.  Each connects to a postgres db,
executes some queries from a config file, and returns JSON to the
client.

I've been running apache bench against all three, and the performance
of Racket vs Perl and Ruby has been... disheartening.  I compiled the
racket code with 'raco exe' before running it, but Perl and Ruby both
blow it away.  The racket executable also seems to grab and hold a
lot of memory, even though I told it to be stateless.  It also tends
to have failures.

You don't need to use 'raco exe'; it's for ease of distribution, not performance.

The stateless option doesn't really apply here, since you aren't using send/suspend (or any of the variants thereof).

ab -c 20 -n 10000 <uri>

What URL(s?) are you testing? It's hard to analyze the numbers below without knowing what they're measuring.

Perl:

Concurrency Level:      20
Time taken for tests:   86.100 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      88150000 bytes
HTML transferred:       86300000 bytes
Requests per second:    116.14 [#/sec] (mean)
Time per request:       172.199 [ms] (mean)
Time per request:       8.610 [ms] (mean, across all concurrent requests)
Transfer rate:          999.82 [Kbytes/sec] received


Ruby:

Concurrency Level:      20
Time taken for tests:   102.914 seconds
Complete requests:      10000
Failed requests:        0
Write errors:           0
Total transferred:      88480000 bytes
HTML transferred:       86050000 bytes
Requests per second:    97.17 [#/sec] (mean)
Time per request:       205.827 [ms] (mean)
Time per request:       10.291 [ms] (mean, across all concurrent requests)
Transfer rate:          839.60 [Kbytes/sec] received


Racket:

Concurrency Level:      20
Time taken for tests:   139.059 seconds
Complete requests:      10000
Failed requests:        687
    (Connect: 0, Receive: 0, Length: 687, Exceptions: 0)
Write errors:           0
Total transferred:      9421469 bytes
HTML transferred:       7100095 bytes
Requests per second:    71.91 [#/sec] (mean)
Time per request:       278.119 [ms] (mean)
Time per request:       13.906 [ms] (mean, across all concurrent requests)
Transfer rate:          66.16 [Kbytes/sec] received

Are you sure the "Failed requests" are really failures? A Stackoverflow answer suggests that these might be the result of a nondeterministic response length. (See http://stackoverflow.com/questions/579450.)

It's odd that the total transferred for the Racket benchmark is almost an order of magnitude less than the totals for Perl and Ruby. It would help to see the actual URLs used in the tests to make sure this isn't an apples-to-oranges comparison.

It would also be useful to run the benchmarks with "-c 1" to measure the pure sequential performance. I'm not familiar with the frameworks in question, but a brief scan suggests that Starman and Unicorn might be forking multiple OS-level workers, which would lead to more parallelism on multicore systems. Your Racket setup is limited to a single core.

I'm hoping it's just inexperience on my part - maybe my Racket code
just sucks.  Or maybe it's that I'm trying to stay functional and
avoid mutation, which I don't bother with in the other two. Anyone
interested in looking at the code and telling me what I'm doing
wrong, or could do better?  I would love to use Racket for more
serious projects.

https://github.com/TurtleKitty/CalicoBill

A few of your functions (eg, customer-invoices) look like they suffer from the "N+1 problem" (basically, executing a query within a loop instead of executing a single query with a join), but I presume you're doing the same thing in Perl and Ruby, so that doesn't have anything to do with the performance *difference*.

I would have written 'sql-ts->string' using 'format' instead, and I would probably use 'for/list' and 'for/hash' instead of 'map' and 'make-immutable-hash'. But overall your Racket code looks fine to me.

Ryan
____________________
 Racket Users list:
 http://lists.racket-lang.org/users

Reply via email to