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