<div class="gmail_quote">On Mon, Jul 9, 2012 at 2:13 PM, Ulf Wiger <span dir="ltr"><<a href="mailto:ulf@feuerlabs.com" target="_blank">ulf@feuerlabs.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div style="word-wrap:break-word"><div class="im"><br><div><div>On 9 Jul 2012, at 12:34, CGS wrote:</div><br><blockquote type="cite"><span style="border-collapse:separate;font-family:Helvetica;font-style:normal;font-variant:normal;font-weight:normal;letter-spacing:normal;line-height:normal;text-align:-webkit-auto;text-indent:0px;text-transform:none;white-space:normal;word-spacing:0px;font-size:medium"><blockquote class="gmail_quote" style="margin-top:0px;margin-right:0px;margin-bottom:0px;margin-left:0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex">
<div>> About the two lists you were speaking about (I suppose you were referring to the output of lists:map/2 and lists:seq/2), the second is destroyed as soon as lists:map/2 exits.<br><br></div>No it isn't. It is only reclaimed when the garbage collector runs.<br>
</blockquote><div><br></div><div>That is a bit puzzling. Looking at the code for lists:map/2:</div><div><br></div><div><div>map(F, [H|T]) -></div><div> [F(H)|map(F, T)];</div><div>map(F, []) when is_function(F, 1) -> [].</div>
</div><div><br></div><div>I can see that when lists:map/2 exits, the only remaining list is an empty list. I might be wrong, but I see only one word remaining for the garbage collector to take care of. That means, at the end, there is only 1 full list remaining (1 word really can be neglected compared with 214 MB which I got for 10^7 characters long list).</div>
</span></blockquote><br></div></div><div>I believe Richard was referring to:</div><div><br></div><div>a) the list created using lists:seq(1,10000000)</div><div>b) the list that results from the call to lists:map/2</div><div>
<br></div><div>Actually, when you do this in the shell, the second list will be remembered in the command history (as well as in the variable bindings). The first list will, as Richard said, disappear when the garbage collector runs.</div>
</div></blockquote><div><br></div><div>Agree. Only that I have the impression that the first list is an empty list when lists:map/2 exits. As I have no way to stabilize my OS RAM consumption at the level of bytes, I don't really care about few words error and therefore I went for 10^6 - 10^7 characters list. The fact that I got in between 2 and 3 words per character supports my first impression. I might be wrong though.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><br></div><div>The quickest and most precise way of finding out how much RAM is needed for a particular data structure is:</div>
</div></blockquote><div><br></div><div>...theoretically...</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><br>
</div><div><div>Eshell V5.9 (abort with ^G)</div><div>1> L = lists:map(fun(_) -> 107 end,lists:seq(1,10000000)), ok.</div><div>ok</div></div><div><div>2> erts_debug:flat_size(L).</div><div>20000000</div></div></div>
</blockquote><div><br></div><div>I am a bit skeptical to use erts_debug:flat_size/1 as it is using the internal representation to compute the size and it doesn't report the actual physical memory consumption (see lib/hipe/cerl/erl_bif_types.erl and the list type definition in erl_types.erl from the same path), while `free' is providing the RAM usage as it is. Using erts_debug:flat_size(List) is quite the same with length(List)*(2 words) because for each list element ([Type,Term]) it adds 2 words (2*t_integer()) and not checking the actual memory consumption.</div>
<div><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><div><br></div><div>In this case, 20M words, which is very much in line with the documentation.</div>
</div></div></blockquote><div><br></div><div>Obviously. :)</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><div>
<br></div><div>If you want to figure out how much memory is actually used by the VM, this is more complicated. This will depend on how the data structure was built - i.e. how it exercised the garbage collector - and how much temporary fragmentation that caused. The Erlang VM is not designed to keep the memory footprint as small as possible, but, in a sense, to maintain a reasonably low level of fragmentation in a non-stop system without ruining the soft-real-time characteristics. If you perform an operation such as creating a list with 10M elements, and from that creating another list of similar size, this can be pretty hard on Erlang's garbage collector, since it can't predict when you are going to quit accumulating data.</div>
</div></div></blockquote><div><br></div><div>That is what I was referring to lower level memory management.</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<div style="word-wrap:break-word"><div><div><br></div><div>Let's try a very unscientific experiment:</div><div><br></div><div><div>3> exit(foo).</div><div>** exception exit: foo</div></div><div>4> timer:tc(fun() -> lists:map(fun(_) -> 107 end,lists:seq(1,10000000)), ok end).</div>
<div><div>{10025590,ok}</div><div><br></div><div>%% create a fun that evaluates F() in a fresh process with a really big heap</div></div><div><div><div>5> RPC = fun(F) -> {_,R} = spawn_opt(fun() -> exit({ok,F()}) end,[{min_heap_size,50000000},monitor]), receive {'DOWN',R,_,_,Res} -> {ok,Return} = Res, Return end end. </div>
<div>#Fun<erl_eval.6.111823515></div></div></div><div><div>6> exit(foo). </div><div>** exception exit: foo</div></div><div>7> timer:tc(fun() -> RPC(fun() -> lists:map(fun(_) -> 107 end,lists:seq(1,10000000)), ok end) end).</div>
<div><div>{5378929,ok}</div></div><div><br></div><div>(I did, in fact, run the timing operations several times and kept the best result.)</div></div></div></blockquote><div><br></div><div>Yeah, it's not easy to get the accurate time of execution. I usually perform the execution several times and make a weighted average value (by eliminating the extrema exceeding a certain error) to get an idea about the execution time.</div>
<div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><div><div><br></div><div>Of course, by creating a slave process, we add some processing overhead, compared to calling the function directly, but in this case it's insignificant, due to the long execution time. We almost reduce the time by half by eliminating a lot of expensive GC.</div>
<div><br></div><div>(If we actually return the big list from the slave process, this increases the cost a bit, but not by nearly as much, since the VM knows how much it needs to grow the heap in order to make room for the result.)</div>
</div></div></blockquote><div><br></div><div>Agree.</div><div><br></div><div><br></div><div>CGS</div><div><br></div></div>