[erlang-questions] Feedback for my first non-trivial Erlang program

Technion technion@REDACTED
Tue Dec 15 12:12:24 CET 2015


Sorry for the spam but continuing the theme further..

The next thing we can do is profile the function. I've run eprof here:

43> eprof:profile(fun() -> pension2:totalBalance(20) end).
{ok,41736.1717744699}
44> eprof:analyze().

****** Process <0.111.0>    -- 100.00 % of profiled time ***
FUNCTION                       CALLS        %  TIME  [uS / CALLS]
--------                       -----  -------  ----  [----------]
orddict:new/0                      1     0.01     1  [      1.00]
erl_eval:do_apply/6                1     0.01     1  [      1.00]
erl_eval:eval_fun/2                1     0.01     1  [      1.00]
erl_eval:guard0/4                  1     0.01     1  [      1.00]
pension2:saBalanceStart/0          1     0.01     1  [      1.00]
shell:apply_fun/3                  1     0.01     1  [      1.00]
erl_eval:exprs/5                   1     0.02     2  [      2.00]
erl_eval:eval_fun/6                1     0.02     2  [      2.00]
erl_eval:expr_list/6               2     0.02     2  [      1.00]
erl_eval:add_bindings/2            1     0.02     2  [      2.00]
erl_eval:'-expr/5-fun-3-'/1        1     0.02     2  [      2.00]
pension2:totalBalance/1            1     0.02     2  [      2.00]
pension2:ca_balance/1              1     0.02     2  [      2.00]
pension2:caBalanceStart/0          1     0.02     2  [      2.00]
shell:'-eval_loop/3-fun-0-'/3      1     0.02     2  [      2.00]
erl_eval:ret_expr/3                3     0.03     3  [      1.00]
erl_eval:guard/4                   1     0.03     3  [      3.00]
erl_eval:match_list/4              1     0.03     3  [      3.00]
erl_eval:new_bindings/0            1     0.03     3  [      3.00]
lists:reverse/1                    1     0.03     3  [      3.00]
erl_internal:bif/3                 1     0.03     3  [      3.00]
erl_eval:expr_list/4               1     0.04     4  [      4.00]
lists:foldl/3                      3     0.04     4  [      1.33]
erl_eval:merge_bindings/2          2     0.06     6  [      3.00]
orddict:to_list/1                  3     0.08     8  [      2.67]
erlang:apply/2                     1     0.09     9  [      9.00]
erl_eval:expr/5                    4     0.13    12  [      3.00]
pension2:ca_balance/2             21     0.29    28  [      1.33]
pension2:yieldRate/0              20     0.50    48  [      2.40]
pension2:tradingFees/1            20     0.51    49  [      2.45]
pension2:saBalance/1              21     0.56    53  [      2.52]
pension2:netIncome/0              60     0.75    71  [      1.18]
pension2:expensesStart/0          60     0.77    73  [      1.22]
pension2:investments/1            40     1.05   100  [      2.50]
pension2:transactionFee/0         60     1.47   140  [      2.33]
pension2:expenses/1              630    16.03  1528  [      2.43]
pension2:salary/1                630    16.17  1541  [      2.45]
pension2:working/1               630    16.38  1561  [      2.48]
pension2:workingTimeUnits/0      630    16.40  1563  [      2.48]
pension2:inflationRate/0        1140    28.23  2690  [      2.36]
-----------------------------  -----  -------  ----  [----------]
Total:                          4000  100.00%  9530  [      2.38]

I never bought the macros into the benchmarks because my suggestion was supposed to be purely a readability thing, but given what we see above, I've rewritten workingTimeUnits/0 and inflationRate/0   into macros. There definitely seemed to be an improvement:

58> timer:tc(pension2, totalBalance, [10]).
{24,30565.063527483566}
60> timer:tc(pension2, totalBalance, [60]).
{390,93052.91160889175}
61> timer:tc(pension2, totalBalance, [70]).
{444312,73294.80080230198}

And now replacing working/1 with T<?WORKINGTIMEUNITS everywhere it's called:
71> timer:tc(pension2, totalBalance, [70]).
{310477,73294.80080230198}
72> timer:tc(pension2, totalBalance, [60]).
{291,93052.91160889175}

That's a significant improvement from where we started.

eprof now puts salary/1 and expenses/1 as using 45% and 48% of the total algorithm time at this point. So the question becomes, can these be written in a diferent way?
________________________________________
From: erlang-questions-bounces@REDACTED <erlang-questions-bounces@REDACTED> on behalf of Technion <technion@REDACTED>
Sent: Tuesday, 15 December 2015 9:47 PM
To: zxq9; erlang-questions@REDACTED
Subject: Re: [erlang-questions] Feedback for my first non-trivial       Erlang  program

I'd like to continue this thread by discussing benchmarks. It's important to work out what optimisations help, and which don't. timer:tc/1 is useful for this. I've run this work some test values here. It's interesting that the time taken increases at much more than a linear rate.

8> timer:tc(pension2, totalBalance, [10]).
{30,30565.063527483566}
9> timer:tc(pension2, totalBalance, [20]).
{81,41736.17177446989}
12> timer:tc(pension2, totalBalance, [60]).
{840,93052.91160889174}
13> timer:tc(pension2, totalBalance, [70]).
{559006,73294.80080230196}

At 75, it ran so long I killed it.

With that said, here is the some of the same bechmarks, after applying zxq9's tail call optimisation.

18> timer:tc(pension2, totalBalance, [10]).
{28,30565.063527483566}
20> timer:tc(pension2, totalBalance, [30]).
{141,53541.01834136172}
21> timer:tc(pension2, totalBalance, [60]).
{823,93052.91160889175}
22> timer:tc(pension2, totalBalance, [70]).
{562409,73294.80080230198}

So there is definitely a measureable improvement there.

You can also write tradingFees in a way that helps recursion, as investments/1 seems to be the bulk of the work:

 tradingFees(T) ->
 X = case working(T) of
 true -> transactionFee();
 false -> tax(expenses(T),T) + transactionFee()
 end,
 X + investments(T).

Further improvements noted (slight):
24> timer:tc(pension2, totalBalance, [10]).
{26,30565.063527483566}
28> timer:tc(pension2, totalBalance, [70]).
{560813,73294.80080230198}

I don't understand what happened with remove/1, but the optimised solution seemed to slow things down in the high cases, but had notable improvements at the low end:

37> timer:tc(pension2, totalBalance, [10]).
{28,30565.063527483566}
41> timer:tc(pension2, totalBalance, [30]).
{246,53541.01834136172}
39> timer:tc(pension2, totalBalance, [60]).
{701,93052.91160889175}
38> timer:tc(pension2, totalBalance, [70]).
{581899,73294.80080230198}

All in all, there's a number of optimisations to be made, but I can see using this app with an input > 100 to still be painful. Is this algorithm documented in any other language anywhere? I'm curious as to whether there's a bigger picture being missed here.

I would also propose - writing some unit tests. It would be disastrous to "optimise" this code in such a way that the output changes. A few unit tests should address that.

Finally, I would like to suggest using github gists for this much code. Copy+pasting it from email lost most of the formatting and made it painful to read/manipulate.
_______________________________________________
erlang-questions mailing list
erlang-questions@REDACTED
http://erlang.org/mailman/listinfo/erlang-questions



More information about the erlang-questions mailing list