blob: 2172a1f65d40dbf357054af51a76328dd091ee02 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.datasketches.theta;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_FLOAT_UNALIGNED;
import static java.lang.foreign.ValueLayout.JAVA_INT_UNALIGNED;
import static java.lang.foreign.ValueLayout.JAVA_LONG_UNALIGNED;
import static org.apache.datasketches.common.Util.LONG_MAX_VALUE_AS_DOUBLE;
import static org.apache.datasketches.theta.PreambleUtil.EMPTY_FLAG_MASK;
import static org.apache.datasketches.theta.PreambleUtil.FLAGS_BYTE;
import static org.apache.datasketches.theta.PreambleUtil.PREAMBLE_LONGS_BYTE;
import static org.apache.datasketches.theta.PreambleUtil.P_FLOAT;
import static org.apache.datasketches.theta.PreambleUtil.RETAINED_ENTRIES_INT;
import static org.apache.datasketches.theta.PreambleUtil.SER_VER;
import static org.apache.datasketches.theta.PreambleUtil.THETA_LONG;
import static org.apache.datasketches.theta.PreambleUtil.extractLgArrLongs;
import static org.apache.datasketches.theta.PreambleUtil.extractLgNomLongs;
import static org.apache.datasketches.theta.PreambleUtil.getUpdatableSegBytes;
import static org.apache.datasketches.theta.PreambleUtil.insertCurCount;
import static org.apache.datasketches.theta.PreambleUtil.insertFamilyID;
import static org.apache.datasketches.theta.PreambleUtil.insertFlags;
import static org.apache.datasketches.theta.PreambleUtil.insertLgArrLongs;
import static org.apache.datasketches.theta.PreambleUtil.insertLgNomLongs;
import static org.apache.datasketches.theta.PreambleUtil.insertLgResizeFactor;
import static org.apache.datasketches.theta.PreambleUtil.insertP;
import static org.apache.datasketches.theta.PreambleUtil.insertPreLongs;
import static org.apache.datasketches.theta.PreambleUtil.insertSeedHash;
import static org.apache.datasketches.theta.PreambleUtil.insertSerVer;
import static org.apache.datasketches.theta.PreambleUtil.insertThetaLong;
import static org.apache.datasketches.theta.PreambleUtil.insertUnionThetaLong;
import static org.apache.datasketches.theta.Rebuilder.actLgResizeFactor;
import static org.apache.datasketches.theta.Rebuilder.moveAndResize;
import static org.apache.datasketches.theta.Rebuilder.quickSelectAndRebuild;
import static org.apache.datasketches.theta.Rebuilder.resize;
import static org.apache.datasketches.theta.UpdateReturnState.InsertedCountIncremented;
import static org.apache.datasketches.theta.UpdateReturnState.InsertedCountIncrementedRebuilt;
import static org.apache.datasketches.theta.UpdateReturnState.InsertedCountIncrementedResized;
import static org.apache.datasketches.theta.UpdateReturnState.RejectedDuplicate;
import static org.apache.datasketches.theta.UpdateReturnState.RejectedOverTheta;
import java.lang.foreign.MemorySegment;
import org.apache.datasketches.common.Family;
import org.apache.datasketches.common.MemorySegmentRequest;
import org.apache.datasketches.common.ResizeFactor;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.SuppressFBWarnings;
import org.apache.datasketches.common.Util;
import org.apache.datasketches.thetacommon.HashOperations;
import org.apache.datasketches.thetacommon.ThetaUtil;
/**
* The UpdatableThetaSketch using the QuickSelect algorithm in a MemorySegment.
* This subclass implements methods, which affect the state (update, rebuild, reset)
*
* <p>This implementation uses data in a given MemorySegment that is owned and managed by the caller.
* This MemorySegment can be off-heap, which if managed properly will greatly reduce the need for
* the JVM to perform garbage collection.</p>
*
* @author Lee Rhodes
* @author Kevin Lang
*/
class DirectQuickSelectSketch extends DirectQuickSelectSketchR {
private static final double DQS_RESIZE_THRESHOLD = 15.0 / 16.0; //tuned for space
int hashTableThreshold_; //computed and mutable, kept only on heap, never serialized.
private final MemorySegmentRequest mSegReq;
/**
* Construct this sketch as a result of a wrap operation where the given MemorySegment already has an updatable sketch image.
* @param wseg the given MemorySegment that has an updatable sketch image.
* @param mSegReq an implementation of the MemorySegmentRequest interface or null.
* @param seed <a href="{@docRoot}/resources/dictionary.html#seed">See Update Hash Seed</a>.
*/
private DirectQuickSelectSketch(
final MemorySegment wseg,
final MemorySegmentRequest mSegReq,
final long seed) {
this.mSegReq = mSegReq == null ? MemorySegmentRequest.DEFAULT : mSegReq;
super(wseg, seed);
}
/**
* Construct a new sketch instance and initialize the given MemorySegment as its backing store.
* This is only called internally by other theta sketch classes.
*
* @param lgNomLongs <a href="{@docRoot}/resources/dictionary.html#lgNomLongs">See lgNomLongs</a>.
* @param seed <a href="{@docRoot}/resources/dictionary.html#seed">See Update Hash Seed</a>.
* @param p
* <a href="{@docRoot}/resources/dictionary.html#p">See Sampling Probability, <i>p</i></a>
* @param rf Resize Factor
* <a href="{@docRoot}/resources/dictionary.html#resizeFactor">See Resize Factor</a>
* @param dstSeg the given MemorySegment object destination. It cannot be null.
* It will be cleared prior to use.
* @param mSegReq an implementation of the MemorySegmentRequest interface or null.
* @param unionGadget true if this sketch is implementing the ThetaUnion gadget function.
* Otherwise, it is behaving as a normal QuickSelectThetaSketch.
*/
DirectQuickSelectSketch(
final int lgNomLongs,
final long seed,
final float p,
final ResizeFactor rf,
final MemorySegment dstSeg,
final MemorySegmentRequest mSegReq,
final boolean unionGadget) {
//Choose family, preambleLongs
final Family family = unionGadget ? Family.UNION : Family.QUICKSELECT;
final int preambleLongs = unionGadget ? Family.UNION.getMinPreLongs() : Family.QUICKSELECT.getMinPreLongs();
//Set RF, lgArrLongs.
final int lgRF = rf.lg();
final int lgArrLongs = lgRF == 0 ? lgNomLongs + 1 : ThetaUtil.MIN_LG_ARR_LONGS;
//check Segment capacity
final int minReqBytes = getUpdatableSegBytes(lgArrLongs, preambleLongs);
final long curSegCapBytes = dstSeg.byteSize();
if (curSegCapBytes < minReqBytes) {
throw new SketchesArgumentException(
"MemorySegment capacity is less than minimum required: " + curSegCapBytes + " < " + minReqBytes);
}
//@formatter:off
//Build preamble
insertPreLongs(dstSeg, preambleLongs); //byte 0
insertLgResizeFactor(dstSeg, lgRF); //byte 0
insertSerVer(dstSeg, SER_VER); //byte 1
insertFamilyID(dstSeg, family.getID()); //byte 2
insertLgNomLongs(dstSeg, lgNomLongs); //byte 3
insertLgArrLongs(dstSeg, lgArrLongs); //byte 4
insertFlags(dstSeg, EMPTY_FLAG_MASK); //byte 5
insertSeedHash(dstSeg, Util.computeSeedHash(seed)); //bytes 6,7
insertCurCount(dstSeg, 0); //bytes 8-11
insertP(dstSeg, p); //bytes 12-15
final long thetaLong = (long)(p * LONG_MAX_VALUE_AS_DOUBLE);
insertThetaLong(dstSeg, thetaLong); //bytes 16-23
//@formatter:on
if (unionGadget) { insertUnionThetaLong(dstSeg, thetaLong); }
//clear hash table area
dstSeg.asSlice(preambleLongs << 3, Long.BYTES << lgArrLongs).fill((byte)0);
hashTableThreshold_ = getOffHeapHashTableThreshold(lgNomLongs, lgArrLongs);
this.mSegReq = mSegReq == null ? MemorySegmentRequest.DEFAULT : mSegReq;
super(dstSeg, seed);
}
/**
* Wrap a sketch around the given source MemorySegment containing sketch data that originated from this sketch.
* @param srcSeg The given MemorySegment object must be in hash table form and not read only.
* @param mSegReq an implementation of the MemorySegmentRequest interface or null.
* @param seed <a href="{@docRoot}/resources/dictionary.html#seed">See Update Hash Seed</a>
* @return instance of this sketch
*/
//called from ThetaUnionImpl and UpdatableThetaSketch
static DirectQuickSelectSketch writableWrap(
final MemorySegment srcSeg,
final MemorySegmentRequest mSegReq,
final long seed) {
final int preambleLongs = ThetaSketch.getPreambleLongs(srcSeg); //byte 0
final int lgNomLongs = extractLgNomLongs(srcSeg); //byte 3
final int lgArrLongs = extractLgArrLongs(srcSeg); //byte 4
UpdatableThetaSketch.checkUnionAndQuickSelectFamily(srcSeg, preambleLongs, lgNomLongs);
checkSegIntegrity(srcSeg, seed, preambleLongs, lgNomLongs, lgArrLongs);
if (isResizeFactorIncorrect(srcSeg, lgNomLongs, lgArrLongs)) {
//If incorrect it sets it to X2 which always works.
insertLgResizeFactor(srcSeg, ResizeFactor.X2.lg());
}
final DirectQuickSelectSketch dqss = new DirectQuickSelectSketch(srcSeg, mSegReq, seed);
dqss.hashTableThreshold_ = getOffHeapHashTableThreshold(lgNomLongs, lgArrLongs);
return dqss;
}
/**
* Fast-wrap a sketch around the given source MemorySegment containing sketch data that originated from
* this sketch. This does NO validity checking of the given MemorySegment.
* @param srcSeg The given MemorySegment must be in hash table form and not read only.
* @param mSegReq an implementation of the MemorySegmentRequest interface or null.
* @param seed <a href="{@docRoot}/resources/dictionary.html#seed">See Update Hash Seed</a>
* @return instance of this sketch
*/
//called from ThetaUnionImpl <- ThetaUnion
static DirectQuickSelectSketch fastWritableWrap(
final MemorySegment srcSeg,
final MemorySegmentRequest mSegReq,
final long seed) {
final int lgNomLongs = extractLgNomLongs(srcSeg); //byte 3
final int lgArrLongs = extractLgArrLongs(srcSeg); //byte 4
final DirectQuickSelectSketch dqss = new DirectQuickSelectSketch(srcSeg, mSegReq, seed);
dqss.hashTableThreshold_ = getOffHeapHashTableThreshold(lgNomLongs, lgArrLongs);
return dqss;
}
//ThetaSketch
//UpdatableThetaSketch
@Override
public UpdatableThetaSketch rebuild() {
final int lgNomLongs = getLgNomLongs();
final int preambleLongs = wseg_.get(JAVA_BYTE, PREAMBLE_LONGS_BYTE) & 0X3F;
if (getRetainedEntries(true) > 1 << lgNomLongs) {
quickSelectAndRebuild(wseg_, preambleLongs, lgNomLongs);
}
return this;
}
@Override
public void reset() {
//clear hash table
//hash table size and hashTableThreshold stays the same
//lgArrLongs stays the same
//thetaLongs resets to p
final int arrLongs = 1 << getLgArrLongs();
final int preambleLongs = wseg_.get(JAVA_BYTE, PREAMBLE_LONGS_BYTE) & 0X3F;
final int preBytes = preambleLongs << 3;
wseg_.asSlice(preBytes, arrLongs * 8L).fill((byte)0);
wseg_.set(JAVA_BYTE, FLAGS_BYTE, (byte) EMPTY_FLAG_MASK);
wseg_.set(JAVA_INT_UNALIGNED, RETAINED_ENTRIES_INT, 0);
final float p = wseg_.get(JAVA_FLOAT_UNALIGNED, P_FLOAT);
final long thetaLong = (long) (p * LONG_MAX_VALUE_AS_DOUBLE);
wseg_.set(JAVA_LONG_UNALIGNED, THETA_LONG, thetaLong);
}
//restricted methods
@Override
UpdateReturnState hashUpdate(final long hash) {
HashOperations.checkHashCorruption(hash);
wseg_.set(JAVA_BYTE, FLAGS_BYTE, (byte) (wseg_.get(JAVA_BYTE, FLAGS_BYTE) & ~EMPTY_FLAG_MASK));
final long thetaLong = getThetaLong();
final int lgNomLongs = getLgNomLongs();
//The over-theta test
if (HashOperations.continueCondition(thetaLong, hash)) {
return RejectedOverTheta; //signal that hash was rejected due to theta or zero.
}
final int lgArrLongs = getLgArrLongs();
final int preambleLongs = wseg_.get(JAVA_BYTE, PREAMBLE_LONGS_BYTE) & 0X3F;
//The duplicate test
final int index = HashOperations.hashSearchOrInsertMemorySegment(wseg_, lgArrLongs, hash, preambleLongs << 3);
if (index >= 0) { return RejectedDuplicate; } //Duplicate, not inserted
//insertion occurred, increment curCount
final int curCount = getRetainedEntries(true) + 1;
wseg_.set(JAVA_INT_UNALIGNED, RETAINED_ENTRIES_INT, curCount); //update curCount
if (isOutOfSpace(curCount)) { //we need to do something, we are out of space
if (lgArrLongs > lgNomLongs) { //at full size, rebuild, assumes no dirty values, changes thetaLong, curCount_
assert lgArrLongs == lgNomLongs + 1 : "lgArr: " + lgArrLongs + ", lgNom: " + lgNomLongs;
//rebuild, refresh curCount based on # values in the hashtable.
quickSelectAndRebuild(wseg_, preambleLongs, lgNomLongs);
return InsertedCountIncrementedRebuilt;
} //end of rebuild, exit
else { //Not at full size, resize. Should not get here if lgRF = 0 and segCap is too small.
final int lgRF = getLgRF();
final int actLgRF = actLgResizeFactor(wseg_.byteSize(), lgArrLongs, preambleLongs, lgRF);
int tgtLgArrLongs = Math.min(lgArrLongs + actLgRF, lgNomLongs + 1);
if (actLgRF > 0) { //Expand in current MemorySegment
//lgArrLongs will change; thetaLong, curCount will not
resize(wseg_, preambleLongs, lgArrLongs, tgtLgArrLongs);
hashTableThreshold_ = getOffHeapHashTableThreshold(lgNomLongs, tgtLgArrLongs);
return InsertedCountIncrementedResized;
} //end of Expand in current MemorySegment, exit.
else { //Request larger segment, then resize. lgArrLongs will change; thetaLong, curCount will not
final int preBytes = preambleLongs << 3;
tgtLgArrLongs = Math.min(lgArrLongs + lgRF, lgNomLongs + 1);
final int tgtArrBytes = 8 << tgtLgArrLongs;
final int reqBytes = tgtArrBytes + preBytes;
final MemorySegment newDstSeg = mSegReq.request(reqBytes);
moveAndResize(wseg_, preambleLongs, lgArrLongs, newDstSeg, tgtLgArrLongs, thetaLong);
final MemorySegment oldSeg = wseg_;
wseg_ = newDstSeg;
mSegReq.requestClose(oldSeg);
hashTableThreshold_ = getOffHeapHashTableThreshold(lgNomLongs, tgtLgArrLongs);
return InsertedCountIncrementedResized;
} //end of request new segment & resize
} //end of resize
} //end of isOutOfSpace
return InsertedCountIncremented;
}
@Override
boolean isOutOfSpace(final int numEntries) {
return numEntries > hashTableThreshold_;
}
/**
* Returns the cardinality limit given the current size of the hash table array.
*
* @param lgNomLongs <a href="{@docRoot}/resources/dictionary.html#lgNomLongs">See lgNomLongs</a>.
* @param lgArrLongs <a href="{@docRoot}/resources/dictionary.html#lgArrLongs">See lgArrLongs</a>.
* @return the hash table threshold
*/
@SuppressFBWarnings(value = "DB_DUPLICATE_BRANCHES", justification = "False Positive, see the code comments")
protected static final int getOffHeapHashTableThreshold(final int lgNomLongs, final int lgArrLongs) {
//SpotBugs may complain (DB_DUPLICATE_BRANCHES) if DQS_RESIZE_THRESHOLD == REBUILD_THRESHOLD,
//but this allows us to tune these constants for different sketches.
final double fraction = lgArrLongs <= lgNomLongs ? DQS_RESIZE_THRESHOLD : ThetaUtil.REBUILD_THRESHOLD;
return (int) (fraction * (1 << lgArrLongs));
}
}