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