[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