Performance of term_to_binary vs Bbinary_to_term

Steve Strong steve@REDACTED
Tue Jun 8 10:12:46 CEST 2021


I’m guessing term_to_binary, if the result is not used, can always be optimised out since it would do nothing.  binary_to_term, on the other hand, can throw a badarg exception so removing it could change behaviour even if the result is not used

> On 8 Jun 2021, at 09:10, Björn Gustavsson <bjorn@REDACTED> wrote:
> 
> 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