|
| 1 | +# PL/Ruby performance |
| 2 | + |
| 3 | +How fast is PL/Ruby compared to the built-in procedural languages and its |
| 4 | +scripting-language peers? These are the first published numbers for the |
| 5 | +extension. Reproduce them any time with the committed suite: |
| 6 | + |
| 7 | +```sh |
| 8 | +PGPORT=5432 sh bench/run.sh |
| 9 | +``` |
| 10 | + |
| 11 | +The harness loads `bench/setup.sql` — the same four workloads written in |
| 12 | +PL/Ruby, PL/pgSQL, PL/Perl, PL/Python and PL/Tcl with matching semantics — and |
| 13 | +times each function under `pgbench -c 1`. |
| 14 | + |
| 15 | +## Results |
| 16 | + |
| 17 | +PostgreSQL 18.4, Ruby 3.2 (MRI, embedded), single client, `pgbench -T 8`, one |
| 18 | +warm session, Ubuntu 24.04 container on x86-64. Peers: PL/Perl (Perl 5.38), |
| 19 | +PL/Python (Python 3.12), PL/Tcl (Tcl 8.6). Transactions per second; higher is |
| 20 | +better. Treat ±5% as noise (the SPI and array rows are the noisiest). |
| 21 | + |
| 22 | +| Benchmark | PL/Ruby | PL/pgSQL | PL/Perl | PL/Python | PL/Tcl | |
| 23 | +|---|---:|---:|---:|---:|---:| |
| 24 | +| Call overhead (return argument) | 46,000 | 55,000 | 53,000 | 51,000 | 53,000 | |
| 25 | +| String ops (reverse+upper+length) | 34,500 | 36,000 | 36,000 | 36,000 | 35,000 | |
| 26 | +| SPI loop over 1,000 rows | 2,600 | 6,600 | 2,400 | 3,800 | 2,500 | |
| 27 | +| Array marshaling (int[100] + 1) | 18,600 | 25,000 | 15,900 | 19,600 | 17,600 | |
| 28 | + |
| 29 | +## Reading the numbers |
| 30 | + |
| 31 | +- **Compute is call-overhead-bound, and PL/Ruby is in the pack.** On the string |
| 32 | + workload all five languages sit within a few percent of each other: the |
| 33 | + executor's function-call machinery dominates, not the interpreter. On the |
| 34 | + trivial return-the-argument call, PL/Ruby is ~15% behind PL/pgSQL and |
| 35 | + PL/Perl. That gap is the MRI entry path — every call crosses into the |
| 36 | + interpreter through a protected `rb_eval`/`rb_protect` trampoline so that Ruby |
| 37 | + exceptions become catchable PostgreSQL errors. It is a fixed per-call cost |
| 38 | + that the string workload's actual work already amortizes away. |
| 39 | + |
| 40 | +- **Row iteration is PL/pgSQL's home turf.** Its `FOR ... IN SELECT` loop |
| 41 | + iterates natively without crossing a language boundary per row, so it leads |
| 42 | + the SPI workload by more than 2x. Among the interpreted languages PL/Python |
| 43 | + is fastest here because `plpy.execute` returns one materialized result whose |
| 44 | + rows are read by cached column mapping; PL/Ruby, PL/Perl and PL/Tcl cluster |
| 45 | + together (PL/Ruby marginally ahead of the other two). PL/Ruby's |
| 46 | + `spi_fetch_row` builds a fresh `Hash` per row — one output-function call and |
| 47 | + one String per column — which is the per-cell conversion cost, not anything |
| 48 | + in the loop itself. |
| 49 | + |
| 50 | +- **Array marshaling favors the languages with native array conversion.** |
| 51 | + PL/pgSQL wins by doing `unnest`/`array_agg` in C. Among the scripting |
| 52 | + languages PL/Ruby is second only to PL/Python and comfortably ahead of |
| 53 | + PL/Perl and PL/Tcl: arguments arrive as a native Ruby `Array` and a returned |
| 54 | + `Array` converts straight back, with no text round-trip. PL/Tcl pays to parse |
| 55 | + the array's text form itself (it does not auto-convert array arguments to Tcl |
| 56 | + lists), and PL/Perl trails despite its arrayref conversion. |
| 57 | + |
| 58 | +## Guidance |
| 59 | + |
| 60 | +- For pure computation, use whichever language reads best; the overhead |
| 61 | + differences are small and the string-level work erases them. |
| 62 | +- For tight loops over large results, prefer a set-based SQL statement (or |
| 63 | + PL/pgSQL) when the logic allows. When you need Ruby's expressiveness per row, |
| 64 | + `spi_query` with a block or `Cursor#each` keeps memory flat while paying the |
| 65 | + same per-row conversion cost measured here. |
| 66 | +- For array- and composite-heavy work, PL/Ruby's native `Array`/`Hash` |
| 67 | + conversion is a genuine strength — the fastest of the interpreted PLs after |
| 68 | + PL/Python, and without the text-parsing tax PL/Tcl carries. |
| 69 | + |
| 70 | +## Notes on method |
| 71 | + |
| 72 | +Each workload is a single prepared-ish statement driven by `pgbench -c 1` for a |
| 73 | +fixed wall-clock window; the reported figure is `pgbench`'s own TPS. One client |
| 74 | +keeps the comparison about per-call language cost rather than concurrency or |
| 75 | +lock behavior. The functions are validated to return identical results across |
| 76 | +all five languages before timing (see `bench/setup.sql`); if a language's |
| 77 | +extension is absent, `bench/run.sh` reports it as `skipped` rather than failing. |
0 commit comments