Feature #22134
openFaster rb_scan_args() for keyword args (optimization)
Description
Motivation¶
When using the rb_scan_args() API, often we want to find a value for a given keyword argument. In order to do this, we call rb_scan_args() like so:
VALUE str;
VALUE kwargs;
VALUE example;
rb_scan_args(argc, argv, "1:", &str, &kwargs); // duplicates the kwargs hash in argv
if (!NIL_P(kwargs)) rb_get_kwargs(kwargs, &id_example, 0, 1, &example); // mutates the duplicated kwargs hash to retrieve `example:`
This duplicates the keyword args hash given in argv. It would be nice to be able to grab a direct reference to the keyword hash
and to have a variant of rb_get_kwargs() that didn't mutate the passed in hash.
Proposal¶
Add a new valid format character for rb_scan_args():
VALUE str;
VALUE kwargs;
VALUE example;
rb_scan_args(argc, argv, "1:^", &str, &kwargs); // access kwargs directly from argv
if (!NIL_P(kwargs)) rb_get_kwargs_const(kwargs, &id_example, 0, 1, &example); // don't mutate the passed kwargs hash
This '^' character would only be valid after a ":'.
I have a pull request available for anyone that is interested.
Thank you!
Updated by nobu (Nobuyoshi Nakada) 4 months ago
Updated by nobu (Nobuyoshi Nakada) about 1 month ago
A small correction to my previous comment: RB_SCAN_ARGS_BORROW_KEYWORDS itself can be used for feature detection, so a separate HAVE_RB_SCAN_ARGS_BORROW_KEYWORDS is unnecessary. Extensions can define it as "" when absent to share the same format string across Ruby versions.
Also, how about naming the non-destructive API rb_lookup_kwargs rather than rb_get_kwargs_const? We could introduce rb_extract_kwargs as a more descriptive name for the existing destructive operation, while retaining rb_get_kwargs as a compatibility alias.
Updated by nobu (Nobuyoshi Nakada) about 1 month ago
Measurements used the same Ruby development build on macOS arm64, with JIT disabled. The following are medians of seven samples, in nanoseconds per call:
| Arguments | Existing approach | rb_lookup_kwargs |
Speedup |
|---|---|---|---|
| One keyword | 67.4 | 54.3 | 1.24x |
| One keyword via a frozen hash splat | 64.3 | 47.6 | 1.35x |
| Four keywords | 121.1 | 106.6 | 1.14x |
| Sixteen keywords via a frozen hash splat | 286.5 | 253.0 | 1.13x |
Updated by nobu (Nobuyoshi Nakada) about 1 month ago
The restriction, rb_lookup_kwargs (or rb_get_kwargs_const) must not be used without "^", should be stated in the document.
Updated by matz (Yukihiro Matsumoto) 14 days ago
No objection. This is a C API optimization with no visible change from Ruby, so I leave the details, including the names, to @nobu (Nobuyoshi Nakada).
Matz.