Performance of term_to_binary vs Bbinary_to_term

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


As Jacob suggested, I think the compiler has just optimised out the term_to_binary call.  On my machine, your original benchmark gives:

1> c(tconvert).
{ok,tconvert}
2> tconvert:run(a, 1000000).

term_to_binary/1 RETURN VALUE:<<131,100,0,1,97>>
REQUEST COUNT:1000000
ELAPSED TIME (usec):4707
TIME PER REQUEST (usec): 0.004707
PROJECTED RATE (req/sec): 212449543.23348203

binary_to_term/1 RETURN VALUE:a
REQUEST COUNT:1000000
ELAPSED TIME (usec):142883
TIME PER REQUEST (usec): 0.142883
PROJECTED RATE (req/sec): 6998733.229285499

So that factor-of-30 difference, similar to what you reported. I change the two loops to use the value from term_to_binary / binary_to_term:

do_term_to_bin( Term, _, 0 ) -> term_to_binary( Term );
do_term_to_bin( Term, _, N )
->
    X = term_to_binary( Term ),
    do_term_to_bin( Term, X, N-1 )
.

do_bin_to_term( <<Bin/binary>> , _, 0 ) -> binary_to_term( Bin );
do_bin_to_term( <<Bin/binary>> , _, N )
->
    X = binary_to_term( Bin ),
    do_bin_to_term( Bin , X, N-1 )
.

And that resulted in:

3> c(tconvert).             
{ok,tconvert}
4> tconvert:run(a, 1000000).

term_to_binary/1 RETURN VALUE:<<131,100,0,1,97>>
REQUEST COUNT:1000000
ELAPSED TIME (usec):68587
TIME PER REQUEST (usec): 0.068587
PROJECTED RATE (req/sec): 14580022.45323458

binary_to_term/1 RETURN VALUE:a
REQUEST COUNT:1000000
ELAPSED TIME (usec):130298
TIME PER REQUEST (usec): 0.130298
PROJECTED RATE (req/sec): 7674714.884342046

So binary_to_term about twice as slow as term_to_binary, which is very much in line with what Jacob measured

> On 8 Jun 2021, at 08:28, 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/
> 
> <tconvert.erl>
> 
> 
> 
> 
>> On 08 Jun 2021, at 07:45, Jacob <jacob01@REDACTED <mailto: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/
>> 
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20210608/ce3c8926/attachment.htm>


More information about the erlang-questions mailing list