зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
297738aab7
Коммит
47a363df69
|
@ -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' };
|
||||
}
|
||||
}
|
||||
|
Загрузка…
Ссылка в новой задаче