7 ms·
To add to the author's experience, I spent 18 months of production time with Elixir and Phoenix. As he says, the templates are compiled and are blindingly fast
by blunte 6y ago
To add to the author's experience, I spent 18 months of production time with Elixir and Phoenix.
As he says, the templates are compiled and are blindingly fast compared to Rails.
Pattern matching is really really nice when used in the right places (and you'll miss it if you go back to Ruby); but it can be overused. There's a faction of Elixir folks who attempt to avoid all conditionals and instead seem to prefer multiple dispatch/multi-methods to handle different cases. That's nice and very concise, because then you can simply call a function and let the pattern matching resolve which of the various implementations you've defined handle it. The big downside here is, as a reader of the code, you have to basically mentally imagine what all cases are covered and what they mean. Sometimes simply reading a switch statement or if/else/then is much clearer.
The super special magic is in the Erlang VM. If you put more energy into learning it and its capabilities, and using it where appropriate, it can shape the structure of your greater system beyond just one webapp; and it can provide a lot of features without you having to cobble together many other (good but independent) solutions.
Lastly, single thread performance is basically a dog. In my anecdotal experience, the same external service written with Elixir+Ecto was 25-50% as performant as a Python+SQLAlchemy program. So the lesson there is, find ways to parallelize or otherwise scale your process if it is batch oriented and handling a large volume of data.
If you asked me today if I would prefer to use Elixir (and Phoenix) over Ruby and Rails, I would say yes... but honestly mostly just because it's a new fascination with different tradeoffs and a better functional story. Function is the past and the future, and it makes your life easier and simpler. Elixir as a language... borrowed too much from Ruby and has too much syntax. It is noisy in a Perl-like way, and perhaps there could be a more concise enhancement of Erlang which would get the job done and not have you spending time visually parsing code.
- toolz 6y agosqlalchemy is an ORM - I'm not sure it can be compared against a web framework, but my experience with phoenix vs python web frameworks is that phoenix is easily faster even for single-threaded web requests (which of course will utilize many threads for things like DB thread pooling etc.)
- ch4s3 6y ago> In my anecdotal experience, the same external service written with Elixir+Ecto was 25-50% as performant as a Python+SQLAlchemy program I'd be interested to see an example here. Using Plug+Ecto has been roughly on par with most real world examples I've seen with SQLALchemy and Flask for single request performance. Python might be a bit faster for some workloads but once you start saturating the CPU, Elixir/BEAM really shines.
- blunte 6y agoI should have been clearer in my final note. My Elixir vs Python example was for standalone batch (script) processing. It was not in the context of a webapp. The case was a recurring calculation system which would take a few hundred thousand records and do about a dozen different calculations against those (some calculations requiring additional lookups). The original script was in Python, and then after the new webapp was built in Elixir, I rewrote it also in Elixir. I discovered that even tuning the batch sizes, the Elixir/Ecto version was much slower than the Python version. Then I looking into per-thread Elixir performance and found out that that is not its strong suit :). So the answer there of course is to leverage the strength of the tools and parallelize it; but I had no need to do that since I already had a working Python version. Could Elixir have been faster if multi-threaded? Probably. Single? No, I doubt it.
- dnautics 6y agoHm, if you have a chance to revisit it, you may want to consider Flow, which will likely take a lot of the pain of parallelization away.
- ch4s3 6y agoAhh, ok. You've sort of hit a weird spot for Elixir. Python will just start up faster and that will be noticeable. Math is much better optimized in Python, though this is changing with NX. The real gain here would probably be streaming chunks of rows out of ecto and maybe firing those off to tasks per CPU core. Which is easier than it sounds. That said, you had working code so no real reason to rewrite it except as a learning exercise.
- ksec 6y ago>Lastly, single thread performance is basically a dog. Is that still the case? I thought BeamVM recently added JIT? I don't expect it to be LuaJIT or JS V8, but Ruby and Python Single Thread performance should be reasonable expectation? I also wish some of these experience has more context in terms of code base size and team size. A Production environment of a small project with a team of 2 is very different to production environment of a project that is large and team of dozens if not hundreds.
- blunte 6y agoIn my case it was a team of 1 working on OTP 19 and 20. Since that is now 3+ years ago, it is quite possible things have improved in the single-thread performance area.
- astrowilson 6y agoThe 3+ years ago is a good reference, thanks for sharing. The issue could also be Ecto related. Ecto 3, which might not have been out at the time, had a bunch of improvements on this front.
- onlyrealcuzzo 6y agoElixir single thread performance /is/ much faster than Ruby and Python [1]. Are you sure you weren't just doing something sub-optimal with Ecto? [1] https://amp.reddit.com/r/elixir/comments/46v1l1/is_elixir_faster_than_rubypython_when_were/ https://amp.reddit.com/r/elixir/comments/46v1l1/is_elixir_fa...
- gregors 6y agoI'm a huge elixir fan, but in micro made up benchmarks I consistently see Ruby win over Elixir. I haven't run it with the jit. One specific case that cripples Elixir - is as far as I know Elixir can't handle large multiplications efficiently. Ruby, Python use karatsuba or some derivative - from what I can tell multiplication in Erlang doesn't. Though I haven't actually asked anyone and was just poking around on my own. example: Multiplying the first 100_000 numbers together I get ruby multi.rb 3.47s user 1.91s system 99% cpu 5.396 total elixir multi.exs 14.32s user 0.62s system 103% cpu 14.441 total
- conradfr 6y ago> attempt to avoid all conditionals I think that's a symptom of the lack of a return keyword (and therefore early returns).
- astrowilson 6y ago> and perhaps there could be a more concise enhancement of Erlang which would get the job done Erlang already is a more concise language than Elixir but also noisier as there are more punctuation characters. I would love if Erlang would drop some of its punctuation, as some of it is frankly unnecessary. Elixir syntax is definitely simpler than Ruby’s. Probably in the same ballpark as Python complexity wise: Elixir has less keywords and less rules thanks to the macro system but on the other hand more affordances, such as optional parenthesis.
- dmitriid 6y ago> Erlang already is a more concise language than Elixir but also noisier as there are more punctuation characters. What? Here's more-or-less the entirety of Erlang's "noisier syntax with more punctuation characters" -spec f(A :: any(), B :: some_type(), C :: list()) -> any(). f(A, {B}, [C | _]) -> A1 = fun() -> io:format(a, []) end, A1(), case C of <<1, _/binary>> -> 1; _ -> #person{ok = ok, field = C} end. Edit: %% plus map syntax #{"tuple" => {1,2}} M#{"key" := "new_value"} %% plus list comprehensions [X || X <- List, X /= 10]. Elixir has all that plus more. The equivalent in Elixir is something along the lines of @spec f(a :: any(), b :: some_type(), c :: list()) :: any() def f(a, {b}, [c | _]) -> a1 = fun() -> IO.inspect(a) end, a1.(), case C do <<1, _ :: binary>> -> 1 _ -> %Person{ok: :ok, field: c} end end ## and don't forget the capture and pipe syntax x |> (&f(y, {&1}, list)).() ## and default function parameters def f(a, b, c \\ []), do: something() ## and sigils String.replace(str, ~r".*", "") ## and Ecto adds its own characters id = 1 (from p in Person, where: p.id == ^id, select: p) |> Repo.all() ## and dropping down to Erlang is a function call on an atom :io.format(x, y) And I'm definitely forgetting more... Edit: ## and string interpolation a = 1 s = "The value is: #{a}" ## and two different map syntaxes %{a: :map, with: "various", keys: "as", atoms: 0} %{"another" => "map", "but" => "keys", "are" => "strings"} ## and different map access map[key] map.key ## and map update %{ map | key: "new_value" } ## and list comprehensions for x <- list, x != 10 do end
- dnautics 6y agoyou are conviently forgetting one thing: The equivalent of "hello world" in erlang is <<"hello world">> Not the best. Keyword lists in erlang are unpretty m:f(value, [{foo, <<"bar">>}, {baz, 47}]) Where in elixir you would do: :m.f(value, foo: "bar", baz: 47)
- ch4s3 6y ago> Sometimes simply reading a switch statement or if/else/then is much clearer Case statements are pretty common, and for code I read the with statement[1] is even more common. This allows you to code a happy path broken into small steps and then collect your edge cases. It doesn't solve every case for if/else or cases, but it's a nice tool. [1]https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1 https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1
- strzibny 6y agoI love with, completely forgot to mention it.
- vram22 6y agoDon't know Elixir. Understand a bit about pattern matching from dabbling in OCaml and F#. Not clear about this: >you have to basically mentally imagine what all cases are covered and what they mean Why? Isn't the logic clearly specified in some way by the multiple dispatch/multi-methods? Because if not, how does the program work? So shouldn't a user be able to read the multi* code, and understand it, just like they can with if/else statements?
- dnautics 6y agoone difference is that you can really afford to be lazy with elixir. If your pattern match fails, one option is to let it throw a MatchError. Especially if the failure is rare, you just let the process die and your supervisors will restart the process into a sane state, and hopefully the next time you hit the code path it will be ok (for example: you are calling out to a 3rd party web service and they have a brief service outage, or some backhoe went over a network link and killed your packets).
- cageface 6y agoThe problem for a new technology like Phoenix that wants to dethrone an established player like Rails is that it's not good enough to be 50% better. It has to be 2x or 3x as productive to offset the smaller ecosystem and pool of developers. And I'm not convinced for most of the things that Rails is used for it's that big of a productivity boost.
- joelbluminator 6y agoAs a Rails dev I have to say: Why trying to dethrone Rails? I don't get the Elixir obsession with Ruby/Rails (yes Jose Valim came from Ruby - so what?). Ruby is smallish enough, I don't get the obsesssion with going after that segment.
- mercer 6y agofrom my experience, Rails is not really much part of the general discussion in Elixir/Phoenix world. Sure, a number of us came from Rails, and lots of 'new to the community' discussions might hit on that, but that's a far cry from being 'obsessed' with dethroning Rails. If anything, my impression is that a significant portion of the community still uses Rails for many of their applications, and some occasional grumblings aside, it's not a big deal.
- strzibny 6y agoExactly!
- joelbluminator 6y ago> And I'm not convinced for most of the things that Rails is used for it's that big of a productivity boost It could be a big productivity hit actually...