How to do timing in Elixir?

4,624 views
Skip to first unread message

Jon Kleiser

unread,
Nov 24, 2012, 4:38:33 AM11/24/12
to elixir-l...@googlegroups.com
Hi,

I'm looking for a way to do timing of operations in Elixir (or Erlang). I'd like to find the number of elapsed milliseconds. I couldn't find anything in the docs, or on this list.

/Jon

José Valim

unread,
Nov 24, 2012, 4:40:57 AM11/24/12
to elixir-l...@googlegroups.com
You can use the timer module in Erlang:

:timer.tc(fn -> code_to_measure end, [])


José Valim
Skype: jv.ptec
Founder and Lead Developer

Jon Kleiser

unread,
Nov 24, 2012, 5:18:53 AM11/24/12
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Hi José,

That was a very quick answer! However, I'm having some problems using this :timer.tc function. I have made a tiny module Words with  a function longones(filepath). I have compiled it to Elixir-Words.beam. I can call it from iex like this: Words.longones("/usr/share/dict/connectives")
I have tried to call it with the timer like this: :timer.tc(Words, longones, ["/usr/share/dict/connectives"])
... but that gave me "undefined function: IEx.Helpers.longones/0". Can you give me a hint?

/Jon

José Valim

unread,
Nov 24, 2012, 5:32:23 AM11/24/12
to elixir-l...@googlegroups.com
The error is because atoms in Erlang are written like "longones" but in Elixir you need to prepend a colon: ":longones". This should fix it:

    :timer.tc(Words, :longones, ["/usr/share/dict/connectives"])


José Valim
Skype: jv.ptec
Founder and Lead Developer



Jon Kleiser

unread,
Nov 24, 2012, 6:20:48 AM11/24/12
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Yes, thanks a lot! Now I can do this, the "heavy version", to find 10 of the longest words in the 2.4MB 'words' file:
:timer.tc(Words, :longones, ["/usr/share/dict/words"])
It returns this:
{11600956,["thyroparathyroidectomize","tetraiodophenolphthalein","scientificophilosophical","pathologicopsychological","formaldehydesulphoxylate","transubstantiationalist","thymolsulphonephthalein","scientificogeographical","pseudolamellibranchiate","Pseudolamellibranchiata"]}
... which means the operation took 11.6 secs (on my old MacBook Air).
This is my module:

defmodule Words do
   def longones(filepath) do
      Enum.take(List.sort(
            Enum.map(File.iterator!(filepath), fn(x) -> x end),
            fn(w1, w2) -> size(w1) > size(w2) end),
         10)
   end
end

When I do a similar operation using the following PicoLisp (interpreted) code on the same Mac ...
(bench (head 10 (reverse (by length sort (in "/usr/share/dict/words" (make (while (read) (link @))))))))
... it takes just 2.381 secs.

Is there anything I can do to speed up my Elixir module?
Would a similar module coded in plain Erlang execute much faster?
Do you expect Elixir to generate much faster code when it reaches version 1.0?

I'm just curious. ;-)

/Jon

José Valim

unread,
Nov 24, 2012, 7:04:48 AM11/24/12
to elixir-l...@googlegroups.com
Is there anything I can do to speed up my Elixir module?

There are a couple things to consider:

1. File.iterator! reads the file line by line using a function and accumulator data. This is very useful to read long files without loading them all into memory. If the file is small, maybe you would have better performance by loading it all in memory at once;

2. You are looping the file twice because Enum.sort does not support an ordering function. I am glad to say this was fixed on master and that could speed things up a bit;

3. File.iterator! is also faster on master. The curent code looped each binary twice to wrongly remove new line feeds;

So there are a few things that could be improved but I don't think it will get much faster by default. Why is that?

Every time you open up a file in Erlang/Elixir, you create a new Erlang/Elixir process that is responsible to manage that file. This allows you to pass files in between nodes, monitor them, talk to them asynchronously and other things. That said, every time you read a line from that file, you are sending a process message. Sending messages are very fast but the point is that you have some overhead to get other features back.

If you want to bypass such features, you can probably squeeze better performance. For example, you can open a file passing [:raw] option which does not wrap the file in a process and treat it as a raw binary. Assuming you are running on Elixir master, we could try this code out:

File.biniterator!(filepath, [:raw]) /> 
  Enum.sort(fn(w1, w2) -> size(w1) > size(w2) end) />
  Enum.take(10)

Note: the pipe operator left /> right(...args) translates to right(left, ...args)

With this, the result went from 10_275_166 to 4_186_974 micro seconds on my machine. :)
The benchmark file can be found here: https://gist.github.com/f792676362fcfe648c0f
 
Would a similar module coded in plain Erlang execute much faster?

Not really. Erlang would force you to loop the file extracting words manually which could give you some tiny performance gains. You could also hand roll the loop manually in Elixir too but I'd prefer the convenience of using an iterator 99 out of 100 times. Therefore, the results would be the same.

Do you expect Elixir to generate much faster code when it reaches version 1.0?

Nope, I don't think it will. :) 

I'm just curious. ;-)

Curiosity is appreciated. :)

Jon Kleiser

unread,
Nov 24, 2012, 7:53:13 AM11/24/12
to elixir-l...@googlegroups.com, jose....@plataformatec.com.br
Thanks again, José! I'm learning. I may wait with trying your suggestions till the new features are available in a precompiled package. Anyway, this is interesting stuff.

/Jon
Reply all
Reply to author
Forward
0 new messages