提交 90c2361f 编写于 作者: A acorn

6852873: Reduce safepoint cleanup time

Summary: New optional flags to reduce inflated monitor cleanup times
Reviewed-by: chrisphi, dice
上级 c901506a
...@@ -920,6 +920,10 @@ class CommandLineFlags { ...@@ -920,6 +920,10 @@ class CommandLineFlags {
\ \
product(intx, AlwaysInflate, 0, "(Unstable) Force inflation") \ product(intx, AlwaysInflate, 0, "(Unstable) Force inflation") \
\ \
product(intx, MonitorBound, 0, "Bound Monitor population") \
\
product(bool, MonitorInUseLists, false, "Track Monitors for Deflation") \
\
product(intx, Atomics, 0, \ product(intx, Atomics, 0, \
"(Unsafe,Unstable) Diagnostic - Controls emission of atomics") \ "(Unsafe,Unstable) Diagnostic - Controls emission of atomics") \
\ \
......
...@@ -185,6 +185,8 @@ struct SharedGlobals { ...@@ -185,6 +185,8 @@ struct SharedGlobals {
} ; } ;
static SharedGlobals GVars ; static SharedGlobals GVars ;
static int MonitorScavengeThreshold = 1000000 ;
static volatile int ForceMonitorScavenge = 0 ; // Scavenge required and pending
// Tunables ... // Tunables ...
...@@ -746,8 +748,85 @@ void Thread::muxRelease (volatile intptr_t * Lock) { ...@@ -746,8 +748,85 @@ void Thread::muxRelease (volatile intptr_t * Lock) {
ObjectMonitor * ObjectSynchronizer::gBlockList = NULL ; ObjectMonitor * ObjectSynchronizer::gBlockList = NULL ;
ObjectMonitor * volatile ObjectSynchronizer::gFreeList = NULL ; ObjectMonitor * volatile ObjectSynchronizer::gFreeList = NULL ;
static volatile intptr_t ListLock = 0 ; // protects global monitor free-list cache static volatile intptr_t ListLock = 0 ; // protects global monitor free-list cache
static volatile int MonitorFreeCount = 0 ; // # on gFreeList
static volatile int MonitorPopulation = 0 ; // # Extant -- in circulation
#define CHAINMARKER ((oop)-1) #define CHAINMARKER ((oop)-1)
// Constraining monitor pool growth via MonitorBound ...
//
// The monitor pool is grow-only. We scavenge at STW safepoint-time, but the
// the rate of scavenging is driven primarily by GC. As such, we can find
// an inordinate number of monitors in circulation.
// To avoid that scenario we can artificially induce a STW safepoint
// if the pool appears to be growing past some reasonable bound.
// Generally we favor time in space-time tradeoffs, but as there's no
// natural back-pressure on the # of extant monitors we need to impose some
// type of limit. Beware that if MonitorBound is set to too low a value
// we could just loop. In addition, if MonitorBound is set to a low value
// we'll incur more safepoints, which are harmful to performance.
// See also: GuaranteedSafepointInterval
//
// As noted elsewhere, the correct long-term solution is to deflate at
// monitorexit-time, in which case the number of inflated objects is bounded
// by the number of threads. That policy obviates the need for scavenging at
// STW safepoint time. As an aside, scavenging can be time-consuming when the
// # of extant monitors is large. Unfortunately there's a day-1 assumption baked
// into much HotSpot code that the object::monitor relationship, once established
// or observed, will remain stable except over potential safepoints.
//
// We can use either a blocking synchronous VM operation or an async VM operation.
// -- If we use a blocking VM operation :
// Calls to ScavengeCheck() should be inserted only into 'safe' locations in paths
// that lead to ::inflate() or ::omAlloc().
// Even though the safepoint will not directly induce GC, a GC might
// piggyback on the safepoint operation, so the caller should hold no naked oops.
// Furthermore, monitor::object relationships are NOT necessarily stable over this call
// unless the caller has made provisions to "pin" the object to the monitor, say
// by incrementing the monitor's _count field.
// -- If we use a non-blocking asynchronous VM operation :
// the constraints above don't apply. The safepoint will fire in the future
// at a more convenient time. On the other hand the latency between posting and
// running the safepoint introduces or admits "slop" or laxity during which the
// monitor population can climb further above the threshold. The monitor population,
// however, tends to converge asymptotically over time to a count that's slightly
// above the target value specified by MonitorBound. That is, we avoid unbounded
// growth, albeit with some imprecision.
//
// The current implementation uses asynchronous VM operations.
//
// Ideally we'd check if (MonitorPopulation > MonitorBound) in omAlloc()
// immediately before trying to grow the global list via allocation.
// If the predicate was true then we'd induce a synchronous safepoint, wait
// for the safepoint to complete, and then again to allocate from the global
// free list. This approach is much simpler and precise, admitting no "slop".
// Unfortunately we can't safely safepoint in the midst of omAlloc(), so
// instead we use asynchronous safepoints.
static void InduceScavenge (Thread * Self, const char * Whence) {
// Induce STW safepoint to trim monitors
// Ultimately, this results in a call to deflate_idle_monitors() in the near future.
// More precisely, trigger an asynchronous STW safepoint as the number
// of active monitors passes the specified threshold.
// TODO: assert thread state is reasonable
if (ForceMonitorScavenge == 0 && Atomic::xchg (1, &ForceMonitorScavenge) == 0) {
if (Knob_Verbose) {
::printf ("Monitor scavenge - Induced STW @%s (%d)\n", Whence, ForceMonitorScavenge) ;
::fflush(stdout) ;
}
// Induce a 'null' safepoint to scavenge monitors
// Must VM_Operation instance be heap allocated as the op will be enqueue and posted
// to the VMthread and have a lifespan longer than that of this activation record.
// The VMThread will delete the op when completed.
VMThread::execute (new VM_ForceAsyncSafepoint()) ;
if (Knob_Verbose) {
::printf ("Monitor scavenge - STW posted @%s (%d)\n", Whence, ForceMonitorScavenge) ;
::fflush(stdout) ;
}
}
}
ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) { ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) {
// A large MAXPRIVATE value reduces both list lock contention // A large MAXPRIVATE value reduces both list lock contention
// and list coherency traffic, but also tends to increase the // and list coherency traffic, but also tends to increase the
...@@ -770,6 +849,11 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) { ...@@ -770,6 +849,11 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) {
Self->omFreeCount -- ; Self->omFreeCount -- ;
// CONSIDER: set m->FreeNext = BAD -- diagnostic hygiene // CONSIDER: set m->FreeNext = BAD -- diagnostic hygiene
guarantee (m->object() == NULL, "invariant") ; guarantee (m->object() == NULL, "invariant") ;
if (MonitorInUseLists) {
m->FreeNext = Self->omInUseList;
Self->omInUseList = m;
Self->omInUseCount ++;
}
return m ; return m ;
} }
...@@ -784,6 +868,7 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) { ...@@ -784,6 +868,7 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) {
// on various locks. // on various locks.
Thread::muxAcquire (&ListLock, "omAlloc") ; Thread::muxAcquire (&ListLock, "omAlloc") ;
for (int i = Self->omFreeProvision; --i >= 0 && gFreeList != NULL; ) { for (int i = Self->omFreeProvision; --i >= 0 && gFreeList != NULL; ) {
MonitorFreeCount --;
ObjectMonitor * take = gFreeList ; ObjectMonitor * take = gFreeList ;
gFreeList = take->FreeNext ; gFreeList = take->FreeNext ;
guarantee (take->object() == NULL, "invariant") ; guarantee (take->object() == NULL, "invariant") ;
...@@ -796,6 +881,15 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) { ...@@ -796,6 +881,15 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) {
if (Self->omFreeProvision > MAXPRIVATE ) Self->omFreeProvision = MAXPRIVATE ; if (Self->omFreeProvision > MAXPRIVATE ) Self->omFreeProvision = MAXPRIVATE ;
TEVENT (omFirst - reprovision) ; TEVENT (omFirst - reprovision) ;
continue ; continue ;
const int mx = MonitorBound ;
if (mx > 0 && (MonitorPopulation-MonitorFreeCount) > mx) {
// We can't safely induce a STW safepoint from omAlloc() as our thread
// state may not be appropriate for such activities and callers may hold
// naked oops, so instead we defer the action.
InduceScavenge (Self, "omAlloc") ;
}
continue;
} }
// 3: allocate a block of new ObjectMonitors // 3: allocate a block of new ObjectMonitors
...@@ -836,6 +930,8 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) { ...@@ -836,6 +930,8 @@ ObjectMonitor * ATTR ObjectSynchronizer::omAlloc (Thread * Self) {
// Acquire the ListLock to manipulate BlockList and FreeList. // Acquire the ListLock to manipulate BlockList and FreeList.
// An Oyama-Taura-Yonezawa scheme might be more efficient. // An Oyama-Taura-Yonezawa scheme might be more efficient.
Thread::muxAcquire (&ListLock, "omAlloc [2]") ; Thread::muxAcquire (&ListLock, "omAlloc [2]") ;
MonitorPopulation += _BLOCKSIZE-1;
MonitorFreeCount += _BLOCKSIZE-1;
// Add the new block to the list of extant blocks (gBlockList). // Add the new block to the list of extant blocks (gBlockList).
// The very first objectMonitor in a block is reserved and dedicated. // The very first objectMonitor in a block is reserved and dedicated.
...@@ -894,7 +990,9 @@ void ObjectSynchronizer::omFlush (Thread * Self) { ...@@ -894,7 +990,9 @@ void ObjectSynchronizer::omFlush (Thread * Self) {
if (List == NULL) return ; if (List == NULL) return ;
ObjectMonitor * Tail = NULL ; ObjectMonitor * Tail = NULL ;
ObjectMonitor * s ; ObjectMonitor * s ;
int Tally = 0;
for (s = List ; s != NULL ; s = s->FreeNext) { for (s = List ; s != NULL ; s = s->FreeNext) {
Tally ++ ;
Tail = s ; Tail = s ;
guarantee (s->object() == NULL, "invariant") ; guarantee (s->object() == NULL, "invariant") ;
guarantee (!s->is_busy(), "invariant") ; guarantee (!s->is_busy(), "invariant") ;
...@@ -906,6 +1004,7 @@ void ObjectSynchronizer::omFlush (Thread * Self) { ...@@ -906,6 +1004,7 @@ void ObjectSynchronizer::omFlush (Thread * Self) {
Thread::muxAcquire (&ListLock, "omFlush") ; Thread::muxAcquire (&ListLock, "omFlush") ;
Tail->FreeNext = gFreeList ; Tail->FreeNext = gFreeList ;
gFreeList = List ; gFreeList = List ;
MonitorFreeCount += Tally;
Thread::muxRelease (&ListLock) ; Thread::muxRelease (&ListLock) ;
TEVENT (omFlush) ; TEVENT (omFlush) ;
} }
...@@ -1747,16 +1846,15 @@ void ObjectSynchronizer::oops_do(OopClosure* f) { ...@@ -1747,16 +1846,15 @@ void ObjectSynchronizer::oops_do(OopClosure* f) {
// Having a large number of monitors in-circulation negatively // Having a large number of monitors in-circulation negatively
// impacts the performance of some applications (e.g., PointBase). // impacts the performance of some applications (e.g., PointBase).
// Broadly, we want to minimize the # of monitors in circulation. // Broadly, we want to minimize the # of monitors in circulation.
// Alternately, we could partition the active monitors into sub-lists //
// of those that need scanning and those that do not. // We have added a flag, MonitorInUseLists, which creates a list
// Specifically, we would add a new sub-list of objectmonitors // of active monitors for each thread. deflate_idle_monitors()
// that are in-circulation and potentially active. deflate_idle_monitors() // only scans the per-thread inuse lists. omAlloc() puts all
// would scan only that list. Other monitors could reside on a quiescent // assigned monitors on the per-thread list. deflate_idle_monitors()
// list. Such sequestered monitors wouldn't need to be scanned by // returns the non-busy monitors to the global free list.
// deflate_idle_monitors(). omAlloc() would first check the global free list, // An alternative could have used a single global inuse list. The
// then the quiescent list, and, failing those, would allocate a new block. // downside would have been the additional cost of acquiring the global list lock
// Deflate_idle_monitors() would scavenge and move monitors to the // for every omAlloc().
// quiescent list.
// //
// Perversely, the heap size -- and thus the STW safepoint rate -- // Perversely, the heap size -- and thus the STW safepoint rate --
// typically drives the scavenge rate. Large heaps can mean infrequent GC, // typically drives the scavenge rate. Large heaps can mean infrequent GC,
...@@ -1769,18 +1867,100 @@ void ObjectSynchronizer::oops_do(OopClosure* f) { ...@@ -1769,18 +1867,100 @@ void ObjectSynchronizer::oops_do(OopClosure* f) {
// An even better solution would be to deflate on-the-fly, aggressively, // An even better solution would be to deflate on-the-fly, aggressively,
// at monitorexit-time as is done in EVM's metalock or Relaxed Locks. // at monitorexit-time as is done in EVM's metalock or Relaxed Locks.
// Deflate a single monitor if not in use
// Return true if deflated, false if in use
bool ObjectSynchronizer::deflate_monitor(ObjectMonitor* mid, oop obj,
ObjectMonitor** FreeHeadp, ObjectMonitor** FreeTailp) {
bool deflated;
// Normal case ... The monitor is associated with obj.
guarantee (obj->mark() == markOopDesc::encode(mid), "invariant") ;
guarantee (mid == obj->mark()->monitor(), "invariant");
guarantee (mid->header()->is_neutral(), "invariant");
if (mid->is_busy()) {
if (ClearResponsibleAtSTW) mid->_Responsible = NULL ;
deflated = false;
} else {
// Deflate the monitor if it is no longer being used
// It's idle - scavenge and return to the global free list
// plain old deflation ...
TEVENT (deflate_idle_monitors - scavenge1) ;
if (TraceMonitorInflation) {
if (obj->is_instance()) {
ResourceMark rm;
tty->print_cr("Deflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(intptr_t) obj, (intptr_t) obj->mark(), Klass::cast(obj->klass())->external_name());
}
}
// Restore the header back to obj
obj->release_set_mark(mid->header());
mid->clear();
assert (mid->object() == NULL, "invariant") ;
// Move the object to the working free list defined by FreeHead,FreeTail.
if (*FreeHeadp == NULL) *FreeHeadp = mid;
if (*FreeTailp != NULL) {
ObjectMonitor * prevtail = *FreeTailp;
prevtail->FreeNext = mid;
}
*FreeTailp = mid;
deflated = true;
}
return deflated;
}
void ObjectSynchronizer::deflate_idle_monitors() { void ObjectSynchronizer::deflate_idle_monitors() {
assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint"); assert(SafepointSynchronize::is_at_safepoint(), "must be at safepoint");
int nInuse = 0 ; // currently associated with objects int nInuse = 0 ; // currently associated with objects
int nInCirculation = 0 ; // extant int nInCirculation = 0 ; // extant
int nScavenged = 0 ; // reclaimed int nScavenged = 0 ; // reclaimed
bool deflated = false;
ObjectMonitor * FreeHead = NULL ; // Local SLL of scavenged monitors ObjectMonitor * FreeHead = NULL ; // Local SLL of scavenged monitors
ObjectMonitor * FreeTail = NULL ; ObjectMonitor * FreeTail = NULL ;
// Iterate over all extant monitors - Scavenge all idle monitors.
TEVENT (deflate_idle_monitors) ; TEVENT (deflate_idle_monitors) ;
for (ObjectMonitor* block = gBlockList; block != NULL; block = next(block)) { // Prevent omFlush from changing mids in Thread dtor's during deflation
// And in case the vm thread is acquiring a lock during a safepoint
// See e.g. 6320749
Thread::muxAcquire (&ListLock, "scavenge - return") ;
if (MonitorInUseLists) {
ObjectMonitor* mid;
ObjectMonitor* next;
ObjectMonitor* curmidinuse;
for (JavaThread* cur = Threads::first(); cur != NULL; cur = cur->next()) {
curmidinuse = NULL;
for (mid = cur->omInUseList; mid != NULL; ) {
oop obj = (oop) mid->object();
deflated = false;
if (obj != NULL) {
deflated = deflate_monitor(mid, obj, &FreeHead, &FreeTail);
}
if (deflated) {
// extract from per-thread in-use-list
if (mid == cur->omInUseList) {
cur->omInUseList = mid->FreeNext;
} else if (curmidinuse != NULL) {
curmidinuse->FreeNext = mid->FreeNext; // maintain the current thread inuselist
}
next = mid->FreeNext;
mid->FreeNext = NULL; // This mid is current tail in the FreeHead list
mid = next;
cur->omInUseCount--;
nScavenged ++ ;
} else {
curmidinuse = mid;
mid = mid->FreeNext;
nInuse ++;
}
}
}
} else for (ObjectMonitor* block = gBlockList; block != NULL; block = next(block)) {
// Iterate over all extant monitors - Scavenge all idle monitors.
assert(block->object() == CHAINMARKER, "must be a block header"); assert(block->object() == CHAINMARKER, "must be a block header");
nInCirculation += _BLOCKSIZE ; nInCirculation += _BLOCKSIZE ;
for (int i = 1 ; i < _BLOCKSIZE; i++) { for (int i = 1 ; i < _BLOCKSIZE; i++) {
...@@ -1795,61 +1975,39 @@ void ObjectSynchronizer::deflate_idle_monitors() { ...@@ -1795,61 +1975,39 @@ void ObjectSynchronizer::deflate_idle_monitors() {
guarantee (!mid->is_busy(), "invariant") ; guarantee (!mid->is_busy(), "invariant") ;
continue ; continue ;
} }
deflated = deflate_monitor(mid, obj, &FreeHead, &FreeTail);
// Normal case ... The monitor is associated with obj. if (deflated) {
guarantee (obj->mark() == markOopDesc::encode(mid), "invariant") ; mid->FreeNext = NULL ;
guarantee (mid == obj->mark()->monitor(), "invariant"); nScavenged ++ ;
guarantee (mid->header()->is_neutral(), "invariant");
if (mid->is_busy()) {
if (ClearResponsibleAtSTW) mid->_Responsible = NULL ;
nInuse ++ ;
} else { } else {
// Deflate the monitor if it is no longer being used nInuse ++;
// It's idle - scavenge and return to the global free list }
// plain old deflation ... }
TEVENT (deflate_idle_monitors - scavenge1) ; }
if (TraceMonitorInflation) {
if (obj->is_instance()) {
ResourceMark rm;
tty->print_cr("Deflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(intptr_t) obj, (intptr_t) obj->mark(), Klass::cast(obj->klass())->external_name());
}
}
// Restore the header back to obj MonitorFreeCount += nScavenged;
obj->release_set_mark(mid->header());
mid->clear();
assert (mid->object() == NULL, "invariant") ; // Consider: audit gFreeList to ensure that MonitorFreeCount and list agree.
// Move the object to the working free list defined by FreeHead,FreeTail. if (Knob_Verbose) {
mid->FreeNext = NULL ; ::printf ("Deflate: InCirc=%d InUse=%d Scavenged=%d ForceMonitorScavenge=%d : pop=%d free=%d\n",
if (FreeHead == NULL) FreeHead = mid ; nInCirculation, nInuse, nScavenged, ForceMonitorScavenge,
if (FreeTail != NULL) FreeTail->FreeNext = mid ; MonitorPopulation, MonitorFreeCount) ;
FreeTail = mid ; ::fflush(stdout) ;
nScavenged ++ ;
}
}
} }
ForceMonitorScavenge = 0; // Reset
// Move the scavenged monitors back to the global free list. // Move the scavenged monitors back to the global free list.
// In theory we don't need the freelist lock as we're at a STW safepoint.
// omAlloc() and omFree() can only be called while a thread is _not in safepoint state.
// But it's remotely possible that omFlush() or release_monitors_owned_by_thread()
// might be called while not at a global STW safepoint. In the interest of
// safety we protect the following access with ListLock.
// An even more conservative and prudent approach would be to guard
// the main loop in scavenge_idle_monitors() with ListLock.
if (FreeHead != NULL) { if (FreeHead != NULL) {
guarantee (FreeTail != NULL && nScavenged > 0, "invariant") ; guarantee (FreeTail != NULL && nScavenged > 0, "invariant") ;
assert (FreeTail->FreeNext == NULL, "invariant") ; assert (FreeTail->FreeNext == NULL, "invariant") ;
// constant-time list splice - prepend scavenged segment to gFreeList // constant-time list splice - prepend scavenged segment to gFreeList
Thread::muxAcquire (&ListLock, "scavenge - return") ;
FreeTail->FreeNext = gFreeList ; FreeTail->FreeNext = gFreeList ;
gFreeList = FreeHead ; gFreeList = FreeHead ;
Thread::muxRelease (&ListLock) ;
} }
Thread::muxRelease (&ListLock) ;
if (_sync_Deflations != NULL) _sync_Deflations->inc(nScavenged) ; if (_sync_Deflations != NULL) _sync_Deflations->inc(nScavenged) ;
if (_sync_MonExtant != NULL) _sync_MonExtant ->set_value(nInCirculation); if (_sync_MonExtant != NULL) _sync_MonExtant ->set_value(nInCirculation);
......
...@@ -150,6 +150,8 @@ class ObjectSynchronizer : AllStatic { ...@@ -150,6 +150,8 @@ class ObjectSynchronizer : AllStatic {
// Basically we deflate all monitors that are not busy. // Basically we deflate all monitors that are not busy.
// An adaptive profile-based deflation policy could be used if needed // An adaptive profile-based deflation policy could be used if needed
static void deflate_idle_monitors(); static void deflate_idle_monitors();
static bool deflate_monitor(ObjectMonitor* mid, oop obj, ObjectMonitor** FreeHeadp,
ObjectMonitor** FreeTailp);
static void oops_do(OopClosure* f); static void oops_do(OopClosure* f);
// debugging // debugging
......
...@@ -139,6 +139,8 @@ Thread::Thread() { ...@@ -139,6 +139,8 @@ Thread::Thread() {
omFreeList = NULL ; omFreeList = NULL ;
omFreeCount = 0 ; omFreeCount = 0 ;
omFreeProvision = 32 ; omFreeProvision = 32 ;
omInUseList = NULL ;
omInUseCount = 0 ;
_SR_lock = new Monitor(Mutex::suspend_resume, "SR_lock", true); _SR_lock = new Monitor(Mutex::suspend_resume, "SR_lock", true);
_suspend_flags = 0; _suspend_flags = 0;
......
...@@ -225,6 +225,8 @@ class Thread: public ThreadShadow { ...@@ -225,6 +225,8 @@ class Thread: public ThreadShadow {
ObjectMonitor * omFreeList ; ObjectMonitor * omFreeList ;
int omFreeCount ; // length of omFreeList int omFreeCount ; // length of omFreeList
int omFreeProvision ; // reload chunk size int omFreeProvision ; // reload chunk size
ObjectMonitor * omInUseList; // SLL to track monitors in circulation
int omInUseCount; // length of omInUseList
public: public:
enum { enum {
...@@ -493,7 +495,6 @@ public: ...@@ -493,7 +495,6 @@ public:
static ByteSize stack_base_offset() { return byte_offset_of(Thread, _stack_base ); } static ByteSize stack_base_offset() { return byte_offset_of(Thread, _stack_base ); }
static ByteSize stack_size_offset() { return byte_offset_of(Thread, _stack_size ); } static ByteSize stack_size_offset() { return byte_offset_of(Thread, _stack_size ); }
static ByteSize omFreeList_offset() { return byte_offset_of(Thread, omFreeList); }
#define TLAB_FIELD_OFFSET(name) \ #define TLAB_FIELD_OFFSET(name) \
static ByteSize tlab_##name##_offset() { return byte_offset_of(Thread, _tlab) + ThreadLocalAllocBuffer::name##_offset(); } static ByteSize tlab_##name##_offset() { return byte_offset_of(Thread, _tlab) + ThreadLocalAllocBuffer::name##_offset(); }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册