Bug 1531626 - (part 6) Introduce a GC_MIN_NURSERY_BYTES parameter r=jonco

Differential Revision: https://phabricator.services.mozilla.com/D21671

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Paul Bone 2019-03-22 05:16:21 +00:00
Родитель 297738aab7
Коммит 47a363df69
7 изменённых файлов: 111 добавлений и 24 удалений

Просмотреть файл

@ -91,6 +91,9 @@ typedef enum JSGCParamKey {
/**
* Maximum size of the generational GC nurseries.
*
* This will be rounded to the nearest gc::ChunkSize. The special value 0
* will disable generational GC.
*
* Pref: javascript.options.mem.nursery.max_kb
* Default: JS::DefaultNurseryBytes
*/
@ -293,6 +296,17 @@ typedef enum JSGCParamKey {
*/
JSGC_NURSERY_FREE_THRESHOLD_FOR_IDLE_COLLECTION_PERCENT = 30,
/**
* Minimum size of the generational GC nurseries.
*
* This value will be rounded to the nearest Nursery::SubChunkStep if below
* gc::ChunkSize, otherwise it'll be rounded to the nearest gc::ChunkSize.
*
* Default: Nursery::SubChunkLimit
* Pref: None
*/
JSGC_MIN_NURSERY_BYTES = 31,
} JSGCParamKey;
/*

Просмотреть файл

@ -465,6 +465,7 @@ static bool MinorGC(JSContext* cx, unsigned argc, Value* vp) {
#define FOR_EACH_GC_PARAM(_) \
_("maxBytes", JSGC_MAX_BYTES, true) \
_("maxMallocBytes", JSGC_MAX_MALLOC_BYTES, true) \
_("minNurseryBytes", JSGC_MIN_NURSERY_BYTES, true) \
_("maxNurseryBytes", JSGC_MAX_NURSERY_BYTES, true) \
_("gcBytes", JSGC_BYTES, false) \
_("gcNumber", JSGC_NUMBER, false) \

Просмотреть файл

@ -286,6 +286,14 @@ static const size_t GCZoneAllocThresholdBase = 30 * 1024 * 1024;
/* JSGC_MAX_MALLOC_BYTES */
static const size_t MaxMallocBytes = 128 * 1024 * 1024;
/*
* JSGC_MIN_NURSERY_BYTES
*
* 192K is conservative, not too low that root marking dominates. The Limit
* should be a multiple of Nursery::SubChunkStep.
*/
static const size_t GCMinNurseryBytes = 192 * 1024;
/* JSGC_ALLOCATION_THRESHOLD_FACTOR */
static const float AllocThresholdFactor = 0.9f;
@ -1394,7 +1402,19 @@ bool GCSchedulingTunables::setParameter(JSGCParamKey key, uint32_t value,
case JSGC_MAX_BYTES:
gcMaxBytes_ = value;
break;
case JSGC_MIN_NURSERY_BYTES:
if (value > gcMaxNurseryBytes_ && gcMaxNurseryBytes_ != 0) {
// We make an exception for gcMaxNurseryBytes_ == 0 since that special
// value is used to disable generational GC.
return false;
}
gcMinNurseryBytes_ = value;
break;
case JSGC_MAX_NURSERY_BYTES:
if ((value < gcMinNurseryBytes_) && (value != 0)) {
// Note that we make an exception for value == 0 as above.
return false;
}
gcMaxNurseryBytes_ = value;
break;
case JSGC_HIGH_FREQUENCY_TIME_LIMIT:
@ -1569,6 +1589,7 @@ void GCSchedulingTunables::setMaxEmptyChunkCount(uint32_t value) {
GCSchedulingTunables::GCSchedulingTunables()
: gcMaxBytes_(0),
maxMallocBytes_(TuningDefaults::MaxMallocBytes),
gcMinNurseryBytes_(TuningDefaults::GCMinNurseryBytes),
gcMaxNurseryBytes_(0),
gcZoneAllocThresholdBase_(TuningDefaults::GCZoneAllocThresholdBase),
allocThresholdFactor_(TuningDefaults::AllocThresholdFactor),
@ -1625,7 +1646,10 @@ void GCSchedulingTunables::resetParameter(JSGCParamKey key,
case JSGC_MAX_BYTES:
gcMaxBytes_ = 0xffffffff;
break;
case JSGC_MIN_NURSERY_BYTES:
case JSGC_MAX_NURSERY_BYTES:
// Reset these togeather to maintain their min <= max invariant.
gcMinNurseryBytes_ = TuningDefaults::GCMinNurseryBytes;
gcMaxNurseryBytes_ = JS::DefaultNurseryBytes;
break;
case JSGC_HIGH_FREQUENCY_TIME_LIMIT:
@ -1694,6 +1718,9 @@ uint32_t GCRuntime::getParameter(JSGCParamKey key, const AutoLockGC& lock) {
return uint32_t(tunables.gcMaxBytes());
case JSGC_MAX_MALLOC_BYTES:
return mallocCounter.maxBytes();
case JSGC_MIN_NURSERY_BYTES:
MOZ_ASSERT(tunables.gcMinNurseryBytes() < UINT32_MAX);
return uint32_t(tunables.gcMinNurseryBytes());
case JSGC_MAX_NURSERY_BYTES:
MOZ_ASSERT(tunables.gcMaxNurseryBytes() < UINT32_MAX);
return uint32_t(tunables.gcMaxNurseryBytes());

Просмотреть файл

@ -142,7 +142,7 @@ bool js::Nursery::init(uint32_t maxNurseryBytes, AutoLockGCBgAlloc& lock) {
if (!allocateNextChunk(0, lock)) {
return false;
}
capacity_ = SubChunkLimit;
capacity_ = roundSize(tunables().gcMinNurseryBytes());
/* After this point the Nursery has been enabled */
setCurrentChunk(0, true);
@ -194,7 +194,7 @@ void js::Nursery::enable() {
if (!allocateNextChunk(0, lock)) {
return;
}
capacity_ = SubChunkLimit;
capacity_ = roundSize(tunables().gcMinNurseryBytes());
}
setCurrentChunk(0, true);
@ -1198,10 +1198,12 @@ void js::Nursery::maybeResizeNursery(JS::GCReason reason) {
MOZ_ASSERT((float(capacity()) * factor) <= SIZE_MAX);
size_t newCapacity = size_t(float(capacity()) * factor);
const size_t minNurseryBytes = roundSize(tunables().gcMinNurseryBytes());
// If one of these conditions is true then we always shrink or grow the
// nursery. This way the thresholds still have an effect even if the goal
// seeking says the current size is ideal.
size_t lowLimit = Max(SubChunkLimit, capacity() / 2);
size_t lowLimit = Max(minNurseryBytes, capacity() / 2);
size_t highLimit =
Min((CheckedInt<size_t>(chunkCountLimit()) * ChunkSize).value(),
(CheckedInt<size_t>(capacity()) * 2).value());
@ -1210,16 +1212,10 @@ void js::Nursery::maybeResizeNursery(JS::GCReason reason) {
if (maxChunkCount() < chunkCountLimit() && promotionRate > GrowThreshold &&
newCapacity > capacity()) {
growAllocableSpace(newCapacity);
} else if (capacity() >= SubChunkLimit + SubChunkStep &&
} else if (capacity() >= minNurseryBytes + SubChunkStep &&
promotionRate < ShrinkThreshold && newCapacity < capacity()) {
shrinkAllocableSpace(newCapacity);
}
// Assert that the limits are set such that we can shrink the nursery below
// one chunk.
static_assert(
SubChunkLimit + SubChunkStep < NurseryChunkUsableSize,
"Nursery limit must be at least one step from the full chunk size");
}
bool js::Nursery::maybeResizeExact(JS::GCReason reason) {
@ -1243,7 +1239,7 @@ bool js::Nursery::maybeResizeExact(JS::GCReason reason) {
if (newMaxNurseryChunks != chunkCountLimit_) {
chunkCountLimit_ = newMaxNurseryChunks;
/* The configured maximum nursery size is changing */
if (maxChunkCount() > newMaxNurseryChunks) {
if (JS_HOWMANY(capacity_, gc::ChunkSize) > newMaxNurseryChunks) {
/* We need to shrink the nursery */
static_assert(NurseryChunkUsableSize < ChunkSize,
"Usable size must be smaller than total size or this "
@ -1253,6 +1249,18 @@ bool js::Nursery::maybeResizeExact(JS::GCReason reason) {
}
}
const size_t minNurseryBytes = roundSize(tunables().gcMinNurseryBytes());
if (minNurseryBytes > capacity()) {
/*
* the configured minimum nursery size is changing and we need to grow the
* nursery
*/
MOZ_ASSERT(minNurseryBytes <= roundSize(tunables().gcMaxNurseryBytes()));
growAllocableSpace(minNurseryBytes);
return true;
}
return false;
}
@ -1296,10 +1304,10 @@ void js::Nursery::shrinkAllocableSpace(size_t newCapacity) {
// clamping in maybeResizeNursery().
MOZ_ASSERT(newCapacity != 0);
// Don't attempt to shrink it to the same size.
if (newCapacity == capacity()) {
if (newCapacity == capacity_) {
return;
}
MOZ_ASSERT(newCapacity < capacity());
MOZ_ASSERT(newCapacity < capacity_);
unsigned newCount = JS_HOWMANY(newCapacity, ChunkSize);
if (newCount < allocatedChunkCount()) {
@ -1311,7 +1319,7 @@ void js::Nursery::shrinkAllocableSpace(size_t newCapacity) {
}
void js::Nursery::minimizeAllocableSpace() {
shrinkAllocableSpace(SubChunkLimit);
shrinkAllocableSpace(tunables().gcMinNurseryBytes());
}
bool js::Nursery::queueDictionaryModeObjectToSweep(NativeObject* obj) {

Просмотреть файл

@ -146,14 +146,6 @@ class Nursery {
*/
static const size_t SubChunkStep = gc::ArenaSize;
/*
* 192K is conservative, not too low that root marking dominates. The Limit
* should be a multiple of the Step.
*/
static const size_t SubChunkLimit = 192 * 1024;
static_assert(SubChunkLimit % SubChunkStep == 0,
"The limit should be a multiple of the step");
struct alignas(gc::CellAlignBytes) CellAlignedByte {
char byte;
};
@ -339,7 +331,6 @@ class Nursery {
size_t spaceToEnd(unsigned chunkCount) const;
size_t capacity() const {
MOZ_ASSERT(capacity_ >= SubChunkLimit || capacity_ == 0);
MOZ_ASSERT(capacity_ <= chunkCountLimit() * gc::ChunkSize);
return capacity_;
}

Просмотреть файл

@ -334,10 +334,12 @@ class GCSchedulingTunables {
UnprotectedData<size_t> maxMallocBytes_;
/*
* JSGC_MIN_NURSERY_BYTES
* JSGC_MAX_NURSERY_BYTES
*
* Maximum nursery size for each runtime.
* Minimum and maximum nursery size for each runtime.
*/
MainThreadData<size_t> gcMinNurseryBytes_;
MainThreadData<size_t> gcMaxNurseryBytes_;
/*
@ -459,6 +461,7 @@ class GCSchedulingTunables {
size_t gcMaxBytes() const { return gcMaxBytes_; }
size_t maxMallocBytes() const { return maxMallocBytes_; }
size_t gcMinNurseryBytes() const { return gcMinNurseryBytes_; }
size_t gcMaxNurseryBytes() const { return gcMaxNurseryBytes_; }
size_t gcZoneAllocThresholdBase() const { return gcZoneAllocThresholdBase_; }
double allocThresholdFactor() const { return allocThresholdFactor_; }

Просмотреть файл

@ -0,0 +1,43 @@
// Test that setting the nursery size works as expected.
load(libdir + "asserts.js");
var testSizesKB = [128, 129, 255, 256, 1023, 1024, 3*1024, 4*1024+1, 16*1024];
// Valid maximum sizes must be >= 1MB.
var testMaxSizesKB = testSizesKB.filter(x => x >= 1024);
for (var max of testMaxSizesKB) {
// Don't test minimums greater than the maximum.
for (var min of testSizesKB.filter(x => x <= max)) {
setMinMax(min, max);
}
}
// The above loops raised the nursery size. Now reduce it to ensure that
// forcibly-reducing it works correctly.
gcparam('minNurseryBytes', 256*1024); // need to avoid min > max;
setMinMax(256, 1024);
// try an invalid configuration.
assertErrorMessage(
() => setMinMax(2*1024, 1024),
Object,
"Parameter value out of range");
function setMinMax(min, max) {
// Set the maximum first so that we don't hit a case where max < min.
gcparam('maxNurseryBytes', max * 1024);
gcparam('minNurseryBytes', min * 1024);
assertEq(max * 1024, gcparam('maxNurseryBytes'));
assertEq(min * 1024, gcparam('minNurseryBytes'));
allocateSomeThings();
gc();
}
function allocateSomeThings() {
for (var i = 0; i < 1000; i++) {
var obj = { an: 'object', with: 'fields' };
}
}