Performance of term_to_binary vs Bbinary_to_term

Björn Gustavsson bjorn@REDACTED
Tue Jun 8 10:10:20 CEST 2021


It turns out that the compiler optimizes away the call to
term_to_binary/1 but not binary_to_term/1.

If I rewrite the benchmark like this:

run(Term, LoopCnt) ->
    T_Start_1 = ?TIMESTAMP,
    Result = t(fun erlang:term_to_binary/1, Term, LoopCnt-1),
    do_present_results("term_to_binary/1", LoopCnt, ?TIMESTAMP -
T_Start_1, Result),
    T_Start_2 = ?TIMESTAMP,
    Result_2 = t(fun erlang:binary_to_term/1, Result, LoopCnt-1),
    do_present_results("binary_to_term/1", LoopCnt, ?TIMESTAMP -
T_Start_2, Result_2).

t(F, Input, 0) ->
    F(Input);
t(F, Input, N) ->
    F(Input),
    t(F, Input, N-1).

I get different results:

1> tconvert:run(a, 10000000).

term_to_binary/1 RETURN VALUE:<<131,100,0,1,97>>
REQUEST COUNT:10000000
ELAPSED TIME (usec):480119
TIME PER REQUEST (usec): 0.0480119
PROJECTED RATE (req/sec): 20828169.68293277

binary_to_term/1 RETURN VALUE:a
REQUEST COUNT:10000000
ELAPSED TIME (usec):1086325
TIME PER REQUEST (usec): 0.1086325
PROJECTED RATE (req/sec): 9205348.30736658
ok
2> 1086325 / 480119.
2.2626161430811944

That is, term_to_binary/1 is roughly twice as fast as binary_to_term/1
for this example.

/Björn

On Tue, Jun 8, 2021 at 9:29 AM Valentin Micic <v@REDACTED> wrote:
>
> As I was surprised with the measurement myself, I am sure that compiler did some significant optimisation  — I am attaching the file with the source code, so you could review it yourself.
> Also, it would be interesting to see how this performs on R22 (I haven’t installed it yet).
>
> In my view, it doesn’t really mattar how fast the testing code is. What matter here is that there’s an order of magnitude difference in performance between the two BIFs.
>
> The calling syntax for the tconvert:run/2 is: tconvert:run( a, 10000000 ).
>
> The first argument is a term to be converted, and the second represents a number of iterations — higher this number, more accurate the measurement will be (at least in my opinion).
>
> After reading your email I’ve looked at my code again, and noticed a potential slow-down for binary_to_term/1 portion of the test.
>
> do_bin_to_term( <<Bin/binary>> , 0 ) -> binary_to_term( Bin );
> do_bin_to_term( <<Bin/binary>> , N )
> ->
>     binary_to_term( <<Bin/binary>> ),
>     do_bin_to_term( Bin , N-1 )
> .
>
>
> When written as
>
> do_bin_to_term( <<Bin/binary>> , 0 ) -> binary_to_term( Bin );
> do_bin_to_term( <<Bin/binary>> , N )
> ->
>     binary_to_term( Bin ),
>     do_bin_to_term( Bin , N-1 )
> .
>
> It speeds up the code by factor 2 (well, duh! Cynic would say — so much for compiler optimisation ;-))
>
> After this “fix”, binary_to_term/1 portion of the test runs “only” 14 times slower.
>
> (cig@REDACTED)322> tconvert:run( a, 10000000 ).
>
> term_to_binary/1 RETURN VALUE:<<131,100,0,1,97>>
> REQUEST COUNT:10000000
> ELAPSED TIME (usec):94664
> TIME PER REQUEST (usec): 0.0094664
> PROJECTED RATE (req/sec): 105636778.50080284
>
> binary_to_term/1 RETURN VALUE:a
> REQUEST COUNT:10000000
> ELAPSED TIME (usec):1385235
> TIME PER REQUEST (usec): 0.1385235
> PROJECTED RATE (req/sec): 7218991.723425989
> ok
>
>
> Kind regards
>
> V/
>
>
>
>
>
> On 08 Jun 2021, at 07:45, Jacob <jacob01@REDACTED> wrote:
>
> Hi,
>
> I've tried to reproduce the measurement, but according to my
> measurements, there is just a factor of 2 on Erlang/OTP 22.
>
> 1> timer:tc(fun () -> bench:t2b(a, 1000000) end)
> {109357,<<131,100,0,1,97>>}
> 2> timer:tc(fun () -> bench:b2t(<<131,100,0,1,97>>, 1000000) end).
> {199488,a}
>
>
> If I do not use the result of each term_to_binary call, the factor (~14)
> is much closer to your measurements:
>
> 3> timer:tc(fun () -> bench:broken_t2b(a, 1000000) end).
> {14404,<<>>}
>
> Are you indeed sure, that the compiler did not optimise away the entire
> call?
>
> /Jacob
>
> ======================== bench.erl ==============================
> -module(bench).
>
> -export([t2b/2, b2t/2, broken_t2b/2]).
>
>
> t2b(T, N) -> t2b(T, N, undefined).
>
> t2b(_, 0, R) -> R;
> t2b(T, N, _) -> R = term_to_binary(T), t2b(T, N-1, R).
>
> b2t(T, N) -> b2t(T, N, undefined).
>
> b2t(_, 0, R) -> R;
> b2t(T, N, _) -> R = binary_to_term(T), b2t(T, N-1, R).
>
> broken_t2b(T, N) -> broken_t2b(T, N, undefined).
>
> broken_t2b(_, 0, R) -> R;
> broken_t2b(T, N, R) -> _ = term_to_binary(T), broken_t2b(T, N-1, R).
> =================================================================
>
>
> On 06.06.21 02:07, Valentin Micic wrote:
>
> Hi all,
>
> I did some performance measurement recently that included conversion of
> an arbitrary erlang term to its external binary representation via
> term_to_binary/1, as well as reversing the result using binary_to_term/1.
>
> I’ve noticed that term_to_binary/1 is significantly faster than
> binary_to_term/1.
>
> Also, I’ve observed that binary_to_term/1 performance gets considerably
> worse as complexity of specified term increases, whilst term_to_binary/1
> maintains (more-less) steady performance.
>
> (cig@REDACTED)40> tconvert:run( a, 10000000 ).
>
> term_to_binary/1 RETURN VALUE:<<131,100,0,1,97>>
> REQUEST COUNT:10000000
> ELAPSED TIME (usec):97070
> TIME PER REQUEST (usec): 0.009707
> PROJECTED RATE (req/sec): *103018440*.30081384
>
> binary_to_term/1 RETURN VALUE:a
> REQUEST COUNT:10000000
> ELAPSED TIME (usec):3383483
> TIME PER REQUEST (usec): 0.3383483
> PROJECTED RATE (req/sec): *2955534*.2822765773
> ok
>
> (cig@REDACTED)41> tconvert:run( {a,<<1,2,3>>, b, [1,2,3], c, {1,2,3},
> d, #{a=>1, b=>2, c=>3}}, 10000000 ).
>
> term_to_binary/1 RETURN
> VALUE:<<131,104,8,100,0,1,97,109,0,0,0,3,1,2,3,100,0,1,
>
> 98,107,0,3,1,2,3,100,0,1,99,104,3,97,1,97,2,97,
>
> 3,100,0,1,100,116,0,0,0,3,100,0,1,97,97,1,100,
>                                 0,1,98,97,2,100,0,1,99,97,3>>
> REQUEST COUNT:10000000
> ELAPSED TIME (usec):97307
> TIME PER REQUEST (usec): 0.0097307
> PROJECTED RATE (req/sec): *102767529*.57135664
>
> binary_to_term/1 RETURN VALUE:{a,<<1,2,3>>,
>                                  b,
>                                  [1,2,3],
>                                  c,
>                                  {1,2,3},
>                                  d,
>                                  #{a => 1,b => 2,c => 3}}
> REQUEST COUNT:10000000
> ELAPSED TIME (usec):8747426
> TIME PER REQUEST (usec): 0.8747426
> PROJECTED RATE (req/sec): *1143193*.4377038456
> ok
>
>
>
> I’ve performed testing on R21.1.
> Any thoughts?
>
> V/
>
>
>


-- 
Björn Gustavsson, Erlang/OTP, Ericsson AB


More information about the erlang-questions mailing list