--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/0928ddde-8ca2-4110-a223-d62b711529a7n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAKC64%2Bz6yHpK%3DJpN1fd7fgz9Z-m2UmsA0a852obGrjCpXzN1-w%40mail.gmail.com.
The challenge I've seen, faced, and had team members face time and time again, is that pattern matching only goes so far. The limitations are:
1. Maps are slower (significantly enough to avoid IMHO), but do
give the flexibility desired (1.79x slower, which could compound
if in the wrong place)
2. Keyword lists must be matched in exact order, and flexibility
with them is limited
It's the frequent edge cases that end up causing problems: they end up being a little clunky in practical use.
-Brandon
[...]
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4%2BsNgEUVdYQv_N6cu90O%2BzheNs1UPo5bZPw8-tT4UROhA%40mail.gmail.com.
Yes, this is a pain point I see people running into. Especially given that you are not supposed to match on keyword lists.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/e77b7315-625a-4c8e-0e63-bbd03116fbbe%40cold.org.
On 10/27/22 3:42 PM, José Valim wrote:
> 1. Maps are slower (significantly enough to avoid IMHO), but do give the flexibility desired (1.79x slower, which could compound if in the wrong place)
Do you have a source for the 1.79 slower?
I don't expect maps to be slower but, more importantly, the difference here should be negligible because options rarely exceed more than a dozen keys and in those scenarios lookups are in the order of single-digit microseconds (source) in the worst case. So it seems counter productive to say you wouldn't pick the more flexible option because an operation will take 1.75us instead of 1us (assuming it is indeed slower). :)
Plus maps have other benefits such as reading multiple keys at once which will certainly offset keyword lists.
1.79 times, as I read it, not 1.79us. And of course benchmarks
being highly subjective, now that I retooled it it's at 2.12x
slower (see notes at the very bottom for likely reasons why).
What drove me to look into this is a very specific situation we face in my project where we process a high frequency of events on a databus per second, and it was bogging down. As I isolated easy wins (external database calls) I started looking into other inefficiencies.
Specifically this is a call that looks at the before and after state of a struct and from that decides if changes should be handled or not.
I expect many will likely find fault with the implementation of
this benchmark, but it is for a specific use case... for the room
in general: let's not digress on if there are better ways to do a
thing or not :)
The gist includes three scenarios:
The #'s:
Name ips average
deviation median 99th %
ordered 5.06 M 197.65 ns ±21980.53% 143
ns 230 ns
map_get 3.86 M 259.27 ns ±28310.93% 183
ns 494 ns
map_pattern 2.39 M 418.72 ns ±11140.31% 426
ns 551 ns
Comparison:
ordered 5.06 M
map_get 3.86 M - 1.31x slower +61.61 ns
map_pattern 2.39 M - 2.12x slower +221.07 ns
The gist / benchmark that created the above: https://gist.github.com/srevenant/ed28a0cf2a167379a21eb482291313ce
Why the change?
I did change it from when I ran it previously to come up with the 1.79x number, because it was referencing our code base, and in the previous benchmark I had less arguments it was matching against. I added a few more arguments to amplify the situation, as you can see.
I do realize this is NOT the heart of my performance problem, so
I hope others can avoid digressing into "you should do it this
other way" type discussions :D
This is just something that came out of exploring some performance issues.
And the suggestion for efficient named arguments came because we'd switched to ordered arguments, and then the code was flipped in one place (a, b) vs (b, a) (face-palm)
-Brandon
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/f3307a40-b4fe-c66f-0576-a162202dfee2%40cold.org.
1.79 times, as I read it, not 1.79us. And of course benchmarks being highly subjective, now that I retooled it it's at 2.12x slower (see notes at the very bottom for likely reasons why).
The gist includes three scenarios:
Fair enough :)
If I understand what you are saying: they are all maps because the source data comes from a map, and it's the method of extracting data from the map that differs (the algorithm), not the inherent nature of a map itself.
I agree, and apologize for the mistaken assertion.
However, what I didn't benchmark as i think about it, is what I
often will see, which is the creation of a map simply to pass
arguments — and this is more relevant to the request/need. The
example was based on existing structs/maps and not creating them
at each function call time.
Instead, for example:
def do_a_thing(%{key2: value2, key1: value1}) do ...
I think it's becoming a common pattern to then construct the maps as part of the call, ala:
do_a_thing(%{key1: 10, key2: 20})
Is this an expensive pattern because it generates a map only for
the next function to extract the keys and ignore the map?
-Brandon
--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/Dbl6CL5TU5A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4L37yu8KVbhuM0gNkVYOzCeoXaKzTBk4aY4OLLRdgRRLg%40mail.gmail.com.
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/9f60ba0c-8403-e93f-d5fb-b3f55df88d14%40cold.org.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/01432858-e854-4747-921a-230e6bbd7489n%40googlegroups.com.
Thank you for starting this interesting discussion!
While I don't think the suggested solution (introducing special
pattern matching syntax) is viable, for the reasons already
mentioned by others,
I do think the problem itself warrants further consideration.
Currently, handling keyword arguments is done in an ad-hoc
fashion.
Approaches between different codebases and even between different
parts of the same codebase vary significantly.
Especially w.r.t. error handling.
Even in Elixir's own codebase this is apparent. Some
(non-exhaustive) examples:
- Passing wrong options to `if` raises an ArgumentError with the
text "invalid or duplicate keys for if, only "do" and an optional
"else" are permitted"
- Passing wrong options to `defimpl` raises an ArgumentError with
the text "unknown options given to defimpl, got: [foo: 10, bar:
20]"
- Passing wrong options to `for` raises a CompileError with the
text "unsupported option :foo given to for"
- Passing wrong options to `inspect` ignores the option(s)
silently.
- Passing wrong options to `GenServer.start_link` ignores the
option(s) silently.
Other differences are between whether only keyword lists are
accepted, or maps with atom keys also, or possibly anything
implementing the `Access` protocol.
And in some places the options are used to create a special struct
representing the parsed options, which is allowed to be passed as
well directly.
This makes me think that we might want to look into
standardizing:
- How to indicate which options are mandatory and which options
have defaults.
- What kind of exception is raised when incorrect values are
passed (and with what message).
- By default raise whenever unrecognized options are passed; the
alternative of ignoring unrecognized options as an explicit opt-in
choice.
I think we could introduce a macro that embeds the code to do
these things and turn the result into a map inside the function
where it is called.
For the reason mentioned by José before (supporting multiple
function clauses with different pattern matching and defaults)
it makes more sense to call this macro in the function body rather
than embellish the function head with some special form.
What I haven't been able to figure out yet is how to call this
macro (`parse_options`?), or in which module in Elixir core it
should live. (`Keyword`? Or in a new `Option` module?)
I haven't written a proof-of-concept yet but I am pretty sure that
it is possible to write an implementation that needs to traverse
the list --or map--
that is passed in to the function only once. (Stopping earlier
when the number of keys inside do not match.)
This should be performant enough for general usage.
If there is a problem, I think that raising an ArgumentError (but
with a different error message detailing what options are missing
or unrecognized)
might be the clearest way to indicate to the caller that they are
using the function incorrectly.
The diligent reader might notice that there certainly is some
overlap between this new macro and initializing a struct with
enforced keys.
~Marten / Qqwy
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/01432858-e854-4747-921a-230e6bbd7489n%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/50a4057e-1d53-77fe-6cf5-1d7804f32b8b%40resilia.nl.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/l9squzpm.82ffe77f-1d5b-4c9f-9adf-83a8ed0cf0e8%40we.are.superhuman.com.
Thank you; I had missed the introduction of `Keyword.validate!`
😊
What is your stance on enhancing it to turn the result into a map
or struct?
Both for performance (performance characteristics of small maps
are similar to those of tuples).
And to make more illegal states unrepresentable:
`Keyword.validate!` does not allow duplicate keys, but the output
is another keyword list, which does not enforce this invariant.
(i.e. "parse,
don't validate")
~Qqwy / Marten
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4KA%2BzATOV%2BW7AQOXeKKkxxg%2BcCfOv-nztsPS5JRu6ezdg%40mail.gmail.com.