All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.timeandspace.smoothie.OutboundOverflowCounts Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
/*
 * Copyright (C) The SmoothieMap Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.timeandspace.smoothie;

import static io.timeandspace.smoothie.InterleavedSegment_BitSetAndStateArea.getOutboundOverflowCountsPerGroup;
import static io.timeandspace.smoothie.InterleavedSegment_BitSetAndStateArea.setOutboundOverflowCountsPerGroup;
import static io.timeandspace.smoothie.HashTable.HASH_TABLE_GROUPS;
import static io.timeandspace.smoothie.HashTable.shouldStopProbing;
import static io.timeandspace.smoothie.InterleavedSegments.dataGroupOffset;
import static io.timeandspace.smoothie.InterleavedSegments.readDataGroupAtOffset;
import static io.timeandspace.smoothie.InterleavedSegments.writeDataGroupAtOffset;
import static io.timeandspace.smoothie.HashTable.DATA_GROUP__OUTBOUND_OVERFLOW_MASK;
import static io.timeandspace.smoothie.HashTable.GROUP_SLOTS;
import static io.timeandspace.smoothie.HashTable.addGroupIndex;
import static io.timeandspace.smoothie.HashTable.assertValidGroupIndex;
import static io.timeandspace.smoothie.LongMath.clearLowestSetBit;
import static io.timeandspace.smoothie.Utils.BYTE_SIZE_DIVISION_SHIFT;

/**
 * This class is a collection of static utility methods for working with {@link
 * ContinuousSegment_BitSetAndStateArea#outboundOverflowCountsPerGroup}.
 */
final class OutboundOverflowCounts {

    /**
     * Values of these two constants correspond to {@link HashTable#MOST_SIGNIFICANT_SLOT_BITS} and
     * {@link HashTable#LEAST_SIGNIFICANT_SLOT_BITS} but semantics are independent so they are kept
     * separate.
     */
    private static final long MOST_SIGNIFICANT_BYTE_BITS = 0x8080808080808080L;
    private static final long LEAST_SIGNIFICANT_BYTE_BITS = 0x0101010101010101L;

    static long outboundOverflowCount_markGroupForChange(
            long outboundOverflowCount_perGroupChanges, long groupIndex) {
        
        return outboundOverflowCount_perGroupChanges |
                outboundOverflowCount_groupForChange(groupIndex);
    }

    static long outboundOverflowCount_groupForChange(long groupIndex) {
        
        return 1L << (groupIndex * GROUP_SLOTS);
    }

    static long computeOutboundOverflowCount_perGroupChanges(
            long baseGroupIndex, long finalGroupIndex) {
        if (baseGroupIndex == finalGroupIndex) { // [Positive likely branch]
            return 0L;
        } else {
            return computeOutboundOverflowCount_perGroupChanges0(baseGroupIndex, finalGroupIndex);
        }
    }

    /**
     * Precomputed outboundOverflowCount_perGroupChanges: an alternative approach to implementing
     * this method is a precomputed table of
     * outboundOverflowCount_perGroupChanges[baseGroupIndex][finalGroupIndex].
     * Or it can be outboundOverflowCount_perGroupChanges[finalGroupIndex - baseGroupIndex] and then
     *  Long.rotateLeft/Right(baseGroupIndex).
     * TODO compare the approaches
     */
    private static long computeOutboundOverflowCount_perGroupChanges0(
            final long baseGroupIndex, final long finalGroupIndex) {
        long outboundOverflowCount_perGroupChanges = 0;
        for (long groupIndex = baseGroupIndex, groupIndexStep = 0; ; ) {
            outboundOverflowCount_perGroupChanges = outboundOverflowCount_markGroupForChange(
                    outboundOverflowCount_perGroupChanges, groupIndex);
            groupIndexStep += 1; // [Quadratic probing]
            groupIndex = addGroupIndex(groupIndex, groupIndexStep);
            if (groupIndex == finalGroupIndex) {
                return outboundOverflowCount_perGroupChanges;
            }
        }
    }

    /**
     * Increments the per-group outbound overflow entry counts ({@link
     * ContinuousSegment_BitSetAndStateArea#outboundOverflowCountsPerGroup}). If the count was zero
     * for a group, changes the corresponding dataGroup to indicate the presence of overflow entries
     * via setting {@link HashTable#DATA_GROUP__OUTBOUND_OVERFLOW_MASK} bits (see {@link
     * HashTable#shouldStopProbing}).
     */
    @ColdPath
    static void incrementOutboundOverflowCountsPerGroup(Object segment,
            int isFullCapacitySegment,
            long outboundOverflowCount_perGroupIncrements) {
        // TODO add check that every byte in outboundOverflowCount_perGroupIncrements is either 0 or
        //  1. For values greater than 1, addOutboundOverflowCountsPerGroup() should be called
        //  instead.
        long matchIncrements = outboundOverflowCount_perGroupIncrements << (Byte.SIZE - 1);
        addOutboundOverflowCountsPerGroup(segment,
                isFullCapacitySegment,
                outboundOverflowCount_perGroupIncrements, matchIncrements);
    }

    @AmortizedPerSegment
    static void addOutboundOverflowCountsPerGroup(SmoothieMap.Segment segment,
            long outboundOverflowCount_perGroupAdditions) {
        addOutboundOverflowCountsPerGroup(segment,
                segment instanceof InterleavedSegments.FullCapacitySegment ? 1 : 0,
                outboundOverflowCount_perGroupAdditions,
                matchChanges(outboundOverflowCount_perGroupAdditions));
    }

    /**
     * This method and {@link #subtractOutboundOverflowCountsPerGroup(Object, int, long, long)} have
     * the same structure. These methods should be changed in parallel.
     */
    @ColdPath
    private static void addOutboundOverflowCountsPerGroup(Object segment,
            int isFullCapacitySegment,
            long outboundOverflowCount_perGroupAdditions, long matchAdditions) {
        long oldOutboundOverflowCountsPerGroup = getOutboundOverflowCountsPerGroup(segment);
        long newOutboundOverflowCountsPerGroup =
                oldOutboundOverflowCountsPerGroup + outboundOverflowCount_perGroupAdditions;
        setOutboundOverflowCountsPerGroup(segment, newOutboundOverflowCountsPerGroup);

        // Find groups where the outbound overflow count is increased from zero and set
        // DATA__OUTBOUND_OVERFLOW_BIT in all slots in the corresponding dataGroup.

        // The same bitwise technique as in HashTable.match().
        // `~(oldOutboundOverflowCountsPerGroup << (Byte.SIZE - 1))` excludes false-positives when
        // a zero byte precedes a byte with value 1 to which addition has been done.
        long bitMask = (oldOutboundOverflowCountsPerGroup - LEAST_SIGNIFICANT_BYTE_BITS) &
                matchAdditions & ~(oldOutboundOverflowCountsPerGroup << (Byte.SIZE - 1));
        for (; bitMask != 0; bitMask = clearLowestSetBit(bitMask)) {
            // TODO check if unsigned conversion produces shorter assembly:
            // some JIT may move the value between registers unnecessarily to extend the sign. We
            // know here that the value is always positive.
            // TODO check the comparison is made and it turns out that unsigned conversion is better
            //  go over all `(long) ` occurrences in the codebase and replace with unsigned
            //  conversion if the value is always positive.
            // [Replacing division with shift]
            long groupIndex =
                    (long) Long.numberOfTrailingZeros(bitMask) >>> BYTE_SIZE_DIVISION_SHIFT;
            long dataGroupOffset = dataGroupOffset(groupIndex
                    , (long) isFullCapacitySegment);
            long dataGroup = readDataGroupAtOffset(segment, dataGroupOffset);
            dataGroup |= DATA_GROUP__OUTBOUND_OVERFLOW_MASK;
            writeDataGroupAtOffset(segment, dataGroupOffset, dataGroup);
        }
    }

    /**
     * Decrements the per-group outbound overflow entry counts ({@link
     * ContinuousSegment_BitSetAndStateArea#outboundOverflowCountsPerGroup}). If the count becomes
     * zero for a group, changes the corresponding dataGroup to indicate the absence of overflow
     * entries via clearing {@link HashTable#DATA_GROUP__OUTBOUND_OVERFLOW_MASK} bits (see {@link
     * HashTable#shouldStopProbing}).
     */
    @ColdPath
    static void decrementOutboundOverflowCountsPerGroup(Object segment,
            int isFullCapacitySegment,
            long outboundOverflowCount_perGroupDecrements) {
        // TODO add check that every byte in outboundOverflowCount_perGroupDecrements is either 0
        //  or 1. For values greater than 1, subtractOutboundOverflowCountsPerGroup() should be
        //  called instead.
        long matchDecrements = outboundOverflowCount_perGroupDecrements << (Byte.SIZE - 1);
        subtractOutboundOverflowCountsPerGroup(segment,
                isFullCapacitySegment,
                outboundOverflowCount_perGroupDecrements, matchDecrements);
    }

    @AmortizedPerSegment
    static void subtractOutboundOverflowCountsPerGroupAndUpdateAllGroups(
            SmoothieMap.Segment segment,
            int isFullCapacitySegment,
            long outboundOverflowCount_perGroupDeductions) {
        long outboundOverflowCountsPerGroup = segment.outboundOverflowCountsPerGroup;
        outboundOverflowCountsPerGroup -= outboundOverflowCount_perGroupDeductions;
        segment.outboundOverflowCountsPerGroup = outboundOverflowCountsPerGroup;

        // Update DATA__OUTBOUND_OVERFLOW_BIT (see HashTable class) in all slots in the hash table
        // in a branchless manner.

        // [Int-indexed loop to avoid a safepoint poll]
        for (int groupIndex = 0; groupIndex < HASH_TABLE_GROUPS; groupIndex++) {
            long outboundOverflowCount = outboundOverflowCountsPerGroup & 0xFF;
            outboundOverflowCountsPerGroup >>>= Byte.SIZE;

            long outboundOverflowCountIsZero = (outboundOverflowCount - 1) >>> (Long.SIZE - 1);
            long dataGroupOffset = dataGroupOffset((long) groupIndex
                    , (long) isFullCapacitySegment);
            long outboundOverflowCountIsPositive = 1L - outboundOverflowCountIsZero;
            long dataGroup = readDataGroupAtOffset(segment, dataGroupOffset);
            dataGroup &= ~(DATA_GROUP__OUTBOUND_OVERFLOW_MASK * outboundOverflowCountIsZero);
            dataGroup |= DATA_GROUP__OUTBOUND_OVERFLOW_MASK * outboundOverflowCountIsPositive;
            writeDataGroupAtOffset(segment, dataGroupOffset, dataGroup);
        }
    }

    /**
     * This method and {@link #addOutboundOverflowCountsPerGroup(Object, int, long, long)} have the
     * same structure. These methods should be changed in parallel.
     */
    @ColdPath
    private static void subtractOutboundOverflowCountsPerGroup(Object segment,
            int isFullCapacitySegment,
            long outboundOverflowCount_perGroupDeductions, long matchDeductions) {
        long outboundOverflowCountsPerGroup = getOutboundOverflowCountsPerGroup(segment);
        outboundOverflowCountsPerGroup -= outboundOverflowCount_perGroupDeductions;
        setOutboundOverflowCountsPerGroup(segment, outboundOverflowCountsPerGroup);

        // Find groups where the outbound overflow count is decreased to zero and unset
        // DATA__OUTBOUND_OVERFLOW_BIT in all slots in the corresponding dataGroup.

        // The same bitwise technique as in HashTable.match().
        // `~(outboundOverflowCountsPerGroup << (Byte.SIZE - 1))` excludes false-positives when a
        // zero byte precedes a byte with value 1 from which outbound overflow count deduction has
        // been done.
        long bitMask = (outboundOverflowCountsPerGroup - LEAST_SIGNIFICANT_BYTE_BITS) &
                matchDeductions & ~(outboundOverflowCountsPerGroup << (Byte.SIZE - 1));
        for (; bitMask != 0; bitMask = clearLowestSetBit(bitMask)) {
            // TODO [check if unsigned conversion produces shorter assembly]
            // [Replacing division with shift]
            long groupIndex =
                    (long) Long.numberOfTrailingZeros(bitMask) >>> BYTE_SIZE_DIVISION_SHIFT;
            long dataGroupOffset = dataGroupOffset(groupIndex
                    , (long) isFullCapacitySegment);
            long dataGroup = readDataGroupAtOffset(segment, dataGroupOffset);
            dataGroup &= ~DATA_GROUP__OUTBOUND_OVERFLOW_MASK;
            writeDataGroupAtOffset(segment, dataGroupOffset, dataGroup);
        }
    }

    /**
     * This operation is in the spirit of `hasmore()`: see
     * https://graphics.stanford.edu/~seander/bithacks.html#HasMoreInWord, except that the `| x`
     * step is omitted because it's needed for the cases when the unsigned values in bytes can
     * be more than 127, while outbound overflow counts can't be more than {@link
     * SmoothieMap#SEGMENT_MAX_ALLOC_CAPACITY} - {@link HashTable#GROUP_SLOTS} = 40.
     */
    @AmortizedPerSegment
    private static long matchChanges(long outboundOverflowCount_perGroupChanges) {
        // Add 127 to every byte so that everything except zeros overflows to the most
        // significant bit.
        return (outboundOverflowCount_perGroupChanges + ~MOST_SIGNIFICANT_BYTE_BITS)
                & MOST_SIGNIFICANT_BYTE_BITS;
    }

    /** This method could be used during debugging. */
    @SuppressWarnings("unused")
    private static void checkOutboundOverflowCounts(Object segment
            , int isFullCapacitySegment
            ) {
        long outboundOverflowCountsPerGroup = getOutboundOverflowCountsPerGroup(segment);
        for (long groupIndex = 0; groupIndex < HASH_TABLE_GROUPS; groupIndex++) {
            byte outboundOverflowCount = (byte) outboundOverflowCountsPerGroup;
            boolean outboundOverflowCountIsPositive = (int) outboundOverflowCount != 0;
            long dataGroupOffset = dataGroupOffset(groupIndex
                    , (long) isFullCapacitySegment);
            long dataGroup = readDataGroupAtOffset(segment, dataGroupOffset);
            boolean dataGroupHasOutboundOverflowMask = !shouldStopProbing(dataGroup);
            if (outboundOverflowCountIsPositive ^ dataGroupHasOutboundOverflowMask) {
                throw new IllegalStateException();
            }
            outboundOverflowCountsPerGroup >>>= Byte.SIZE;
        }
    }

    private OutboundOverflowCounts() {}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy