| /* |
| * 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); |
| } |
| } |
| } |