blob: a3b717beb26b8cc1cd1bc7cdb91f050f4319c7c4 [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.quantiles;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_DOUBLE_UNALIGNED;
import static java.lang.foreign.ValueLayout.JAVA_LONG_UNALIGNED;
import static org.apache.datasketches.quantiles.ClassicUtil.DOUBLES_SER_VER;
import static org.apache.datasketches.quantiles.ClassicUtil.checkFamilyID;
import static org.apache.datasketches.quantiles.ClassicUtil.checkK;
import static org.apache.datasketches.quantiles.ClassicUtil.computeBitPattern;
import static org.apache.datasketches.quantiles.DoublesUtil.checkDoublesSerVer;
import static org.apache.datasketches.quantiles.PreambleUtil.COMBINED_BUFFER;
import static org.apache.datasketches.quantiles.PreambleUtil.COMPACT_FLAG_MASK;
import static org.apache.datasketches.quantiles.PreambleUtil.EMPTY_FLAG_MASK;
import static org.apache.datasketches.quantiles.PreambleUtil.FLAGS_BYTE;
import static org.apache.datasketches.quantiles.PreambleUtil.MAX_DOUBLE;
import static org.apache.datasketches.quantiles.PreambleUtil.MIN_DOUBLE;
import static org.apache.datasketches.quantiles.PreambleUtil.N_LONG;
import static org.apache.datasketches.quantiles.PreambleUtil.ORDERED_FLAG_MASK;
import static org.apache.datasketches.quantiles.PreambleUtil.READ_ONLY_FLAG_MASK;
import static org.apache.datasketches.quantiles.PreambleUtil.extractFamilyID;
import static org.apache.datasketches.quantiles.PreambleUtil.extractFlags;
import static org.apache.datasketches.quantiles.PreambleUtil.extractK;
import static org.apache.datasketches.quantiles.PreambleUtil.extractN;
import static org.apache.datasketches.quantiles.PreambleUtil.extractPreLongs;
import static org.apache.datasketches.quantiles.PreambleUtil.extractSerVer;
import static org.apache.datasketches.quantiles.PreambleUtil.insertFamilyID;
import static org.apache.datasketches.quantiles.PreambleUtil.insertFlags;
import static org.apache.datasketches.quantiles.PreambleUtil.insertK;
import static org.apache.datasketches.quantiles.PreambleUtil.insertMaxDouble;
import static org.apache.datasketches.quantiles.PreambleUtil.insertMinDouble;
import static org.apache.datasketches.quantiles.PreambleUtil.insertN;
import static org.apache.datasketches.quantiles.PreambleUtil.insertPreLongs;
import static org.apache.datasketches.quantiles.PreambleUtil.insertSerVer;
import java.lang.foreign.MemorySegment;
import java.util.Objects;
import org.apache.datasketches.common.Family;
import org.apache.datasketches.common.MemorySegmentRequest;
import org.apache.datasketches.common.MemorySegmentStatus;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.SketchesReadOnlyException;
import org.apache.datasketches.quantilescommon.QuantilesAPI;
/**
* Implements the QuantilesDoublesSketch in a MemorySegment.
*
* @author Kevin Lang
* @author Lee Rhodes
*/
final class DirectUpdateDoublesSketch extends UpdatableQuantilesDoublesSketch {
private static final int MIN_DIRECT_DOUBLES_SER_VER = 3;
private MemorySegmentRequest mSegReq_ = null;
private MemorySegment seg_;
//**CONSTRUCTORS**
private DirectUpdateDoublesSketch(final int k, final MemorySegment seg, final MemorySegmentRequest mSegReq) {
super(k); //Checks k
mSegReq_ = mSegReq;
seg_ = seg;
}
/**
* Creates a new instance of a QuantilesDoublesSketch in a MemorySegment.
*
* @param k Parameter that controls space usage of sketch and accuracy of estimates.
* Must be greater than 1 and less than 65536 and a power of 2.
* @param dstSeg the non-null destination MemorySegment that will be initialized to hold the data for this sketch.
* It must initially be at least (16 * MIN_K + 32) bytes, where MIN_K defaults to 2. As it grows
* it will request more MemorySegment using the MemorySegmentRequest callback.
* @param mSegReq the MemorySegmentRequest used if the given MemorySegment needs to expand.
* Otherwise, it can be null and the default MemorySegmentRequest will be used.
* @return a DirectUpdateDoublesSketch
*/
static DirectUpdateDoublesSketch newInstance(final int k, final MemorySegment dstSeg, final MemorySegmentRequest mSegReq) {
Objects.requireNonNull(dstSeg, "The MemorySegment dstSeg must not be null");
final long segCap = dstSeg.byteSize();
checkDirectSegCapacity(k, 0, segCap);
//initialize dstSeg
dstSeg.set(JAVA_LONG_UNALIGNED, 0, 0L); //clear pre0
insertPreLongs(dstSeg, 2);
insertSerVer(dstSeg, DOUBLES_SER_VER);
insertFamilyID(dstSeg, Family.QUANTILES.getID());
insertFlags(dstSeg, EMPTY_FLAG_MASK);
insertK(dstSeg, k);
if (segCap >= COMBINED_BUFFER) {
insertN(dstSeg, 0L);
insertMinDouble(dstSeg, Double.NaN);
insertMaxDouble(dstSeg, Double.NaN);
}
return new DirectUpdateDoublesSketch(k, dstSeg, mSegReq);
}
/**
* Wrap this sketch around the given updatable MemorySegment image of a QuantilesDoublesSketch.
*
* @param srcSeg the given MemorySegment image of an UpdatableQuantilesDoublesSketch and must not be null.
* @param mSegReq the MemorySegmentRequest used if the given MemorySegment needs to expand.
* Otherwise, it can be null and the default MemorySegmentRequest will be used.
* @return a sketch that wraps the given srcSeg
*/
static DirectUpdateDoublesSketch wrapInstance(final MemorySegment srcSeg, final MemorySegmentRequest mSegReq) {
Objects.requireNonNull(srcSeg, "The source MemorySegment must not be null");
final long segCap = srcSeg.byteSize();
final int preLongs = extractPreLongs(srcSeg);
final int serVer = extractSerVer(srcSeg);
final int familyID = extractFamilyID(srcSeg);
final int flags = extractFlags(srcSeg);
final int k = extractK(srcSeg);
final boolean empty = (flags & EMPTY_FLAG_MASK) > 0; //Preamble flags empty state
final long n = empty ? 0 : extractN(srcSeg);
//VALIDITY CHECKS
checkPreLongs(preLongs);
checkFamilyID(familyID);
checkDoublesSerVer(serVer, MIN_DIRECT_DOUBLES_SER_VER);
checkDirectFlags(flags); //Cannot be compact
checkK(k);
checkCompact(serVer, flags);
checkDirectSegCapacity(k, n, segCap);
checkEmptyAndN(empty, n);
return new DirectUpdateDoublesSketch(k, srcSeg, mSegReq);
}
//**END CONSTRUCTORS**
@Override
public double getMaxItem() {
if (isEmpty()) { throw new IllegalArgumentException(QuantilesAPI.EMPTY_MSG); }
return seg_.get(JAVA_DOUBLE_UNALIGNED, MAX_DOUBLE);
}
@Override
public double getMinItem() {
if (isEmpty()) { throw new IllegalArgumentException(QuantilesAPI.EMPTY_MSG); }
return seg_.get(JAVA_DOUBLE_UNALIGNED, MIN_DOUBLE);
}
@Override
public long getN() {
return (seg_.byteSize() < COMBINED_BUFFER) ? 0 : seg_.get(JAVA_LONG_UNALIGNED, N_LONG);
}
@Override
public boolean hasMemorySegment() {
return (seg_ != null);
}
@Override
public boolean isOffHeap() {
return (seg_ != null) ? seg_.isNative() : false;
}
@Override
public boolean isReadOnly() {
return seg_.isReadOnly();
}
@Override
public boolean isSameResource(final MemorySegment that) {
return MemorySegmentStatus.isSameResource(seg_, that);
}
@Override
public void update(final double dataItem) {
if (Double.isNaN(dataItem)) { return; }
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
final int curBBCount = getBaseBufferCount();
final int newBBCount = curBBCount + 1; //derived, not stored
//must check MemorySegment capacity before we put anything in it
final int combBufItemCap = getCombinedBufferItemCapacity();
if (newBBCount > combBufItemCap) { //newBBCount can never exceed 2 * k.
//unlike the heap sketch, this will grow the base buffer immediately to full size.
seg_ = growCombinedSegBuffer(2 * getK());
}
final long curN = getN();
final long newN = curN + 1;
if (curN == 0) { //set min and max quantiles
putMaxItem(dataItem);
putMinItem(dataItem);
} else {
if (dataItem > getMaxItem()) { putMaxItem(dataItem); }
if (dataItem < getMinItem()) { putMinItem(dataItem); }
}
seg_.set(JAVA_DOUBLE_UNALIGNED, COMBINED_BUFFER + ((long) curBBCount * Double.BYTES), dataItem); //put the item
seg_.set(JAVA_BYTE, FLAGS_BYTE, (byte) 0); //not compact, not ordered, not empty
if (newBBCount == (2 * k_)) { //Propagate
// make sure there will be enough levels for the propagation
final int curSegItemCap = getCombinedBufferItemCapacity();
final int itemSpaceNeeded = DoublesUpdateImpl.getRequiredItemCapacity(k_, newN);
//check seg has capacity to accommodate new level
if (itemSpaceNeeded > curSegItemCap) {
// copies base buffer plus old levels, adds space for new level
seg_ = growCombinedSegBuffer(itemSpaceNeeded);
}
// sort base buffer via accessor which modifies the underlying base buffer,
// then use as one of the inputs to propagate-carry
final DoublesSketchAccessor bbAccessor = DoublesSketchAccessor.wrap(this, true);
bbAccessor.sort();
final long newBitPattern = DoublesUpdateImpl.inPlacePropagateCarry(
0, // starting level
null,
bbAccessor,
true,
k_,
DoublesSketchAccessor.wrap(this, true),
getBitPattern()
);
assert newBitPattern == computeBitPattern(k_, newN); // internal consistency check
//bit pattern on direct is always derived, no need to save it.
}
putN(newN);
doublesSV = null;
}
@Override
public void reset() {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
if (seg_.byteSize() >= COMBINED_BUFFER) {
seg_.set(JAVA_BYTE, FLAGS_BYTE, (byte) EMPTY_FLAG_MASK); //not compact, not ordered
seg_.set(JAVA_LONG_UNALIGNED, N_LONG, 0L);
seg_.set(JAVA_DOUBLE_UNALIGNED, MIN_DOUBLE, Double.NaN);
seg_.set(JAVA_DOUBLE_UNALIGNED, MAX_DOUBLE, Double.NaN);
}
}
//Restricted overrides
@Override
int getBaseBufferCount() {
return ClassicUtil.computeBaseBufferItems(getK(), getN());
}
@Override
long getBitPattern() {
final int k = getK();
final long n = getN();
return ClassicUtil.computeBitPattern(k, n);
}
@Override
double[] getCombinedBuffer() {
final int k = getK();
if (isEmpty()) { return new double[k << 1]; } //2K
final long n = getN();
final int itemCap = ClassicUtil.computeCombinedBufferItemCapacity(k, n);
final double[] combinedBuffer = new double[itemCap];
MemorySegment.copy(seg_, JAVA_DOUBLE_UNALIGNED, COMBINED_BUFFER, combinedBuffer, 0, itemCap);
return combinedBuffer;
}
@Override
int getCombinedBufferItemCapacity() {
return Math.max(0, (int)seg_.byteSize() - COMBINED_BUFFER) / Double.BYTES;
}
@Override
MemorySegment getMemorySegment() {
return seg_;
}
@Override
UpdatableQuantilesDoublesSketch getSketchAndReset() {
final HeapUpdateDoublesSketch skCopy = HeapUpdateDoublesSketch.heapifyInstance(seg_);
reset();
return skCopy;
}
@Override
double[] growCombinedBuffer(final int curCombBufItemCap, final int itemSpaceNeeded) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
seg_ = growCombinedSegBuffer(itemSpaceNeeded);
// copy out any data that was there
final double[] newCombBuf = new double[itemSpaceNeeded];
MemorySegment.copy(seg_, JAVA_DOUBLE_UNALIGNED, COMBINED_BUFFER, newCombBuf, 0, curCombBufItemCap);
return newCombBuf;
}
//Puts
@Override
void putMinItem(final double minQuantile) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
assert (seg_.byteSize() >= COMBINED_BUFFER);
seg_.set(JAVA_DOUBLE_UNALIGNED, MIN_DOUBLE, minQuantile);
}
@Override
void putMaxItem(final double maxQuantile) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
assert (seg_.byteSize() >= COMBINED_BUFFER);
seg_.set(JAVA_DOUBLE_UNALIGNED, MAX_DOUBLE, maxQuantile);
}
@Override
void putN(final long n) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
assert (seg_.byteSize() >= COMBINED_BUFFER);
seg_.set(JAVA_LONG_UNALIGNED, N_LONG, n);
}
@Override
void putCombinedBuffer(final double[] combinedBuffer) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
MemorySegment.copy(combinedBuffer, 0, seg_, JAVA_DOUBLE_UNALIGNED, COMBINED_BUFFER, combinedBuffer.length);
}
@Override
void putBaseBufferCount(final int baseBufferCount) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
}
@Override
void putBitPattern(final long bitPattern) {
if (seg_.isReadOnly()) { throw new SketchesReadOnlyException("This sketch is read only."); }
}
//Direct supporting methods
private MemorySegment growCombinedSegBuffer(final int itemSpaceNeeded) {
final long segBytes = seg_.byteSize();
final int needBytes = (itemSpaceNeeded << 3) + COMBINED_BUFFER; //+ preamble + min & max
assert needBytes > segBytes;
mSegReq_ = (mSegReq_ == null) ? MemorySegmentRequest.DEFAULT : mSegReq_;
final MemorySegment newSeg = mSegReq_.request(needBytes);
MemorySegment.copy(seg_, 0, newSeg, 0, segBytes);
mSegReq_.requestClose(seg_);
return newSeg;
}
//Checks
/**
* Checks the validity of the direct MemorySegment capacity assuming n, k.
* @param k the given k
* @param n the given n
* @param segCapBytes the current MemorySegment capacity in bytes
*/
static void checkDirectSegCapacity(final int k, final long n, final long segCapBytes) {
final int reqBufBytes = getUpdatableStorageBytes(k, n);
if (segCapBytes < reqBufBytes) {
throw new SketchesArgumentException("Possible corruption: MemorySegment capacity too small: "
+ segCapBytes + " < " + reqBufBytes);
}
}
static void checkCompact(final int serVer, final int flags) {
final boolean compact = (serVer == 2) || ((flags & COMPACT_FLAG_MASK) > 0);
if (compact) {
throw new SketchesArgumentException("MemorySegment is in compact form and is not supported for this writableWrap Instance.");
}
}
static void checkPreLongs(final int preLongs) {
if ((preLongs < 1) || (preLongs > 2)) {
throw new SketchesArgumentException(
"Possible corruption: PreLongs must be 1 or 2: " + preLongs);
}
}
static void checkDirectFlags(final int flags) {
final int allowedFlags = //Cannot be compact!
READ_ONLY_FLAG_MASK | EMPTY_FLAG_MASK | ORDERED_FLAG_MASK;
final int flagsMask = ~allowedFlags;
if ((flags & flagsMask) > 0) {
throw new SketchesArgumentException(
"Possible corruption: Invalid flags field: Cannot be compact! "
+ Integer.toBinaryString(flags));
}
}
static void checkEmptyAndN(final boolean empty, final long n) {
if (empty && (n > 0)) {
throw new SketchesArgumentException(
"Possible corruption: Empty Flag = true and N > 0: " + n);
}
}
}