jj1bdx
Related question: Implications of String concatenation vs. IOList for ANSI color codes
I’m wondering the performance difference between
ANSI.red <> "red" <> ANSI.reset <> ANSI.green <> "green" <> ANSI.reset
and
:erlang.list_to_binary([ANSI.red, "red", ANSI.reset, ANSI.green, "green", ANSI.reset])
I guess the one with :erlang.list_to_binary/1 is faster, but I might be wrong. Any suggestion welcome.
Trending in Questions
Hello!
Suppose you are building workflow (order / task / payment) processing system with the following requirements:
Each workflow con...
New
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app?
Looking for hints regarding:
Addi...
New
Kia ora,
We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
Hi all, I wanted to ask how the community is dealing with post-release steps.
Today we have Ecto migrations, which make sure that the db...
New
Hello,
I have an Elixir backend that implements a custom protocol over TCP. I want to load test the backend and assess the performance o...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve.
They are GUI (Emerge) and State management (S...
New
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










Showing Posts 1 to 10- Show Best Posts
- Show All Posts (oldest first)
- Show All Posts (newest first)
gregvaughn
IO lists offer some amazing performance benefits, but if you’re new to Elixir, first focus on “make it work make it work right, make it work fast” in that order. Actually, calling
:erlang.list_to_binary/1explicitly is a bit of a red flag. The true speed comes from passing IO lists into the final methods that write to the device, whether that is anIO.putsor a socket, etc.To focus on your explicit question, I’m not entirely sure. I’d expect the 2nd one to be faster, but you’d want to benchmark it to be certain. The first one will create a new binary with each
<>operator, but I would hope the 2nd one would create a single binary and consecutively write each piece of the IO list into it.jj1bdx
Containing doing things in Elixir functions is an acceptable principle. If putting multiple binaries into one list does not gain much performance, using
<>operator certainly looks easier to maintain. Maybe I’m too much influenced by the Erlang idioms.I was thinking about the difference on forming a large string (or binary) for output, such as that for
Plug.Conn.send_resp/3, formed as a list of partial strings.jmitchell
@gregvaughn is absolutely right that correctness is the first priority. After that if you’re not satisfied with performance you need to measure, measure, measure.
Benchmarking
Thanks to your question I took the opportunity to try the bmark benchmarking tool. Here’s a quick benchmark test I wrote based on the direction in the project’s README and your suggested expressions.
I’m piping to
IO.putshere so we can compare these approaches against IO lists. By default bmark does 20 runs per benchmark block, but I’ve increased them to 1000 to get more statistically significant results.Next I run
mix bmark. As expected I see a bunch ofredgreenlines printed and colored appropriately.Results
The benchmark timing data are in the
$PROJ_ROOT/results/directory. To compare the two implementations I runAnd the results!
In this particular example, the
:erlang.list_to_binary, on average, took 1.8553 times as long as the<>. Don’t interpret this to mean you should always use<>. The inputs in these examples are rather small. The README for the bmark project has a bit more detail on how to interpret these results.Moar data
Now let’s try benchmarking a couple more approaches:
Let’s compare each of these new approaches against the
<>results:Left:
:binary_concat_operator, Right::io_list_all_known_dataInterestingly
<>is still faster, although we have little confidence about that because thep-value is as high as it can get!Left:
:binary_concat_operator, Right::io_list_append_to_end<>crushes the deeply nested IO list, and we can be pretty confident about that for these particular inputs on my test machine. However, that may not be true for concatenating sequences of strings in general. To check that you’d need to do a lot more benchmarks with different kinds of inputs and ideally on several different kinds of machines.Exercises for the reader
<>, if at all.OvermindDL1
Aren’t you timing the performance of IO.puts in those calls? It should package up the data, send it to the leader via a message, then continue on, which that will probably be the most costly bit of the benchmark by far (due to data copying because of small binaries).
However yes, IOLists are more efficient with larger sets of binaries and bits of other ‘stuff’ thrown in.
At smaller scale it is not.
jmitchell
Yes, but I’m doing the same thing for all of them so statistically significant results are still meaningful.
Is that the least expensive way to guarantee an IO list gets serialized to a binary in the same way it does for
IO.puts?I’m still learning Elixir, and I’m not sure where the IO list flattening occurs. Is it always done by
List.flattenor can it even be deferred to Erlang/BEAM through another path?One thing’s for sure: benchmarking is much more insightful when you have a good understanding of the internals you’re exercising.
OvermindDL1
Not in this case, the different data structures will be sent around differently.
What happens for an IOList is this when output to IO, like I’ll use phoenix as an example here:
You have a template, it gets generated as a
renderfunction that takes arguments and output an IOList, a simple example may be:Template:
Output:
And with lots of loops and repeated string you can see how things can be re-used, so say you have something like a function that returns a set of HTML, like an input element or so, and you have a form that has lots of these, the binaries for those only exist once but are pointed to by the IOList many times, thus saving potentially a lot of memory in addition to holding it in cache better, this is when an IOList can be useful in-program especially, however when outputting to the network socket is when it shines.
Say you made a template that was one big giant binary of 10k in size, it will serialize that out to the socket decently fast and linearly, however the OS has a call that can take a ‘list’ memory locations and lengths to output, this is what an IOList uses when output on an IO socket, so when you output a template that has a lot of repeated parts the kernel will output the first set in the list, then the second, then the third, which could just be the part pointed to by the first again, then a fourth, which could just be ‘part’ of another memory section, etc… It holds less in RAM, less in the CPU cache, and it can all be done in-kernel without doing a lot of context switches.
Thus, testing what you did above, which had no repeated binaries, no large binaries that could be put on the ‘global’ heap, no repeated segments, then this is basically a worst-case for IOLists, so your benchmark will show that IOList is the worst, but for the case of, say, an HTML template, lots of repeated elements, can keep huge parts in memory in the shared binary heap, etc… then IOLists ‘should’ perform not just better but potentially a lot better (hardware depending, but even at worst it will not be slow by any stretch).
jmitchell
Good point. I need to poke around the Elixir and Erlang source to understand how IO works with various terms and what situations cause a term to be serialized to a binary.
Thanks for the explanation. The benefits of IO lists aren’t lost on me. They turn what could be a quadratic time operation,
O(n^2), into a linear time operation,O(n). It’s good to use them for the same reason you’d want to use Java’sStringBuilderinstead of its string concatenation operator,+. IO lists may be even better because, as you mentioned with network sockets, they can be streamed out in contiguous chunks without ever needing to create a new binary from two existing binaries using<>. You also make a great point about the cache hierarchy working better with many small binaries than a single behemoth binary.The benchmarks should not be taken seriously, although they could be a useful starting point for anyone wanting to delve deeper.
OvermindDL1
Exactly! ^.^
jj1bdx
As @OvermindDL1 writes, IOList is “flattened” efficiently in Erlang VM, so the programmers do not have to take care of flattening the list.
I later discovered that
Plug.conn.send_resp/3took the HTML body argument as an IOlist, so I didn’t have to use:erlang.list_to_binary/1anyway. I’ve also rewritten another short expression using<>. So now I’ve got rid of the:erlangfunctiongregvaughn
Here’s one write-up of how IO lists can be helpful without flattening all the way down at the OS level: https://www.bignerdranch.com/blog/elixir-and-io-lists-part-2-io-lists-in-phoenix/