Warning: Micro-Benchmarks ahead! Keep this in mind as you read. Usually, micro-benchmarks are looked at with skepticism; however, I'm claiming this is useful information because it's a real-world application, solving a real-world need... even if it is only a very tiny little program in a very large world, after all.
The file wasn't so big that I was worried about memory use, but since I only needed a leaf from each branch of the tree, I chose the SAX-ish API anyway. That's going to make any code more bloated, but it wasn't too bad; the results are in Version 1. After I got everything working, I got to wondering about the performance, so I wrote it again in Ruby. I did not try to use the same logic; instead, I did it in what, for me, is more idiomatic Ruby code. The timings (I chose the average-looking times; I did not properly benchmark these, but I did run each program several times and then grabbed the middlin' looking one), which may or may not be surprising, look like this:
| Version | Language | Total time (s) | CPU usage |
|---|---|---|---|
| Version 1 | Go | 0.113 | 96% |
| Version R | Ruby | 0.099 | 66% |
"Hmmm", I hear you say. Well, the Go version is actually parsing the XML, and we all know XML for the bloated, expensive-to-parse format that it is. OTOH, Ruby is doing regexp on every line, and is additionally reading the entire file into memory first and splitting it into an array on line endings. Hmmm. Well, let's try a Go version that is a little more like the Ruby version. That's Version 2:
| Version | Language | Total time (s) | CPU usage |
|---|---|---|---|
| Version 2 | Go | 0.284 | 103% |
Yowsa! That's going in the wrong direction. Interestingly, it's now using more than one core of my CPU, so it's doing something thready underneath. Maybe it's because I'm reading the file line-by-line off the disk? Let's make it even more like the Ruby version; Version 3:
| Version | Language | Total time (s) | CPU usage |
|---|---|---|---|
| Version 3 | Go | 0.292 | 105% |
Definitely going in the wrong direction. Maybe it's the sre2 library? Let's try Version 4:
| Version | Language | Total time (s) | CPU usage |
|---|---|---|---|
| Version 4 | Go | 0.037 | 89% |
Ok, that's better. Armed with this, I went back to not reading the file entirely into memory in Version 5:
| Version | Language | Total time (s) | CPU usage |
|---|---|---|---|
| Version 1 | Go | 0.113 | 96% |
| Version R | Ruby | 0.099 | 96% |
| Version 2 | Go | 0.284 | 103% |
| Version 3 | Go | 0.292 | 105% |
| Version 4 | Go | 0.037 | 89% |
| Version 5 | Go | 0.034 | 91% |
Not a lot of difference; I did see a couple of runs where the CPU use dropped to 89% without affecting the total time, but these are pretty small numbers and we could be seeing actual run time being overwhelmed by the program initialization and what-not.
Anyway, I thought it was interesting. Ruby is slow as all get-out, but for micro-tasks where most of the heavy lifting is running in native C (regexp in Ruby is native, as is IO), it's more than capable enough. It's also worth noticing that this was with ca. 30 lines of Go code, vs. 8 lines of Ruby code.