<br><div class="gmail_quote"><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
The code below is an example that is supposed to allocate more<br>
and more memory until it is terminated.  While it is running,  it<br>
outputs roughly how much memory it has allocated so far.<br></blockquote><div><br>You could run with &quot;@MLton gc-messages --&quot; to see the actual heap sizes as the program runs.<br> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">


If I run the example with &quot;@MLton max-heap 2950M --&quot; run-time<br>
parameters then it keeps allocating memory,  the numbers printed<br>
by the example being roughly in sync with what Windows&#39; Task<br>
Manager shows.   Around 3GB it is terminated with the message<br>
&quot;Out of memory with max heap size 3,093,299,200&quot;,<br>
which seems like exactly what should happen.<br></blockquote><div><br>You can use &quot;@MLton max-heap 2.95G --&quot; and &quot;@MLton max-heap 3G --&quot; as shorter forms to express gigabyte sized heaps.  That is, the {max,fixed}-heap options accept a floating-point number (to express fractional portions of a size) and the &quot;G&quot;/&quot;g&quot; size modifier.<br>

</div><div> </div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
However,  if I run the example with &quot;@MLton max-heap 3000M --&quot;<br>
then around the the same point where it was was stopped before<br>
(the program reports roughly 3.05GB of allocated memory), my<br>
system starts thrashing and I can see in the Task Manager<br>
that the program has allocated around 4.5GB of memory and is still<br>
trying to get more.<br>
<br>
Is this what you would expect?  I thought that the max-heap setting<br>
would include the amount of additional memory required for garbage<br>
collection.  Is that true?  Or do I always have to just allow<br>
half of the system memory for max-heap to make sure to not allocate<br>
too much memory?<br></blockquote><div><br>You should run your program with &quot;@MLton gc-messages --&quot; to be sure, but I believe that the behavior you are seeing is explained by the heap resizing policy.  There is very little space overhead in the garbage collector and runtime over that consumed by the ML heap.  So, the max-heap option only bounds the size of the ML heap (which includes the card/cross map used for generational garbage collection).  MLton will never attempt to create a heap that is larger than the max-heap option.<br>
<br>However, MLton starts execution with a small heap and allows it to grow in response to the demands of the live data.  After each garbage collection, MLton decides whether to grow or shrink the heap in order to have the heap size approximately 8x the live data at the end of the garbage collection, further modified by the constraints of the available ram, max-heap, etc.  MLton will never try to *allocate* a heap that is larger than the max-heap option; however, in order to *obtain* a larger heap than the currently allocated heap, MLton may be required to allocate the new (bigger) heap and copy the data from the current heap over.  The idea is that the user would like their program to continue running; suffering a little bit of paging during the time that the two heaps are allocated is preferable to aborting with an out-of-memory error.  After the copy, the old heap is discarded and total memory usage is again bounded by max-heap.  (Note: This is *not* the policy for the copying collection; that is, during a copying collection, we do not have two heaps of max-heap size.  Instead, the semi-space size is fixed so as to be 1/2 of max-heap; or, if the live data is more than 1/2 the max-heap, the runtime will use the mark-compact collection.)<br>
<br>Obtaining the larger heap from the old heap actually happens in two ways.  For platforms that support it, we first attempt to mremap the existing heap to a larger size.  If we are able to expand to at least half-way between the current size and the desired size, we take that as acceptable growth, even if it isn&#39;t quite the full desired heap size.  If we can&#39;t expand, or the platform doesn&#39;t support mremap, then we attempt to create a heap of the desired size.  This, clearly, results in a second heap that is allocated while the existing heap is still allocated.  On a 32-bit system, allocating a second 3G+ heap while one heap of significant size is allocated will fail due to the virtual address space limitations; in that situation, and if the program is running with @MLton may-page-heap true --, MLton will attempt to write out the existing heap to a file, deallocate the existing heap, and now allocate the large heap.  We hope that by deallocating the existing heap, there is now sufficient contiguous address space to create the heap of sufficient size.  On a 64-bit system, there shouldn&#39;t ever be any trouble finding another 3G+ portion of the virtual address space while the existing heap is allocated.  I suspect that this is what you are seeing.<br>
<br>Note, that while Windows doesn&#39;t natively support mremap, Wesley implemented a few things that try to mimic mmap/mremap/munamp functionality under Windows.  There seems to be some complication with the Windows memory system that requires committing in addition to reserving memory, and some manner of being able to extend an existing map with space before it and after it.  I don&#39;t understand it.  However, I do note that the generic version of mremap (&lt;src&gt;/runtime/platform/mremap.c), which is used by the Windows platforms, starts off trying to allocate an entirely new region of the desired size and copy the existing region into it.  (This actually seems to be redundant with the behavior of the garbage collector proper.)  The comment states: &quot;Prefer a moving remap -&gt; it results in less mmapped regions&quot;.  Since such an allocation is likely to never fail on a 64-bit platform, you will (briefly) have multiple regions allocated at the same time, likely to exceed the max-heap setting.  Of course, that &quot;(briefly)&quot; is the time it takes to allocate a 3G region and copy the existing (nearly) 3G region; if you have &lt;6G physical memory, there will be paging.<br>
<br>As for why things happen differently with the different max-heap settings, I think that you just experience a slightly different heap growth trajectory.  This is a simple example, but suppose I compare running with fixed-heap 31M and fixed-heap 33M.  And suppose a very simple heap-size doubling policy, starting with a heap of size 2M.  So the first run will have heap sizes: 2M, 4M, 8M, 16M, 31M.  In order to transition from 16M to 31M, you might required 46M memory (the old and new heaps both allocated at the same time), but if you have enough RAM, you probably won&#39;t notice it, because it isn&#39;t too much worse than your desired max-heap.  The second run will have heap sizes: 2M, 4M, 8M, 16M, 32M, 33M.  Now, to transition from 32M to 33M, you require 65M memory, and that probably becomes noticeable, since it significantly exceeds your desired max-heap.<br>

</div></div>