1/*
2    Title:  heapsizing.cpp - parameters to adjust heap size
3
4    Copyright (c) Copyright David C.J. Matthews 2012, 2015, 2017
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License version 2.1 as published by the Free Software Foundation.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19*/
20
21/*
22This module is intended to deal with heap sizing based on measurements of the time taken
23in the GC compared with the application code.  Currently it is very basic.
24This also provides GC timing information to the ML code as well as statistics and
25debugging.
26*/
27
28#ifdef HAVE_CONFIG_H
29#include "config.h"
30#elif defined(_WIN32)
31#include "winconfig.h"
32#else
33#error "No configuration file"
34#endif
35
36#ifdef HAVE_WINDOWS_H
37#include <windows.h>
38#endif
39
40#ifdef HAVE_STRING_H
41#include <string.h>
42#endif
43
44#ifdef HAVE_UNISTD_H
45#include <unistd.h> // For sysconf
46#endif
47
48#ifdef HAVE_SYS_TYPES_H
49#include <sys/types.h>
50#endif
51
52#ifdef HAVE_SYS_SYSCTL_H
53#include <sys/sysctl.h>
54#endif
55
56#ifdef HAVE_FLOAT_H
57#include <float.h>
58#endif
59
60#ifdef HAVE_MATH_H
61#include <math.h>
62#endif
63
64#ifdef HAVE_ASSERT_H
65#include <assert.h>
66#define ASSERT(x)   assert(x)
67#else
68#define ASSERT(x)
69#endif
70
71#include "globals.h"
72#include "arb.h"
73#include "diagnostics.h"
74#include "rts_module.h"
75#include "timing.h"
76#include "heapsizing.h"
77#include "statistics.h"
78#include "memmgr.h"
79
80// The one and only parameter object
81HeapSizeParameters gHeapSizeParameters;
82
83#ifdef HAVE_WINDOWS_H
84// There's no (documented) way to get the per-process hard page
85// count in Windows.  Cygwin uses GetProcessMemoryInfo to return the
86// value in ru_majflt but this is actually incorrect because it returns
87// the soft page count not the hard page count.  We previously used the
88// undocumented NtQuerySystemInformation call.
89static long GetPaging(long)
90{
91    return 0;
92}
93#else
94inline long GetPaging(long rusagePage)
95{
96    return rusagePage;
97}
98#endif
99
100HeapSizeParameters::HeapSizeParameters()
101{
102    startPF = GetPaging(0);
103    fullGCNextTime = false;
104    performSharingPass = false;
105    lastAllocationSucceeded = true;
106    allocationFailedBeforeLastMajorGC = false;
107    minHeapSize = 0;
108    maxHeapSize = 0; // Unlimited
109    lastFreeSpace = 0;
110    pagingLimitSize = 0;
111    highWaterMark = 0;
112    sharingWordsRecovered = 0;
113    cumulativeSharingSaving = 0;
114    // Initial values until we've actually done a sharing pass.
115    sharingRecoveryRate = 0.5; // The structure sharing recovers half the heap.
116    sharingCostFactor = 2; // It doubles the cost
117}
118
119// These macros were originally in globals.h and used more generally.
120// Since only K_to_words is used now this can be greatly simplified.
121#define BITSPERWORD (sizeof(PolyWord)*8)
122#define ROUNDUP_UNITS(m,n)   (((m) + (n) - 1) / (n))
123#define ROUNDUP(m,n)   (ROUNDUP_UNITS(m,n) * (n))
124#define K_to_words(k) ROUNDUP((k) * (1024 / sizeof(PolyWord)),BITSPERWORD)
125
126// Returns physical memory size in bytes
127static POLYUNSIGNED GetPhysicalMemorySize(void);
128
129// These are the maximum values for the number of words.
130#if (SIZEOF_VOIDP == 4)
131#   define MAXIMUMADDRESS   0x3fffffff
132#else
133#   define MAXIMUMADDRESS   0x1fffffffffffffff
134#endif
135
136// Set the initial size based on any parameters specified on the command line.
137// Any of these can be zero indicating they should default.
138void HeapSizeParameters::SetHeapParameters(POLYUNSIGNED minsize, POLYUNSIGNED maxsize, POLYUNSIGNED initialsize, unsigned percent)
139{
140    minHeapSize = K_to_words(minsize); // If these overflow assume the result will be zero
141    maxHeapSize = K_to_words(maxsize);
142    POLYUNSIGNED initialSize = K_to_words(initialsize);
143
144    POLYUNSIGNED memsize = GetPhysicalMemorySize() / sizeof(PolyWord);
145
146    // If no maximum is given default it to 80% of the physical memory.
147    // This allows some space for the OS and other things.
148    if (maxHeapSize == 0 || maxHeapSize > MAXIMUMADDRESS)
149    {
150        if (memsize != 0)
151            maxHeapSize = memsize - memsize / 5;
152        else maxHeapSize = MAXIMUMADDRESS;
153        // But if this must not be smaller than the minimum size.
154        if (maxHeapSize < minHeapSize) maxHeapSize = minHeapSize;
155        if (maxHeapSize < initialSize) maxHeapSize = initialSize;
156    }
157
158    // The default minimum is zero; in practice the live data size.
159
160    // The default initial size is the minimum if that has been provided,
161    // otherwise 8M words.  There are applications that only require a small
162    // heap and if we set the heap large to begin with we'll never do a
163    // full GC and reduce it.
164    if (initialSize == 0)
165    {
166        if (minHeapSize != 0)
167            initialSize = minHeapSize;
168        else initialSize = 8 * gMem.DefaultSpaceSize();
169        // But not more than the maximum
170        if (initialSize > maxHeapSize) initialSize = maxHeapSize;
171    }
172    // Together with the constraints on user settings that ensures this holds.
173    ASSERT(initialSize >= minHeapSize && initialSize <= maxHeapSize);
174
175    // Initially we divide the space equally between the major and
176    // minor heaps.  That means that there will definitely be space
177    // for the first minor GC to copy its data.  This division can be
178    // changed later on.
179    gMem.SetSpaceForHeap(initialSize);
180    gMem.SetSpaceBeforeMinorGC(initialSize/2);
181    lastFreeSpace = initialSize;
182    highWaterMark = initialSize;
183
184    if (percent == 0)
185        userGCRatio = 1.0 / 9.0; // Default to 10% GC to 90% application
186    else
187        userGCRatio = (float)percent / (float)(100 - percent);
188
189    predictedRatio = lastMajorGCRatio = userGCRatio;
190
191    if (debugOptions & DEBUG_HEAPSIZE)
192    {
193        Log("Heap: Initial settings: Initial heap ");
194        LogSize(initialSize);
195        Log(" minimum ");
196        LogSize(minHeapSize);
197        Log(" maximum ");
198        LogSize(maxHeapSize);
199        Log(" target ratio %f\n", userGCRatio);
200    }
201}
202
203void HeapSizeParameters::SetReservation(POLYUNSIGNED rsize)
204{
205    gMem.SetReservation(K_to_words(rsize));
206}
207
208// Called in the minor GC if a GC thread needs to grow the heap.
209// Returns zero if the heap cannot be grown. "space" is the space required for the
210// object (and length field) in case this is larger than the default size.
211LocalMemSpace *HeapSizeParameters::AddSpaceInMinorGC(POLYUNSIGNED space, bool isMutable)
212{
213    // See how much space is allocated to the major heap.
214    POLYUNSIGNED spaceAllocated = gMem.CurrentHeapSize() - gMem.CurrentAllocSpace();
215
216    // The new segment is either the default size or as large as
217    // necessary for the object.
218    POLYUNSIGNED spaceSize = gMem.DefaultSpaceSize();
219    if (space > spaceSize) spaceSize = space;
220
221    // We allow for extension if the total heap size after extending it
222    // plus one allocation area of the default size would not be more
223    // than the allowed heap size.
224    if (spaceAllocated + spaceSize + gMem.DefaultSpaceSize() <= gMem.SpaceForHeap())
225    {
226        LocalMemSpace *sp = gMem.NewLocalSpace(spaceSize, isMutable); // Return the space or zero if it failed
227        // If this is the first time the allocation failed report it.
228        if (sp == 0 && (debugOptions & DEBUG_HEAPSIZE) && lastAllocationSucceeded)
229        {
230            Log("Heap: Allocation of new heap segment size ");
231            LogSize(spaceSize);
232            Log(" failed.  Limit reached?\n");
233        }
234        lastAllocationSucceeded = sp != 0;
235        return sp;
236    }
237    return 0; // Insufficient space
238}
239
240// Called in the major GC before the copy phase if the heap is more than
241// 90% full.  This should improve the efficiency of copying.
242LocalMemSpace *HeapSizeParameters::AddSpaceBeforeCopyPhase(bool isMutable)
243{
244    LocalMemSpace *sp = gMem.NewLocalSpace(gMem.DefaultSpaceSize(), isMutable);
245    if (sp == 0 && (debugOptions & DEBUG_HEAPSIZE) && lastAllocationSucceeded)
246        Log("Heap: Allocation of new heap segment failed.  Limit reached?\n");
247    lastAllocationSucceeded = sp != 0;
248    return sp;
249}
250
251// The steepness of the curve.
252#define PAGINGCOSTSTEEPNESS 20.0
253// The additional cost at the boundary
254#define PAGINGCOSTFACTOR    3.0
255// The number of pages at the boundary
256#define PAGINGCOUNTFACTOR   1000.0
257
258// Called at the end of collection.  This is where we should do the
259// fine adjustment of the heap size to minimise the GC time.
260// Growing the heap is just a matter of adjusting the limits.  We
261// don't actually need to allocate the space here.
262// See also adjustHeapSizeAfterMinorGC for adjustments after a minor GC.
263void HeapSizeParameters::AdjustSizeAfterMajorGC(POLYUNSIGNED wordsRequired)
264{
265    // Cumulative times since the last major GC
266    TIMEDATA gc, nonGc;
267    gc.add(majorGCSystemCPU);
268    gc.add(majorGCUserCPU);
269    nonGc.add(majorNonGCSystemCPU);
270    nonGc.add(majorNonGCUserCPU);
271
272    if (highWaterMark < heapSizeAtStart) highWaterMark = heapSizeAtStart;
273
274    POLYUNSIGNED heapSpace = gMem.SpaceForHeap() < highWaterMark ? gMem.SpaceForHeap() : highWaterMark;
275    currentSpaceUsed = wordsRequired;
276    for (std::vector<LocalMemSpace*>::iterator i = gMem.lSpaces.begin(); i < gMem.lSpaces.end(); i++)
277    {
278        currentSpaceUsed += (*i)->allocatedSpace();
279    }
280    // N.B.  Normally currentSpaceUsed will be less than the size of the heap
281    // except if wordsRequired is very large.
282
283    // The times for all the minor GCs up to this.  The cost of this (major) GC
284    // is actually in minorGCUserCPU/minorGCSystemCPU.
285    TIMEDATA minorGC;
286    minorGC.add(gc);
287    minorGC.sub(minorGCUserCPU);
288    minorGC.sub(minorGCSystemCPU);
289
290    if (performSharingPass)
291    {
292        // We ran the sharing pass last time: calculate the actual recovery rate.
293        POLYUNSIGNED originalSpaceUsed = currentSpaceUsed + sharingWordsRecovered;
294        sharingRecoveryRate = (double)sharingWordsRecovered / (double)originalSpaceUsed;
295        if (debugOptions & DEBUG_HEAPSIZE)
296            Log("Heap: Sharing recovery rate was %0.3f and cost %0.3f seconds (%0.3f%% of total).\n",
297                sharingRecoveryRate, sharingCPU.toSeconds(), sharingCPU.toSeconds() / gc.toSeconds());
298        // The cost factor is the ratio of the cost of sharing to the cost without.
299        sharingCostFactor = sharingCPU.toSeconds() / (gc.toSeconds() - sharingCPU.toSeconds());
300        // Subtract the sharing cost from the GC cost because the initial estimate is
301        // the cost without running the sharing pass.
302        gc.sub(sharingCPU);
303    }
304
305    if (gc.toSeconds() != 0.0 && nonGc.toSeconds() != 0.0)
306        lastMajorGCRatio = gc.toSeconds() / nonGc.toSeconds();
307
308    if (debugOptions & DEBUG_HEAPSIZE)
309    {
310        POLYUNSIGNED currentFreeSpace = currentSpaceUsed < heapSpace ? 0: heapSpace - currentSpaceUsed;
311        Log("Heap: GC cpu time %2.3f non-gc time %2.3f ratio %0.3f for free space ",
312            gc.toSeconds(), nonGc.toSeconds(), lastMajorGCRatio);
313        LogSize((lastFreeSpace + currentFreeSpace)/2);
314        Log("\n");
315        Log("Heap: GC real time %2.3f non-gc time %2.3f ratio %0.3f\n",
316            majorGCReal.toSeconds(), majorNonGCReal.toSeconds(), majorGCReal.toSeconds()/majorNonGCReal.toSeconds());
317        Log("Heap: Total of minor GCs %2.3f, %2.3f of total\n", minorGC.toSeconds(), minorGC.toSeconds() / gc.toSeconds());
318    }
319
320    // Calculate the paging threshold.
321    if (pagingLimitSize != 0 || majorGCPageFaults != 0)
322    {
323        if (majorGCPageFaults == 0) majorGCPageFaults = 1; // Less than one
324        // Some paging detected.  The expression here is the inverse of the one used to
325        // compute the paging contribution in the cost function.
326        double scaleFactor = 1.0 + log((double)majorGCPageFaults / PAGINGCOUNTFACTOR) / PAGINGCOSTSTEEPNESS;
327        ASSERT(scaleFactor > 0.0);
328        POLYUNSIGNED newLimit = (POLYUNSIGNED)((double)heapSpace / scaleFactor);
329        if (pagingLimitSize == 0)
330            pagingLimitSize = newLimit;
331        else
332            pagingLimitSize = (newLimit + pagingLimitSize) / 2;
333    }
334    if (allocationFailedBeforeLastMajorGC)
335    {
336        // If the last allocation failed then we may well have reached the
337        // maximum available memory.  Set the paging limit to be the current
338        // heap size.  We want to avoid hitting the limit because typically
339        // that happens when we try to extend the major heap in a minor GC
340        // resulting in the minor GC failing and a major GC starting.
341        if (pagingLimitSize == 0 || heapSizeAtStart < pagingLimitSize)
342            pagingLimitSize = heapSizeAtStart;
343    }
344    if (pagingLimitSize != 0 && (debugOptions & DEBUG_HEAPSIZE))
345    {
346        Log("Heap: Paging threshold adjusted to ");
347        LogSize(pagingLimitSize);
348        Log(" with %ld page faults\n", majorGCPageFaults);
349    }
350
351    // Calculate the new heap size and the predicted cost.
352    POLYUNSIGNED newHeapSize;
353    double cost;
354    bool atTarget = getCostAndSize(newHeapSize, cost, false);
355    // If we have been unable to allocate any more memory we may already
356    // be at the limit.
357    if (allocationFailedBeforeLastMajorGC && newHeapSize > heapSizeAtStart)
358    {
359        cost = costFunction(heapSizeAtStart, false, true);
360        atTarget = false;
361    }
362
363    if (atTarget)
364    {
365        // We are at the target level.  We don't want to attempt sharing.
366        performSharingPass = false;
367        cumulativeSharingSaving = 0;
368    }
369    else
370    {
371        POLYUNSIGNED newHeapSizeWithSharing;
372        double costWithSharing;
373        // Get the cost and heap size if sharing was enabled.  If we are at the
374        // limit, though, we need to work using the size we can achieve.
375        if (! allocationFailedBeforeLastMajorGC)
376            (void)getCostAndSize(newHeapSizeWithSharing, costWithSharing, true);
377        else
378        {
379            newHeapSizeWithSharing = heapSizeAtStart;
380            costWithSharing = costFunction(heapSizeAtStart, true, true);
381        }
382        // Run the sharing pass if that would give a lower cost.
383        // Subtract the cumulative saving that would have been made if the
384        // sharing had been run before.  This is an estimate and depends on the
385        // extent to which a reduction in the heap earlier would be carried through
386        // to later GCs.
387        cumulativeSharingSaving =
388            cumulativeSharingSaving * ((double)currentSpaceUsed / (double)heapSpace);
389        if (debugOptions & DEBUG_HEAPSIZE)
390            Log("Heap: Cumulative sharing saving %0.2f\n", cumulativeSharingSaving);
391        if (costWithSharing - cumulativeSharingSaving < cost)
392        {
393            // Run the sharing pass next time.
394            performSharingPass = true;
395            cumulativeSharingSaving = 0;
396        }
397        else
398        {
399            // Don't run the sharing pass next time
400            performSharingPass = false;
401            // Running a sharing pass reduces the heap for subsequent
402            // runs.  Add this into the cost.
403            double freeSharingCost = costFunction(newHeapSizeWithSharing, true, false);
404            if (freeSharingCost < cost && freeSharingCost > userGCRatio)
405            {
406                if (debugOptions & DEBUG_HEAPSIZE)
407                    Log("Heap: Previous sharing would have saved %0.2f\n", cost - freeSharingCost);
408                cumulativeSharingSaving += cost - freeSharingCost;
409            }
410        }
411    }
412
413    if (debugOptions & DEBUG_HEAPSIZE)
414    {
415        if (performSharingPass)
416            Log("Heap: Next full GC will enable the sharing pass\n");
417        Log("Heap: Resizing from ");
418        LogSize(gMem.SpaceForHeap());
419        Log(" to ");
420        LogSize(newHeapSize);
421        Log(".  Estimated ratio %2.2f\n", cost);
422    }
423    // Set the sizes.
424    gMem.SetSpaceForHeap(newHeapSize);
425    // Set the minor space size.  It can potentially use the whole of the
426    // rest of the available heap but there could be a problem if that exceeds
427    // the available memory and causes paging.  We need to raise the limit carefully.
428    // Also, if we use the whole of the heap we may not then be able to allocate
429    // new areas in the major heap without going over the limit.  Restrict it to
430    // half of the available heap.
431    POLYUNSIGNED nextLimit = highWaterMark + highWaterMark / 32;
432    if (nextLimit > newHeapSize) nextLimit = newHeapSize;
433    // gMem.CurrentHeapSize() is the live space size.
434    if (gMem.CurrentHeapSize() > nextLimit)
435        gMem.SetSpaceBeforeMinorGC(0); // Run out of space
436    else gMem.SetSpaceBeforeMinorGC((nextLimit-gMem.CurrentHeapSize())/2);
437
438    lastFreeSpace = newHeapSize - currentSpaceUsed;
439    predictedRatio = cost;
440}
441
442// Called after a minor GC.  Currently does nothing.
443// See also adjustHeapSize for adjustments after a major GC.
444bool HeapSizeParameters::AdjustSizeAfterMinorGC(POLYUNSIGNED spaceAfterGC, POLYUNSIGNED spaceBeforeGC)
445{
446    POLYUNSIGNED spaceCopiedOut = spaceAfterGC-spaceBeforeGC;
447    TIMEDATA gc, total;
448    minorGCsSinceMajor++;
449    // The major costs are cumulative so we use those
450    gc.add(majorGCSystemCPU);
451    gc.add(majorGCUserCPU);
452    total.add(gc);
453    total.add(majorNonGCSystemCPU);
454    total.add(majorNonGCUserCPU);
455    float g = gc.toSeconds() / total.toSeconds();
456
457    if (debugOptions & DEBUG_HEAPSIZE)
458    {
459        Log("Heap: Space before ");
460        LogSize(spaceBeforeGC);
461        Log(", space after ");
462        LogSize(spaceAfterGC);
463        Log("\n");
464        Log("Heap: Minor resizing factors g = %f, recent pf = %ld, cumulative pf = %ld\n",
465            g, minorGCPageFaults, majorGCPageFaults);
466    }
467
468    if (highWaterMark < gMem.CurrentHeapSize()) highWaterMark = gMem.CurrentHeapSize();
469
470    POLYUNSIGNED nextLimit = highWaterMark + highWaterMark / 32;
471    if (nextLimit > gMem.SpaceForHeap()) nextLimit = gMem.SpaceForHeap();
472
473    // Set the space available for the allocation area to be the difference between the
474    // total heap size and the allowed heap size together with as much space as we copied
475    // on this GC.  That allows for the next minor GC to copy the same amount without
476    // extending the heap.  If the next minor GC adds more than this the heap will be
477    // extended and a corresponding amount deducted so that the heap shrinks again.
478    POLYUNSIGNED currHeap = gMem.CurrentHeapSize();
479    POLYUNSIGNED currAlloc = gMem.CurrentAllocSpace();
480    POLYUNSIGNED nonAlloc = currHeap - currAlloc + spaceCopiedOut;
481    // TODO: If we have limited the space to the high water mark + 1/32 but that is less
482    // than we really need we should increase it further.
483    POLYUNSIGNED allowedAlloc = nonAlloc >= nextLimit ? 0 : nextLimit - nonAlloc;
484    // Normally the allocation area will be empty but if we've failed to copy
485    // everything out, especially a big object, it may not be.
486    POLYUNSIGNED allocatedInAlloc = gMem.AllocatedInAlloc();
487
488    // If we hit the limit at the last major GC we have to be much more careful.
489    // If the minor GC cannot allocate a major GC space when it needs it the minor
490    // GC will fail immediately and a major GC will be started.  It's better to
491    // risk doing more minor GCs than we need by making the allocation area smaller
492    // rather than run out of space.
493    if (allocationFailedBeforeLastMajorGC)
494        allowedAlloc = allowedAlloc / 2;
495    if (gMem.CurrentAllocSpace() - allocatedInAlloc != allowedAlloc)
496    {
497        if (debugOptions & DEBUG_HEAPSIZE)
498        {
499            Log("Heap: Adjusting space for allocation area from ");
500            LogSize(gMem.SpaceBeforeMinorGC());
501            Log(" to ");
502            LogSize(allowedAlloc);
503            Log("\n");
504        }
505        gMem.SetSpaceBeforeMinorGC(allowedAlloc);
506        if (allowedAlloc < gMem.DefaultSpaceSize() * 2 || minorGCPageFaults > 100)
507            return false; // Trigger full GC immediately.
508     }
509
510    // Trigger a full GC if the live data is very large or if we have exceeeded
511    // the target ratio over several GCs (this smooths out small variations).
512    if ((minorGCsSinceMajor > 4 && g > predictedRatio*0.8) || majorGCPageFaults > 100)
513        fullGCNextTime = true;
514    return true;
515}
516
517// Estimate the GC cost for a given heap size.  The result is the ratio of
518// GC time to application time.
519// This is really guesswork.
520double HeapSizeParameters::costFunction(POLYUNSIGNED heapSize, bool withSharing, bool withSharingCost)
521{
522    POLYUNSIGNED heapSpace = gMem.SpaceForHeap() < highWaterMark ? gMem.SpaceForHeap() : highWaterMark;
523    POLYUNSIGNED currentFreeSpace = heapSpace < currentSpaceUsed ? 0: heapSpace - currentSpaceUsed;
524    POLYUNSIGNED averageFree = (lastFreeSpace + currentFreeSpace) / 2;
525    POLYUNSIGNED spaceUsed = currentSpaceUsed; // N.B.  currentSpaceUsed includes the new space we want
526    if (heapSize <= currentSpaceUsed)
527        return 1.0E6;
528    // If we run the sharing pass the live space will be smaller.
529    if (withSharing)
530        spaceUsed -= (POLYUNSIGNED)((double)currentSpaceUsed * sharingRecoveryRate);
531    POLYUNSIGNED estimatedFree = heapSize - spaceUsed;
532    // The cost scales as the inverse of the amount of free space.
533    double result = lastMajorGCRatio * (double)averageFree / (double)estimatedFree;
534    // If we run the sharing pass the GC cost will increase.
535    if (withSharing && withSharingCost)
536        result += result*sharingCostFactor;
537
538    // The paging contribution depends on the page limit
539    double pagingCost = 0.0;
540    if (pagingLimitSize != 0)
541    {
542        double factor = ((double)heapSize - (double)pagingLimitSize) / (double)pagingLimitSize * PAGINGCOSTSTEEPNESS;
543        pagingCost = PAGINGCOSTFACTOR * exp(factor);
544        result += pagingCost;
545    }
546
547    if (debugOptions & DEBUG_HEAPSIZE)
548    {
549        Log("Heap: Cost for heap of size ");
550        LogSize(heapSize);
551        Log(" is %2.2f with paging contributing %2.2f with%s sharing pass.\n", result, pagingCost, withSharing ? "" : "out");
552    }
553    return result;
554}
555
556// Calculate the size for the minimum cost.  Returns true if this is bounded by
557// the user GC ratio and false if we minimised the cost
558// TODO: This could definitely be improved although it's not likely to contribute much to
559// the overall cost of a GC.
560bool HeapSizeParameters::getCostAndSize(POLYUNSIGNED &heapSize, double &cost, bool withSharing)
561{
562    bool isBounded = false;
563    POLYUNSIGNED heapSpace = gMem.SpaceForHeap() < highWaterMark ? gMem.SpaceForHeap() : highWaterMark;
564    // Calculate a new heap size.  We allow a maximum doubling or halving of size.
565    // It's probably more important to limit the increase in case we hit paging.
566    POLYUNSIGNED sizeMax = heapSpace * 2;
567    if (sizeMax > maxHeapSize) sizeMax = maxHeapSize;
568    POLYUNSIGNED sizeMin = heapSpace / 2;
569    if (sizeMin < minHeapSize) sizeMin = minHeapSize;
570    // We mustn't reduce the heap size too far.  If the application does a lot
571    // of work with few allocations and particularly if it calls PolyML.fullGC
572    // explicitly we could attempt to shrink the heap below the current live data size.
573    // Add 3*space size here.  We require 2* after a minor GC. Add 1 for rounding.
574    POLYUNSIGNED minForAllocation = gMem.CurrentHeapSize() + gMem.DefaultSpaceSize() * 3;
575    if (minForAllocation > maxHeapSize) minForAllocation = maxHeapSize;
576    if (sizeMin < minForAllocation) sizeMin = minForAllocation;
577
578    double costMin = costFunction(sizeMin, withSharing, true);
579    if (costMin <= userGCRatio)
580        // If the cost of the minimum is below or at the target we
581        // use that and don't need to look further.
582        isBounded = true;
583    else
584    {
585        double costMax = costFunction(sizeMax, withSharing, true);
586        while (sizeMax > sizeMin + gMem.DefaultSpaceSize())
587        {
588            POLYUNSIGNED sizeNext = (sizeMin + sizeMax) / 2;
589            double cost = costFunction(sizeNext, withSharing, true);
590            if (cost < userGCRatio)
591                isBounded = true;
592            if (cost < userGCRatio || (costMax > costMin && costMax > userGCRatio))
593            {
594                sizeMax = sizeNext;
595                costMax = cost;
596            }
597            else
598            {
599                sizeMin = sizeNext;
600                costMin = cost;
601            }
602            ASSERT(costMin >= userGCRatio);
603        }
604    }
605    ASSERT(sizeMin >= minHeapSize && sizeMin <= maxHeapSize);
606    // If we are bounded by the user GC ratio we actually return the size and cost
607    // that is slightly above the user ratio.
608    heapSize = sizeMin;
609    cost = costMin;
610    return isBounded;
611}
612
613bool HeapSizeParameters::RunMajorGCImmediately()
614{
615    if (fullGCNextTime)
616    {
617        fullGCNextTime = false;
618        return true;
619    }
620    return false;
621}
622
623
624static bool GetLastStats(TIMEDATA &userTime, TIMEDATA &systemTime, TIMEDATA &realTime, long &pageCount)
625{
626#if (defined(_WIN32) && ! defined(__CYGWIN__))
627    FILETIME kt, ut;
628    FILETIME ct, et; // Unused
629    FILETIME rt;
630    GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut);
631    GetSystemTimeAsFileTime(&rt);
632    userTime = ut;
633    systemTime = kt;
634    realTime = rt;
635    pageCount = GetPaging(0);
636#else
637    struct rusage rusage;
638    if (getrusage(RUSAGE_SELF, &rusage) != 0)
639        return false;
640    userTime = rusage.ru_utime;
641    systemTime = rusage.ru_stime;
642    struct timeval tv;
643    if (gettimeofday(&tv, NULL) != 0)
644        return false;
645    realTime = tv;
646    pageCount = GetPaging(rusage.ru_majflt);
647#endif
648    return true;
649}
650
651void HeapSizeParameters::RecordAtStartOfMajorGC()
652{
653    heapSizeAtStart = gMem.CurrentHeapSize();
654    allocationFailedBeforeLastMajorGC = !lastAllocationSucceeded;
655}
656
657// This function is called at the beginning and end of garbage
658// collection to record the time used.
659// This also reports the GC time if GC debugging is enabled.
660void HeapSizeParameters::RecordGCTime(gcTime isEnd, const char *stage)
661{
662    switch (isEnd)
663    {
664    case GCTimeStart:
665        {
666            // Start of GC
667            TIMEDATA userTime, systemTime, realTime;
668            long pageCount;
669            if (! GetLastStats(userTime, systemTime, realTime, pageCount))
670                break;
671            lastUsageU = userTime;
672            lastUsageS = systemTime;
673            lastRTime = realTime;
674            userTime.sub(startUsageU);  // Times since the start
675            systemTime.sub(startUsageS);
676            realTime.sub(startRTime);
677            if (debugOptions & DEBUG_GC)
678                Log("GC: Non-GC time: CPU user: %0.3f system: %0.3f real: %0.3f page faults: %ld\n",
679                    userTime.toSeconds(), systemTime.toSeconds(), realTime.toSeconds(), pageCount - startPF);
680            minorNonGCUserCPU.add(userTime);
681            majorNonGCUserCPU.add(userTime);
682            minorNonGCSystemCPU.add(systemTime);
683            majorNonGCSystemCPU.add(systemTime);
684            minorNonGCReal.add(realTime);
685            majorNonGCReal.add(realTime);
686            startUsageU = lastUsageU;
687            startUsageS = lastUsageS;
688            startRTime = lastRTime;
689            // Page faults in the application are included
690            minorGCPageFaults += pageCount - startPF;
691            majorGCPageFaults += pageCount - startPF;
692            startPF = pageCount;
693            break;
694        }
695
696    case GCTimeIntermediate:
697        // Report intermediate GC time for debugging
698        if (debugOptions & DEBUG_GC)
699        {
700            TIMEDATA userTime, systemTime, realTime;
701            long pageCount;
702            if (! GetLastStats(userTime, systemTime, realTime, pageCount))
703                break;
704            TIMEDATA nextU = userTime, nextS = systemTime, nextR = realTime;
705            userTime.sub(lastUsageU);
706            systemTime.sub(lastUsageS);
707            realTime.sub(lastRTime);
708
709            Log("GC: (%s) CPU user: %0.3f system: %0.3f real: %0.3f speed up %0.1f\n", stage, userTime.toSeconds(),
710                systemTime.toSeconds(), realTime.toSeconds(),
711                realTime.toSeconds() == 0.0 ? 0.0 : (userTime.toSeconds() + systemTime.toSeconds()) / realTime.toSeconds());
712            lastUsageU = nextU;
713            lastUsageS = nextS;
714            lastRTime = nextR;
715        }
716        break;
717
718    case GCTimeEnd: // End of GC.
719        {
720            TIMEDATA userTime, systemTime, realTime;
721            long pageCount;
722            if (! GetLastStats(userTime, systemTime, realTime, pageCount))
723                break;
724            lastUsageU = userTime;
725            lastUsageS = systemTime;
726            lastRTime = realTime;
727
728            userTime.sub(startUsageU);  // Times since the start
729            systemTime.sub(startUsageS);
730            realTime.sub(startRTime);
731
732            totalGCUserCPU.add(userTime);
733            totalGCSystemCPU.add(systemTime);
734            totalGCReal.add(realTime);
735
736            if (debugOptions & DEBUG_GC)
737            {
738                Log("GC: CPU user: %0.3f system: %0.3f real: %0.3f speed up %0.1f page faults %ld\n", userTime.toSeconds(),
739                    systemTime.toSeconds(), realTime.toSeconds(),
740                    realTime.toSeconds() == 0.0 ? 0.0 : (userTime.toSeconds() + systemTime.toSeconds()) / realTime.toSeconds(),
741                    pageCount - startPF);
742            }
743            minorGCUserCPU.add(userTime);
744            majorGCUserCPU.add(userTime);
745            minorGCSystemCPU.add(systemTime);
746            majorGCSystemCPU.add(systemTime);
747            minorGCReal.add(realTime);
748            majorGCReal.add(realTime);
749            startUsageU = lastUsageU;
750            startUsageS = lastUsageS;
751            startRTime = lastRTime;
752            minorGCPageFaults += pageCount - startPF;
753            majorGCPageFaults += pageCount - startPF;
754            startPF = pageCount;
755            globalStats.copyGCTimes(totalGCUserCPU, totalGCSystemCPU);
756        }
757        break;
758    }
759}
760
761// Record the recovery rate and cost after running the GC sharing pass.
762// TODO: We should probably average these because if we've run a full
763// sharing pass and then a full GC after the recovery rate will be zero.
764void HeapSizeParameters::RecordSharingData(POLYUNSIGNED recovery)
765{
766    sharingWordsRecovered = recovery;
767    TIMEDATA userTime, systemTime, realTime;
768    long pageCount;
769    if (! GetLastStats(userTime, systemTime, realTime, pageCount))
770        return;
771    userTime.sub(startUsageU);  // Times since the start
772    systemTime.sub(startUsageS);
773    sharingCPU = userTime;
774    sharingCPU.add(systemTime);
775}
776
777Handle HeapSizeParameters::getGCUtime(TaskData *taskData) const
778{
779#if (defined(_WIN32) && ! defined(__CYGWIN__))
780    return Make_arb_from_Filetime(taskData, totalGCUserCPU);
781#else
782    return Make_arb_from_pair_scaled(taskData, ((struct timeval)totalGCUserCPU).tv_sec, ((struct timeval)totalGCUserCPU).tv_usec, 1000000);
783#endif
784}
785
786Handle HeapSizeParameters::getGCStime(TaskData *taskData) const
787{
788#if (defined(_WIN32) && ! defined(__CYGWIN__))
789    return Make_arb_from_Filetime(taskData, totalGCSystemCPU);
790#else
791    return Make_arb_from_pair_scaled(taskData, ((struct timeval)totalGCSystemCPU).tv_sec, ((struct timeval)totalGCSystemCPU).tv_usec, 1000000);
792#endif
793}
794
795void HeapSizeParameters::Init()
796{
797#if (defined(_WIN32) && ! defined(__CYGWIN__))
798    // Record an initial time of day to use as the basis of real timing
799    FILETIME s;
800    GetSystemTimeAsFileTime(&s);
801#else
802    struct timeval s;
803    gettimeofday(&s, NULL);
804#endif
805    startTime = s;  // Overall start time
806    startRTime = startTime; // Start of this non-gc phase
807
808    resetMajorTimingData();
809#if (defined(_WIN32) && ! defined(__CYGWIN__))
810    startPF = GetPaging(0);
811#else
812    startPF = GetPaging(0);
813#endif
814}
815
816void HeapSizeParameters::Final()
817{
818    // Print the overall statistics
819    if (debugOptions & (DEBUG_GC|DEBUG_HEAPSIZE))
820    {
821        TIMEDATA userTime, systemTime, realTime;
822#if (defined(_WIN32) && ! defined(__CYGWIN__))
823        FILETIME kt, ut;
824        FILETIME ct, et; // Unused
825        FILETIME rt;
826        GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut);
827        GetSystemTimeAsFileTime(&rt);
828        userTime.add(ut);
829        systemTime.add(kt);
830        realTime.add(rt);
831 #else
832        struct rusage rusage;
833        struct timeval tv;
834        if (getrusage(RUSAGE_SELF, &rusage) != 0 || gettimeofday(&tv, NULL) != 0)
835            return;
836        userTime.add(rusage.ru_utime);
837        systemTime.add(rusage.ru_stime);
838        realTime.add(tv);
839#endif
840        realTime.sub(startTime);
841        userTime.sub(totalGCUserCPU);
842        systemTime.sub(totalGCSystemCPU);
843        realTime.sub(totalGCReal);
844        if (debugOptions & DEBUG_GC)
845        {
846            Log("GC (Total): Non-GC time: CPU user: %0.3f system: %0.3f real: %0.3f\n",
847                userTime.toSeconds(), systemTime.toSeconds(), realTime.toSeconds());
848            Log("GC (Total): GC time: CPU user: %0.3f system: %0.3f real: %0.3f\n",
849                totalGCUserCPU.toSeconds(), totalGCSystemCPU.toSeconds(), totalGCReal.toSeconds());
850        }
851        if (debugOptions & DEBUG_HEAPSIZE)
852        {
853            TIMEDATA gc, nonGc;
854            gc.add(totalGCUserCPU);
855            gc.add(totalGCSystemCPU);
856            nonGc.add(userTime);
857            nonGc.add(systemTime);
858            Log("Heap: Total CPU GC time %0.3fsecs,  Non-GC %0.3fsecs, ratio %0.3f\n",
859                gc.toSeconds(), nonGc.toSeconds(), gc.toSeconds() / nonGc.toSeconds());
860        }
861    }
862}
863
864
865void HeapSizeParameters::resetMinorTimingData(void)
866{
867    minorNonGCUserCPU.fromSeconds(0);
868    minorNonGCSystemCPU.fromSeconds(0);
869    minorNonGCReal.fromSeconds(0);
870    minorGCUserCPU.fromSeconds(0);
871    minorGCSystemCPU.fromSeconds(0);
872    minorGCReal.fromSeconds(0);
873    minorGCPageFaults = 0;
874}
875
876void HeapSizeParameters::resetMajorTimingData(void)
877{
878    resetMinorTimingData();
879    majorNonGCUserCPU.fromSeconds(0);
880    majorNonGCSystemCPU.fromSeconds(0);
881    majorNonGCReal.fromSeconds(0);
882    majorGCUserCPU.fromSeconds(0);
883    majorGCSystemCPU.fromSeconds(0);
884    majorGCReal.fromSeconds(0);
885    majorGCPageFaults = 0;
886    minorGCsSinceMajor = 0;
887}
888
889
890class HeapSizing: public RtsModule
891{
892public:
893    virtual void Init(void);
894    virtual void Stop(void);
895};
896
897// Declare this.  It will be automatically added to the table.
898static HeapSizing heapSizeModule;
899
900void HeapSizing::Init(void)
901{
902    gHeapSizeParameters.Init();
903}
904
905void HeapSizing::Stop()
906{
907    gHeapSizeParameters.Final();
908}
909
910static POLYUNSIGNED GetPhysicalMemorySize(void)
911{
912    POLYUNSIGNED maxMem = 0-1; // Maximum unsigned value.
913#if defined(HAVE_WINDOWS_H)
914    {
915        MEMORYSTATUSEX memStatEx;
916        memset(&memStatEx, 0, sizeof(memStatEx));
917        memStatEx.dwLength = sizeof(memStatEx);
918        if (! GlobalMemoryStatusEx(&memStatEx))
919            memStatEx.ullTotalPhys = 0; // Clobber any rubbish since it says it failed.
920        if (memStatEx.ullTotalPhys) // If it's non-zero assume it succeeded
921        {
922            DWORDLONG dwlMax = maxMem;
923            if (memStatEx.ullTotalPhys > dwlMax)
924                return maxMem;
925            else
926                return (POLYUNSIGNED)memStatEx.ullTotalPhys;
927        }
928    }
929
930#endif
931#if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
932    {
933        // Linux and Solaris.  This gives a silly value in Cygwin.
934        long physPages      = sysconf(_SC_PHYS_PAGES);
935        long physPagesize   = sysconf(_SC_PAGESIZE);
936        if (physPages != -1 && physPagesize != -1)
937        {
938            unsigned long maxPages = maxMem / physPagesize;
939            if ((unsigned long)physPages > maxPages)
940                return maxMem;
941            else // We've checked it won't overflow.
942                return physPages*physPagesize;
943        }
944    }
945#endif
946#if defined(HAVE_SYSCTL) && defined(CTL_HW)
947    // FreeBSD and Mac OS X.  It seems HW_MEMSIZE has been added to
948    // Max OS X to return a 64-bit value.
949#ifdef HW_MEMSIZE
950    {
951        static int mib[2] = { CTL_HW, HW_MEMSIZE };
952        uint64_t physMem = 0;
953        size_t len = sizeof(physMem);
954        if (sysctl(mib, 2, &physMem, &len, NULL, 0) == 0 && len == sizeof(physMem))
955        {
956            if (physMem > (uint64_t)maxMem)
957                return maxMem;
958            else
959                return (POLYUNSIGNED)physMem;
960        }
961    }
962#endif
963#ifdef HW_PHYSMEM
964    // If HW_MEMSIZE isn't there or the call failed try this.
965    {
966        static int mib[2] = { CTL_HW, HW_PHYSMEM };
967        unsigned int physMem = 0;
968        size_t len = sizeof(physMem);
969        if (sysctl(mib, 2, &physMem, &len, NULL, 0) == 0 && len == sizeof(physMem))
970        {
971            if (physMem > maxMem)
972                return maxMem;
973            else
974                return physMem;
975        }
976    }
977#endif
978#endif
979    return 0; // Unable to determine
980}
981
982