diff --git a/vm/ByteCodeTranslator/src/cn1_globals.h b/vm/ByteCodeTranslator/src/cn1_globals.h index 7974bae6cb6..77ff2afdec9 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.h +++ b/vm/ByteCodeTranslator/src/cn1_globals.h @@ -1290,7 +1290,6 @@ const int currentCodenameOneCallStackOffset = threadStateData->callStackOffset; typedef struct CN1BibopPage { struct CN1BibopPage* _Atomic nextAll; // append-only global registry chain - struct CN1BibopPage* nextFresh[2]; // alternating per-GC fresh-page stack links struct CN1BibopPage* nextPool; // FREE/PARTIAL pool / SWEEP stack link int classIndex; int slotSize; @@ -1305,7 +1304,16 @@ typedef struct CN1BibopPage { // page in O(1) -- without the per-slot walk -- whenever it can PROVE the page is // homogeneous. The fields are always present (so the struct layout is identical in // A/B builds); only the writes/reads are gated. See cn1BibopSweep for the proof. - JAVA_BOOLEAN gcAllocedSinceSweep; // any alloc into the page since last sweep/reset + JAVA_BOOLEAN gcAllocedSinceSweep; // any alloc into the page since last sweep/reset. + // Alloc paths set it, the FREE-pool reformat + // resets it, and the grace pass reads it, all + // via relaxed __atomic ops (those can overlap + // a concurrent mark). Only the sweep accesses + // it plain: it runs on the GC thread after + // mark (program-ordered vs the grace pass) on + // retired pages no mutator holds, and the + // pool handoff mutex orders it vs the next + // owner's stores // (owner-thread single-writer; published to the // GC via the sweep-stack release-push) JAVA_BOOLEAN gcNeedsReclaim; // a survivor carries a finalizer or monitor -> @@ -1335,7 +1343,13 @@ typedef struct CN1BibopPage { // idempotent across parallel markers) int gcGraceEpoch; // upper bound on survivor epochs as of the last // full walk (GC-thread only) - _Atomic int gcFreshEpoch[2]; // queued once on each alternating epoch stack +#ifdef CN1_GRACE_AUDIT + int gcAuditSnapshot; // QA builds only: bumpIndex at mark start. + // Relaxed __atomic access everywhere -- the + // GC writes/reads it during marking while a + // mutator can reformat the page from the + // FREE pool +#endif } CN1BibopPage; // Per-thread current page per size class; defined in cn1_globals.m. Touched only @@ -1343,11 +1357,10 @@ typedef struct CN1BibopPage { extern __thread CN1BibopPage* bibopCurrent[CN1_BIBOP_NUM_CLASSES]; extern _Atomic long bibopBytesSinceGc; extern _Atomic long bibopGcTriggerBytes; -// Atomic mirror of currentGcMarkValue for mutator-side adaptive/fresh-page +// Atomic mirror of currentGcMarkValue for mutator-side adaptive-policy // decisions. currentGcMarkValue itself remains owned by the GC/mark threads. extern _Atomic int bibopGcEpoch; extern _Atomic int bibopBypassGeneration[CN1_BIBOP_NUM_CLASSES]; -extern CN1BibopPage* _Atomic bibopFreshPages[2]; #if defined(CN1_GC_INSTRUMENT) && !defined(CN1_DISABLE_BIBOP) // QA-only diagnostics. Production builds contain neither the counters nor // their atomic updates. @@ -1359,7 +1372,6 @@ extern _Atomic long cn1BibopBeltRuns; extern _Atomic long cn1BibopAdoptedRescanSkips; #endif extern int currentGcMarkValue; -extern void cn1BibopNoteFreshAllocation(CN1BibopPage* page, int epoch); #ifndef CN1_BIBOP_NO_FASTSWEEP // Called from monitorEnter (any thread) when a monitor (CN1ThreadData) is freshly // attached to a heap object. If the object is a BiBOP slot it bumps a global live-monitor @@ -1471,17 +1483,18 @@ static inline JAVA_OBJECT cn1BibopFastAlloc(CODENAME_ONE_THREAD_STATE, int size, #endif __atomic_store_n(&o->__codenameOneGcMark, -1, __ATOMIC_RELEASE); atomic_store_explicit(&p->bumpIndex, bi + 1, memory_order_release); - int __cn1FreshEpoch = atomic_load_explicit(&bibopGcEpoch, memory_order_relaxed); - if(__builtin_expect(atomic_load_explicit(&p->gcFreshEpoch[__cn1FreshEpoch & 1], memory_order_relaxed) - != __cn1FreshEpoch, 0)) { - cn1BibopNoteFreshAllocation(p, __cn1FreshEpoch); - } #ifndef CN1_BIBOP_NO_FASTSWEEP - // Mark the page dirty so the O(1) sweep never treats a page that still has - // fresh mark==-1 (grace-candidate) slots as homogeneous. Single plain store - // to the already-hot page header; published to the GC by the eventual - // retire release-push. - p->gcAllocedSinceSweep = JAVA_TRUE; + // Mark the page dirty: the O(1) sweep never treats a page that still has + // fresh mark==-1 (grace-candidate) slots as homogeneous, and the grace + // pass slot-scans exactly the flagged pages ("-1 slot present" implies + // "allocated into since last sweep" -- the sweep converts every -1 it + // sees). Relaxed atomic (compiles to the same plain store on the hot + // path) because the GRACE PASS reads this concurrently: pre-mark stores + // are ordered ahead of it by the mark-start thread pause, and a store + // it can still miss is by definition a during-mark allocation -- + // SATB-covered this cycle and rescanned next cycle since only the + // sweep (never a concurrent phase) clears the flag. + __atomic_store_n(&p->gcAllocedSinceSweep, JAVA_TRUE, __ATOMIC_RELAXED); #endif CN1_BIBOP_ACCOUNT_BYTES(threadStateData, p->slotSize); // allocationsSinceLastGC / totalAllocations (the isHighFrequencyGC heuristic) @@ -1559,13 +1572,9 @@ static inline JAVA_OBJECT cn1BibopFastAllocNoZero(CODENAME_ONE_THREAD_STATE, int #endif __atomic_store_n(&o->__codenameOneGcMark, -1, __ATOMIC_RELEASE); atomic_store_explicit(&p->bumpIndex, bi + 1, memory_order_release); - int __cn1FreshEpoch = atomic_load_explicit(&bibopGcEpoch, memory_order_relaxed); - if(__builtin_expect(atomic_load_explicit(&p->gcFreshEpoch[__cn1FreshEpoch & 1], memory_order_relaxed) - != __cn1FreshEpoch, 0)) { - cn1BibopNoteFreshAllocation(p, __cn1FreshEpoch); - } #ifndef CN1_BIBOP_NO_FASTSWEEP - p->gcAllocedSinceSweep = JAVA_TRUE; + // relaxed: concurrently read by the grace pass (see cn1BibopFastAlloc) + __atomic_store_n(&p->gcAllocedSinceSweep, JAVA_TRUE, __ATOMIC_RELAXED); #endif CN1_BIBOP_ACCOUNT_BYTES(threadStateData, p->slotSize); return o; diff --git a/vm/ByteCodeTranslator/src/cn1_globals.m b/vm/ByteCodeTranslator/src/cn1_globals.m index 6acec7c55ef..6103caf1b8d 100644 --- a/vm/ByteCodeTranslator/src/cn1_globals.m +++ b/vm/ByteCodeTranslator/src/cn1_globals.m @@ -946,6 +946,9 @@ static void cn1DrainDeadThreadPending() { // walks the page registry and its slots before their definitions. static inline JAVA_OBJECT cn1BibopSlot(CN1BibopPage* p, int i); static CN1BibopPage* _Atomic bibopAllPages; +#ifdef CN1_GRACE_AUDIT +static void cn1GraceAuditPreSweep(CODENAME_ONE_THREAD_STATE); +#endif #endif #ifdef CN1_BIBOP_VALIDATE // Belt diagnostic: while set, gcMarkObject logs the class of each newly-marked object @@ -1283,29 +1286,48 @@ void codenameOneGCMark() { // dangling child -> the intermittent Property->Double / container->content crash. Drain // every fresh NON-LEAF object here so a surviving grace object's subtree survives // WITH it. Primitive arrays and other leaf classes have no subtree and are left to - // the sweep's normal one-cycle grace. The alternating dirty-page stacks prevent a - // page allocated into during this mark from corrupting the stack being consumed. + // the sweep's normal one-cycle grace. + // + // Walk the FULL page registry, pruned by gcAllocedSinceSweep. The invariant is + // exact: a mark==-1 slot can only exist on a page allocated into since that + // page's last sweep (the sweep converts every -1 it sees to V), and EVERY + // allocation path sets the flag before the mark-start thread sync publishes it + // -- so a flag-FALSE page provably holds no fresh slot and is skipped without + // touching its slots. The flag is read with a relaxed atomic (its writers + // mirror this; same machine code as the old plain access): pre-mark stores + // are ordered ahead of this pass by the mark-start thread pause, a store + // this read can still miss is by definition a during-mark allocation (SATB + // covers its links this cycle), and only the sweep -- never a phase running + // concurrently with mutators or with this pass -- clears the flag, so a + // missed store is re-observed next cycle. This + // replaced a queue-of-fresh-pages scheme (issue 5425): queue-once-per-epoch + // dedup left every allocation AFTER the queue was consumed (rest of the mark + // plus the whole unbarriered inter-cycle window) untraced when the page was + // not re-queued the next epoch, and the sweep then freed objects reachable + // only through those untraced fresh objects -> user-visible heap corruption. { - for(int lane = 0 ; lane < 2 ; lane++) { - CN1BibopPage* gp = atomic_exchange_explicit(&bibopFreshPages[lane], - (CN1BibopPage*)0, - memory_order_acquire); - while(gp != 0) { -#ifdef CN1_GC_INSTRUMENT - atomic_fetch_add_explicit(&cn1BibopFreshPagesScanned, 1, - memory_order_relaxed); + CN1BibopPage* gp = atomic_load_explicit(&bibopAllPages, memory_order_acquire); + while(gp != 0) { +#ifndef CN1_BIBOP_NO_FASTSWEEP + if(__atomic_load_n(&gp->gcAllocedSinceSweep, __ATOMIC_RELAXED) == JAVA_FALSE) { + gp = atomic_load_explicit(&gp->nextAll, memory_order_acquire); + continue; + } #endif - int gn = atomic_load_explicit(&gp->bumpIndex, memory_order_acquire); - for(int gi = 0 ; gi < gn ; gi++) { - JAVA_OBJECT go = cn1BibopSlot(gp, gi); - if(__atomic_load_n(&go->__codenameOneGcMark, __ATOMIC_ACQUIRE) == -1 - && go->__codenameOneParentClsReference != 0 - && go->__codenameOneParentClsReference->markFunction != 0) { - gcMarkObject(d, go, JAVA_FALSE); - } +#ifdef CN1_GC_INSTRUMENT + atomic_fetch_add_explicit(&cn1BibopFreshPagesScanned, 1, + memory_order_relaxed); +#endif + int gn = atomic_load_explicit(&gp->bumpIndex, memory_order_acquire); + for(int gi = 0 ; gi < gn ; gi++) { + JAVA_OBJECT go = cn1BibopSlot(gp, gi); + if(__atomic_load_n(&go->__codenameOneGcMark, __ATOMIC_ACQUIRE) == -1 + && go->__codenameOneParentClsReference != 0 + && go->__codenameOneParentClsReference->markFunction != 0) { + gcMarkObject(d, go, JAVA_FALSE); } - gp = gp->nextFresh[lane]; } + gp = atomic_load_explicit(&gp->nextAll, memory_order_acquire); } gcMarkDrain(d); } @@ -1365,6 +1387,11 @@ void codenameOneGCMark() { } if(n > 0) gcMarkDrain(d); } +#if defined(CN1_GRACE_AUDIT) && !defined(CN1_DISABLE_BIBOP) + // QA builds only: right before the sweep, verify the grace pass reached every + // pre-mark fresh object; trace and report anything it missed (issue 5425). + cn1GraceAuditPreSweep(d); +#endif #if CN1_ADOPT_POLICY != 0 && !defined(CN1_DISABLE_BIBOP) // Marking (incl. grace, belt and SATB) is fully done. Register the objects matured // this cycle into allObjectsInHeap now -- single-threaded, locked, before the sweep. @@ -1886,7 +1913,6 @@ JAVA_INT java_lang_System_identityHashCode___java_lang_Object_R_int(CODENAME_ONE // struct CN1BibopPage is defined in cn1_globals.h (shared with the inlined bump). static CN1BibopPage* _Atomic bibopAllPages = 0; // registry head (atomic) -CN1BibopPage* _Atomic bibopFreshPages[2]; // alternating epoch stacks static _Atomic long long bibopAllPagesCount = 0; // grow-only registration count static CN1BibopPage* bibopFreePool = 0; // bibopMutex static CN1BibopPage* bibopPartialPool[CN1_BIBOP_NUM_CLASSES]; // bibopMutex @@ -1961,39 +1987,26 @@ static void cn1BibopFormatPage(CN1BibopPage* p, int ci) { p->freeList = 0; p->freeCount = 0; p->owned = JAVA_FALSE; - p->nextFresh[0] = 0; - p->nextFresh[1] = 0; #ifndef CN1_BIBOP_NO_FASTSWEEP - p->gcAllocedSinceSweep = JAVA_FALSE; + // Relaxed atomic, not plain: the acquire-path format (cn1BibopAcquirePage) + // reformats a FREE-pool page that is already in the registry, on a mutator + // thread, possibly while the grace pass concurrently reads this flag. The + // store is value-identical (the sweep already reset the flag before pooling + // the page) so any interleaving reads FALSE; the atomic just keeps the + // concurrent read/write pair well-defined. The new-page path formats before + // registry insertion, where nothing can observe the page. + __atomic_store_n(&p->gcAllocedSinceSweep, JAVA_FALSE, __ATOMIC_RELAXED); p->gcNeedsReclaim = JAVA_FALSE; p->gcHasMonitors = JAVA_FALSE; p->gcHasAdopted = JAVA_FALSE; atomic_store_explicit(&p->gcLastMarkedEpoch, 0, memory_order_relaxed); p->gcGraceEpoch = 0; #endif - atomic_store_explicit(&p->gcFreshEpoch[0], 0, memory_order_relaxed); - atomic_store_explicit(&p->gcFreshEpoch[1], 0, memory_order_relaxed); -} - -// Queue a page only on its first allocation in a GC epoch. The grace pass can -// now walk pages that actually received fresh objects instead of rescanning the -// grow-only registry on every collection. -void cn1BibopNoteFreshAllocation(CN1BibopPage* page, int epoch) { - int lane = epoch & 1; - int seen = atomic_load_explicit(&page->gcFreshEpoch[lane], memory_order_relaxed); - while(seen != epoch) { - if(atomic_compare_exchange_weak_explicit(&page->gcFreshEpoch[lane], &seen, epoch, - memory_order_acq_rel, - memory_order_relaxed)) { - CN1BibopPage* head = atomic_load_explicit(&bibopFreshPages[lane], memory_order_relaxed); - do { - page->nextFresh[lane] = head; - } while(!atomic_compare_exchange_weak_explicit(&bibopFreshPages[lane], &head, page, - memory_order_release, - memory_order_relaxed)); - return; - } - } +#ifdef CN1_GRACE_AUDIT + // Same registry-visible reformat race as the flag above: the GC thread reads + // and rewrites this field during marking in audit builds. + __atomic_store_n(&p->gcAuditSnapshot, 0, __ATOMIC_RELAXED); +#endif } void cn1BibopBeginGcCycle(void) { @@ -2005,6 +2018,29 @@ void cn1BibopBeginGcCycle(void) { // sustained allocator. bibopCycleAllocatedBytes = atomic_exchange_explicit(&bibopBytesSinceGc, 0, memory_order_acq_rel); +#ifdef CN1_GRACE_AUDIT + // QA builds only: snapshot every page's cursor at mark start. Slots below the + // snapshot existed before the grace pass ran, so a complete grace pass must + // have traced every one of them that is still fresh at pre-sweep time. + // Mutators are still running here, so a snapshot may trail a page's true + // cursor by the allocations racing mark start. That is INTENTIONAL + // under-approximation: boundary slots are during-mark allocations -- the + // class the grace guarantee does not cover this cycle (SATB + the sticky + // dirty flag cover them) -- and excluding them keeps the audit free of + // false positives. The audited set still spans every clearly-pre-mark slot, + // which is exactly the population the issue-5425 bug dropped; snapshotting + // later (after the pause) would widen coverage by only those boundary slots + // while making benign mid-mark allocations report as misses. + { + CN1BibopPage* ap = atomic_load_explicit(&bibopAllPages, memory_order_acquire); + while(ap != 0) { + __atomic_store_n(&ap->gcAuditSnapshot, + atomic_load_explicit(&ap->bumpIndex, memory_order_acquire), + __ATOMIC_RELAXED); + ap = atomic_load_explicit(&ap->nextAll, memory_order_acquire); + } + } +#endif } // Raw 64KB page memory comes from large arenas -- one posix_memalign per @@ -2493,13 +2529,9 @@ static JAVA_OBJECT cn1BibopAlloc(CODENAME_ONE_THREAD_STATE, int size, struct cla // publish the new cursor with release AFTER the slot (incl. its // mark) is fully initialized. atomic_store_explicit(&p->bumpIndex, bi + 1, memory_order_release); - int epoch = atomic_load_explicit(&bibopGcEpoch, memory_order_relaxed); - if(atomic_load_explicit(&p->gcFreshEpoch[epoch & 1], memory_order_relaxed) - != epoch) { - cn1BibopNoteFreshAllocation(p, epoch); - } #ifndef CN1_BIBOP_NO_FASTSWEEP - p->gcAllocedSinceSweep = JAVA_TRUE; + // relaxed: concurrently read by the grace pass (see cn1BibopFastAlloc) + __atomic_store_n(&p->gcAllocedSinceSweep, JAVA_TRUE, __ATOMIC_RELAXED); #endif CN1_BIBOP_ACCOUNT_BYTES(threadStateData, p->slotSize); return o; @@ -2515,13 +2547,9 @@ static JAVA_OBJECT cn1BibopAlloc(CODENAME_ONE_THREAD_STATE, int size, struct cla } // free-list slot path cn1BibopInitSlot(threadStateData, o, size, parent); - int epoch = atomic_load_explicit(&bibopGcEpoch, memory_order_relaxed); - if(atomic_load_explicit(&p->gcFreshEpoch[epoch & 1], memory_order_relaxed) - != epoch) { - cn1BibopNoteFreshAllocation(p, epoch); - } #ifndef CN1_BIBOP_NO_FASTSWEEP - p->gcAllocedSinceSweep = JAVA_TRUE; + // relaxed: concurrently read by the grace pass (see cn1BibopFastAlloc) + __atomic_store_n(&p->gcAllocedSinceSweep, JAVA_TRUE, __ATOMIC_RELAXED); #endif CN1_BIBOP_ACCOUNT_BYTES(threadStateData, p->slotSize); return o; @@ -2935,6 +2963,48 @@ static void cn1BibopSweep(CODENAME_ONE_THREAD_STATE) { classSlots, classLive); } +#ifdef CN1_GRACE_AUDIT +// QA builds only (grace-completeness gate, born from issue 5425): walk the FULL +// page registry right before the sweep, ignoring every pruning heuristic, and +// trace any slot that (a) existed before the grace pass ran (below the +// mark-start snapshot) and (b) is still fresh (gcMark == -1) with a published +// non-leaf class. missedFresh counts fresh objects the grace pass did not visit +// (small counts can be benign: a free-list slot re-allocated mid-mark below the +// snapshot after the grace pass ran is SATB-covered this cycle and re-traced +// next cycle). doomedChildren counts objects that became newly marked ONLY by +// tracing them -- ANY nonzero value is a collector bug: without this pass the +// sweep frees those children while a surviving fresh object still references +// them (dangling reference -> heap corruption). +static void cn1GraceAuditPreSweep(CODENAME_ONE_THREAD_STATE) { + long missedFresh = 0; + long beforeFresh = gcMarkNewObjectCount; + CN1BibopPage* gp = atomic_load_explicit(&bibopAllPages, memory_order_acquire); + while(gp != 0) { + int gn = __atomic_load_n(&gp->gcAuditSnapshot, __ATOMIC_RELAXED); + int bi = atomic_load_explicit(&gp->bumpIndex, memory_order_acquire); + if(gn > bi) gn = bi; // page was reformatted mid-cycle; stale snapshot + for(int gi = 0 ; gi < gn ; gi++) { + JAVA_OBJECT go = cn1BibopSlot(gp, gi); + if(__atomic_load_n(&go->__codenameOneGcMark, __ATOMIC_ACQUIRE) == -1 + && go->__codenameOneParentClsReference != 0 + && go->__codenameOneParentClsReference->markFunction != 0) { + missedFresh++; + gcMarkObject(threadStateData, go, JAVA_FALSE); + } + } + gp = atomic_load_explicit(&gp->nextAll, memory_order_acquire); + } + long freshMarked = gcMarkNewObjectCount - beforeFresh; + gcMarkDrain(threadStateData); + long recovered = gcMarkNewObjectCount - beforeFresh - freshMarked; + if(missedFresh > 0 || recovered > 0) { + fprintf(stderr, "[GRACE-AUDIT] epoch=%d missedFresh=%ld doomedChildren=%ld\n", + currentGcMarkValue, missedFresh, recovered); + fflush(stderr); + } +} +#endif + // (The overflow-rescan helpers cn1BibopRescanStart / cn1BibopRescanStep live // further down, next to gcMarkDrain, because they use the mark worklist.) diff --git a/vm/benchmarks/README.md b/vm/benchmarks/README.md index 973066a7f9f..81f2ff419ed 100644 --- a/vm/benchmarks/README.md +++ b/vm/benchmarks/README.md @@ -77,13 +77,52 @@ by the host JVM. The harness additionally requires runtime evidence that: - the BiBOP-only allocator thread graduated to the high-throughput pacing tier; - the 24 MiB baseline trigger grew under sustained survival; - a survivor-heavy size class activated the bounded legacy bypass/reprobe path; -- grace marking used the fresh-page set rather than the grow-only page registry. +- grace marking slot-scanned only pages flagged `gcAllocedSinceSweep` rather + than every slot of the grow-only page registry. It then measures best wall time and per-process peak RSS for the production adaptive collector against the legacy collector and a no-pacing diagnostic build. Those compile-time variants are QA controls only; applications ship one collector with the adaptive behavior enabled, not user-selectable GC flags. +## Grace-completeness audit (`-DCN1_GRACE_AUDIT`) + +The concurrent collector gives fresh (`gcMark == -1`) BiBOP objects one cycle +of sweep grace, so an object reachable ONLY through a surviving fresh object +must be traced by the mark's grace pass or the sweep frees it while it is +still referenced (the issue-5425 dictionary corruption). `-DCN1_GRACE_AUDIT` +compiles in a QA-only pre-sweep pass that snapshots every page's bump cursor +at mark start and, right before the sweep, full-walks the registry tracing +any pre-snapshot slot that is still fresh. A cycle in which the grace pass +missed nothing prints nothing -- **silence is success**. A cycle with a miss +prints one `[GRACE-AUDIT]` line reporting: + +- `missedFresh` — fresh slots the grace pass did not visit. Small counts can + be benign (a free-list slot re-allocated mid-mark, below the snapshot, after + the grace pass ran — SATB covers its links this cycle and the sticky + `gcAllocedSinceSweep` flag re-traces it next cycle). +- `doomedChildren` — objects that became marked ONLY by tracing those missed + slots. **Any nonzero value is a collector bug**: without the audit pass the + sweep would free each of them while a surviving object still references it. + +`GraceAudit` is the driver shaped to break queue/dedup-based grace schemes: +`System.gc()` is asynchronous, so a single thread allocates dropped fresh +nodes (each holding the only reference to an older object) WHILE the mark +runs, then goes quiet across the next cycle. Gate: + +```bash +./translate-and-build.sh GraceAudit target/grace-audit -DCN1_GRACE_AUDIT +./target/grace-audit # PASS: no line reports doomedChildren != 0 + # (an empty stderr is a fully clean run; benign + # missedFresh-only lines may still appear) +``` + +The fresh-page-stack grace scheme this audit was written against reported +100-370 missed slots and 100-250 doomed children per cycle; the +`gcAllocedSinceSweep`-pruned registry walk reports zero doomed across the +suite. `StormAB` (sustained single-thread storm) and `LoadLoop` (repeated +dictionary build/drop) are the matching wall-time/RSS A/B drivers. + `ClinitThrow` is a standalone liveness reproducer (not byte-identical to the host JVM by design — ParparVM's initialization-failure semantics differ): a throwing `` must release the class-init monitor so other threads diff --git a/vm/benchmarks/src/com/bench/GraceAudit.java b/vm/benchmarks/src/com/bench/GraceAudit.java new file mode 100644 index 00000000000..4304c3071e4 --- /dev/null +++ b/vm/benchmarks/src/com/bench/GraceAudit.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.bench; + +/** + * Grace-completeness gate born from issue 5425: a bursty small-object size + * class allocates fresh objects WHILE the concurrent mark runs (System.gc is + * asynchronous), each holding the only reference to an older object, then + * goes quiet across the next GC cycle. Any grace scheme that tracks "pages + * with fresh slots" incrementally must still trace those objects; the + * fresh-page-stack scheme this driver was written against dropped them and + * the sweep freed their children while still referenced. Run with + * -DCN1_GRACE_AUDIT: every reported doomedChildren value must be zero. + */ +public class GraceAudit { + static class Node { + Object a, b, c; + } + + static class Filler { + long a, b, c, d, e, f, g, h, i2, j, k, l; + } + + static Object[] keep = new Object[256]; + static Object[] tmp = new Object[16]; + static long checksum; + + public static void main(String[] args) throws Exception { + for (int round = 0; round < 120; round++) { + // Refill payload children (each will end up referenced ONLY by an + // unpublished fresh node). + for (int j = 0; j < 256; j++) { + if (keep[j] == null) { + Node k = new Node(); + k.b = k; + keep[j] = k; + } + } + // Kick a concurrent mark, then keep allocating fresh dropped nodes + // WHILE it runs: some land after the grace pass already visited (or + // dismissed) this page, the window where queue/dedup-based grace + // schemes lose track of fresh objects. + System.gc(); + for (int slice = 0; slice < 40; slice++) { + for (int i = 0; i < 8; i++) { + Node n = new Node(); + int j = (slice * 8 + i) & 255; + n.a = keep[j]; + keep[j] = null; + tmp[0] = n; + tmp[0] = null; + } + Thread.sleep(3); + } + // Quiet phase: no Node allocation at all across the next cycle, so + // the Node page is never re-queued; filler drives the byte trigger. + for (int i = 0; i < 120000; i++) { + Filler f = new Filler(); + f.b = i; + tmp[i & 15] = f; + } + System.gc(); + Thread.sleep(150); + } + for (int i = 0; i < 16; i++) { + if (tmp[i] != null) { + checksum++; + } + } + for (int i = 0; i < 256; i++) { + if (keep[i] != null) { + checksum += 3; + } + } + System.out.println("GRACE_AUDIT_DRIVER_DONE checksum=" + checksum); + } +} diff --git a/vm/benchmarks/src/com/bench/LoadLoop.java b/vm/benchmarks/src/com/bench/LoadLoop.java new file mode 100644 index 00000000000..803dc7fcbf4 --- /dev/null +++ b/vm/benchmarks/src/com/bench/LoadLoop.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.bench; + +import java.util.Hashtable; + +/** + * Models the issue-5425 looping Dtest: repeatedly build a large dictionary + * (Hashtable of String -> small byte[]), drop the previous one, and report + * per-round wall time. Steady-state per-round time should be flat; growth + * means the collector degrades as loads repeat. + */ +public class LoadLoop { + static Hashtable dict; + + public static void main(String[] args) { + for (int round = 0; round < 12; round++) { + long start = System.currentTimeMillis(); + Hashtable h = new Hashtable(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 120000; i++) { + sb.setLength(0); + sb.append("word"); + sb.append(i); + String key = sb.toString(); + byte[] def = new byte[16 + (i & 31)]; + def[0] = (byte) i; + h.put(key, def); + } + dict = h; + long ms = System.currentTimeMillis() - start; + System.out.println("round " + round + " ms=" + ms + " size=" + h.size()); + } + System.out.println("LOAD_LOOP_DONE"); + } +} diff --git a/vm/benchmarks/src/com/bench/StormAB.java b/vm/benchmarks/src/com/bench/StormAB.java new file mode 100644 index 00000000000..c3e243e9d06 --- /dev/null +++ b/vm/benchmarks/src/com/bench/StormAB.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.bench; + +/** + * A/B driver for the issue-5425 pacing question: a single thread that + * sustains a small-object allocation storm (the Dtest dictionary-load shape) + * with a modest retained set. Compare wall time and peak RSS across VM + * revisions. + */ +public class StormAB { + static class Filler { + long a, b, c, d, e, f, g, h, i2, j, k, l; + } + + static Object[] tmp = new Object[16]; + static long checksum; + + public static void main(String[] args) { + long start = System.currentTimeMillis(); + for (int round = 0; round < 40; round++) { + for (int i = 0; i < 1000000; i++) { + Filler f = new Filler(); + f.a = i; + tmp[i & 15] = f; + } + } + for (int i = 0; i < 16; i++) { + if (tmp[i] != null) { + checksum++; + } + } + System.out.println("STORM_AB_DONE checksum=" + checksum + + " ms=" + (System.currentTimeMillis() - start)); + } +}