Hello,
I’d like to propose a small extension to the ets API, and would like some feedback before making the proposal official.
Motivation:
ets:lookup
is pretty slow if the tuple to be copied is big, and only a small part of it is used.
ets:lookup_element
is the recommended solution to this problem, unfortunately it throws an exception if there is no match for the key, which is significantly slower than the way that lookup returns nil.
While profiling some Erlang code, I found some cases where an ETS table has huge tuples (18 fields, some of which are themselves quite big), making lookup costly, but using lookup_element could cause performance regressions when the element is absent. In addition to being slower, having to wrap lookup_element with a try/catch also makes it a bit less ergonomic.
Proposal
Add a new function ets:lookup_element_as_list/3
to the standard library. It shares most of its implementation with ets:lookup_element/3
, and takes the same arguments, but has the following returns:
- If there is no match for the key, return
[]
likeets:lookup
(instead of throwing an exception likeets:lookup_element
) - If there is a single match for the key, return the corresponding element wrapped in a singleton list:
[V]
- If the table is a bag/duplicate_bag, and there is at least one match, returns them in a list
Shown with code
For a set/ordered_set table, the following are identical:
foo(Table, Key) ->
try ets:lookup_element(Table, Key, 2) of
Value -> [Value]
catch
_:badarg -> [].
And
foo(Table, Key) -> ets:lookup_element_as_list(Table, Key, 2).
Benchmarks
I have implemented this (it is rather short, about 100LoC, half of which are docs and tests), and benchmarked it on some micro-benchmark on my M1 laptop. I tested both the case where the key is present and when it is absent.
When it is present, lookup_element_as_list is roughly as fast as lookup_element, which is about 50% faster than lookup.
When it is absent, lookup_element_as_list is roughly as fast as lookup, which is about 50% faster than lookup_element.
The microbenchmark is obviously not representative of a real application, but I consider it evidence that there are situations where neither lookup
nor lookup_element
are ideal from a performance point of view.
Questions
I have a few questions for the community:
- Do you agree that this is worth adding to ETS?
- Do you have any suggestions of changes either to the name or the semantics?
- Should I post this as a formal EEP, or is it sufficiently limited that I can just send a pull-request on github?