test/micro/org/openjdk/micro/hotspot/gc/g1/WriteBarrier.java
author redestad
Tue, 02 Oct 2018 11:53:46 +0200
branchJEP-230-microbenchmarks-branch
changeset 56913 013359fdfeb2
parent 56905 test/micro/src/classes/org/openjdk/micro/hotspot/gc/g1/WriteBarrier.java@d4ab0656f48e
permissions -rw-r--r--
Reorganize micro sources

/*
 * Copyright (c) 2015, Oracle 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.  Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.openjdk.micro.hotspot.gc.g1;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.OperationsPerInvocation;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Warmup;

/**
 * Several different tests cases of reference writes that might require write
 * barrier depending on the GC used. Multiple sub-classes available with
 * specific command line options set to test G1, Parallel GC and CMS.
 *
 * @author staffan.friberg@oracle.com (sfriberg)
 */
@State(Scope.Benchmark)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
@Fork(jvmArgsAppend = {"-XX:+UseG1GC", "-Xmx256m", "-Xms256m", "-Xmn64m"}, value = 5)
public class WriteBarrier {

    // Datastructures that enables writes between different parts and regions on the heap
    private Object oldReferee_region1;
    private Object oldReferee_region2;
    private Object youngReferee_region3;
    private Object youngReferee_region4;
    private Object nullReferee = null;

    private static final int OLD_REFERENCES_LENGTH = 131072;
    private final Holder[] oldReferences = new Holder[OLD_REFERENCES_LENGTH];
    private Holder oldReference_region1;
    private Holder youngReference_region3;

    // Keep alive to avoid them being garbage collected but not used in benchmarks
    private final LinkedList<Holder> padding = new LinkedList<>();
    private final LinkedList<Holder> liveData = new LinkedList<>();
    private final HashMap<String, Long> gcCount = new HashMap<>();

    /**
     * Setup method for the benchmarks
     *
     * Allocate objects in a certain order to make sure the end up on the heap
     * in the right way to later use them in tests.
     */
    @Setup
    public void setup() {
        // Allocate together and System.gc to move them to Old Space and
        // keep in the same region by doing a fast promotion
        oldReferee_region1 = new Object();
        oldReference_region1 = new Holder(oldReferee_region1);
        System.gc();

        // Fill up old space to 80%
        List<MemoryPoolMXBean> pools = ManagementFactory.getMemoryPoolMXBeans();
        for (MemoryPoolMXBean pool : pools) {
            if (pool.getName().contains("Old Gen")) {
                pool.setUsageThreshold((pool.getUsage().getMax() / 5) * 4);

                while (!pool.isUsageThresholdExceeded()) {
                    // Allocate new referee and and then increase live data count
                    // and force promotion until heap is full enough. The last
                    // oldReferee will most likely be located in a different region
                    // compared to the the initially allocated objects.
                    oldReferee_region2 = new Object();
                    for (int i = 0; i < 10000; i++) {
                        liveData.add(new Holder(new byte[512], new Object()));
                    }
                }
                break;
            }
        }
        int index = 0;
        for (Holder holder : liveData) {
            if (index < oldReferences.length) {
                oldReferences[index++] = holder;
            }
        }

        // Allocate reference and referee together to keep them in same region
        // Allocate Object first so they are located in the same memory order
        // as objects in old space
        youngReferee_region3 = new Object();
        youngReference_region3 = new Holder(youngReferee_region3);

        // Allocate padding and a new referee to make sure the reference and
        // referee are in different regions
        for (int i = 0; i < 2000; i++) {
            Holder tempHolder = new Holder(new byte[500], new Object());
            padding.add(tempHolder);
            youngReferee_region4 = tempHolder.getReference();
        }

        /*
         * Get GC numbers after all allocation but before any benchmark execution
         * starts to verify that no GCs happen during the benchmarking it self as
         * object will then move.
         */
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            gcCount.put(gcBean.getName(), gcBean.getCollectionCount());
        }
    }

    /**
     * Invalidate any benchmark result if a GC occurs during execution of
     * benchmark as moving objects will destroy the assumptions of the tests
     */
    @TearDown(Level.Iteration)
    public void checkGCCount() {
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            if (gcBean.getCollectionCount() != gcCount.get(gcBean.getName())) {
                throw new RuntimeException("A GC has happened during iteration and the microbenchmark result is invalid.");
            }
        }
    }

    /**
     * Write a reference in an object located in the old space and where the
     * written pointer is to a old object in the same region
     */
    @Benchmark
    public void oldPointingToOldInSameRegion() {
        oldReference_region1.setReference(oldReferee_region1);
    }

    /**
     * Write a reference in an object located in the old space and where the
     * written pointer is to a old object in a different region
     */
    @Benchmark
    public void oldPointingToOldInDifferentRegion() {
        oldReference_region1.setReference(oldReferee_region2);
    }

    /**
     * Write a reference in an object located in the old space and where the
     * written pointer is to an object in the young space
     */
    @Benchmark
    public void oldPointingToYoungInDifferentRegion() {
        oldReference_region1.setReference(youngReferee_region3);
    }

    /**
     * Write a reference in an object located in the young space and where the
     * written pointer is to an object in the old space
     */
    @Benchmark
    public void youngPointingToOldInDifferentRegion() {
        youngReference_region3.setReference(oldReferee_region2);
    }

    /**
     * Write a reference in an object located in the young space and where the
     * written pointer is to a young object in the same region
     */
    @Benchmark
    public void youngPointingToYoungInSameRegion() {
        youngReference_region3.setReference(youngReferee_region3);
    }

    /**
     * Write a reference in an object located in the young space and where the
     * written pointer is to a young object in a different region
     */
    @Benchmark
    public void youngPointingToYoungInDifferentRegion() {
        youngReference_region3.setReference(youngReferee_region4);
    }

    /**
     * Write by compiler provable null to an object located in old space
     */
    @Benchmark
    public void oldPointingToExplicitNull() {
        oldReference_region1.setReference(null);
    }

    /**
     * Write by compiler unprovable null to an object located in old space
     */
    @Benchmark
    public void oldPointingToImplicitNull() {
        oldReference_region1.setReference(nullReferee);
    }

    /**
     * Write by compiler provable null to an object located in young space
     */
    @Benchmark
    public void youngPointingToExplicitNull() {
        youngReference_region3.setReference(null);
    }

    /**
     * Write by compiler unprovable null to an object located in young space
     */
    @Benchmark
    public void youngPointingToImplicitNull() {
        youngReference_region3.setReference(nullReferee);
    }

    /**
     * Iterate and update over many old references to point to a young object.
     * Since they are in different regions we will need to check the card, and
     * since we will update many different reference in different memory
     * locations/cards the card will need to be queued as no filtering will
     * catch it.
     */
    @Benchmark
    @OperationsPerInvocation(value = OLD_REFERENCES_LENGTH)
    public void manyOldPointingToYoung() {
        for (Holder oldReference : oldReferences) {
            oldReference.setReference(youngReferee_region3);
        }
    }

    /**
     * Iterate and update over many old references to point to explicit null.
     */
    @Benchmark
    @OperationsPerInvocation(value = OLD_REFERENCES_LENGTH)
    public void manyOldPointingToExplicitNull() {
        for (Holder oldReference : oldReferences) {
            oldReference.setReference(null);
        }
    }

    /**
     * Iterate and update over many old references to point to implicit null.
     */
    @Benchmark
    @OperationsPerInvocation(value = OLD_REFERENCES_LENGTH)
    public void manyOldPointingToImplicitNull() {
        for (Holder oldReference : oldReferences) {
            oldReference.setReference(nullReferee);
        }
    }

    /*
     * Holder object for reference and padding
     */
    static class Holder {

        private Object reference;
        private final byte[] padding;

        public Holder(Object reference) {
            this(null, reference);
        }

        public Holder(byte[] padding, Object reference) {
            this.padding = padding;
            this.reference = reference;
        }

        public void setReference(Object reference) {
            this.reference = reference;
        }

        public Object getReference() {
            return this.reference;
        }
    }
}