blob: 1ac603e54e5e9df30838d6bbffbeb6f5780d181f [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.hll;
import static org.apache.datasketches.common.Util.checkBounds;
import static org.apache.datasketches.hll.PreambleUtil.HASH_SET_INT_ARR_START;
import static org.apache.datasketches.hll.PreambleUtil.HASH_SET_PREINTS;
import static org.apache.datasketches.hll.PreambleUtil.HLL_PREINTS;
import static org.apache.datasketches.hll.PreambleUtil.LIST_PREINTS;
import static org.apache.datasketches.hll.PreambleUtil.computeLgArr;
import static org.apache.datasketches.hll.PreambleUtil.extractAuxCount;
import static org.apache.datasketches.hll.PreambleUtil.extractCompactFlag;
import static org.apache.datasketches.hll.PreambleUtil.extractCurMode;
import static org.apache.datasketches.hll.PreambleUtil.extractFamilyId;
import static org.apache.datasketches.hll.PreambleUtil.extractHashSetCount;
import static org.apache.datasketches.hll.PreambleUtil.extractInt;
import static org.apache.datasketches.hll.PreambleUtil.extractLgArr;
import static org.apache.datasketches.hll.PreambleUtil.extractLgK;
import static org.apache.datasketches.hll.PreambleUtil.extractListCount;
import static org.apache.datasketches.hll.PreambleUtil.extractPreInts;
import static org.apache.datasketches.hll.PreambleUtil.extractSerVer;
import java.lang.foreign.MemorySegment;
import org.apache.datasketches.common.Family;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.SketchesReadOnlyException;
/**
* @author Lee Rhodes
* @author Kevin Lang
*/
final class HllUtil {
static final int KEY_BITS_26 = 26;
static final int VAL_BITS_6 = 6;
static final int KEY_MASK_26 = (1 << KEY_BITS_26) - 1;
static final int VAL_MASK_6 = (1 << VAL_BITS_6) - 1;
static final int EMPTY = 0;
static final int MIN_LOG_K = 4;
static final int MAX_LOG_K = 21;
static final double HLL_HIP_RSE_FACTOR = 0.8325546111576977; //sqrt(log(2.0))
static final double HLL_NON_HIP_RSE_FACTOR = 1.0389617614136892; //sqrt((3.0 * log(2.0)) - 1.0)
static final double COUPON_RSE_FACTOR = .409; //at transition point not the asymptote
static final double COUPON_RSE = COUPON_RSE_FACTOR / (1 << 13);
static final int LG_INIT_LIST_SIZE = 3;
static final int LG_INIT_SET_SIZE = 5;
static final int RESIZE_NUMER = 3;
static final int RESIZE_DENOM = 4;
static final int loNibbleMask = 0x0f;
static final int hiNibbleMask = 0xf0;
static final int AUX_TOKEN = 0xf;
/**
* Log2 table sizes for exceptions based on lgK from 0 to 26.
* However, only lgK from 4 to 21 are used.
*/
static final int[] LG_AUX_ARR_INTS = {
0, 2, 2, 2, 2, 2, 2, 3, 3, 3, //0 - 9
4, 4, 5, 5, 6, 7, 8, 9, 10, 11, //10 - 19
12, 13, 14, 15, 16, 17, 18 //20 - 26
};
//Checks
static int checkLgK(final int lgK) {
if ((lgK >= MIN_LOG_K) && (lgK <= MAX_LOG_K)) { return lgK; }
throw new SketchesArgumentException(
"Log K must be between 4 and 21, inclusive: " + lgK);
}
static void checkSegSize(final long minBytes, final long capBytes) {
if (capBytes < minBytes) {
throw new SketchesArgumentException(
"Given MemorySegment is not large enough: " + capBytes);
}
}
static void checkNumStdDev(final int numStdDev) {
if ((numStdDev < 1) || (numStdDev > 3)) {
throw new SketchesArgumentException(
"NumStdDev may not be less than 1 or greater than 3.");
}
}
static CurMode checkPreamble(final MemorySegment seg) {
return checkPreamble(seg, true);
}
/**
* Checks the preamble of an image.
* @param seg the image
* @param checkSetLimits true for an image from outside. False only for the sketch's own segment
* while it is being promoted, when a coupon list or set is one coupon past its limit.
* @return the current mode of the image
*/
static CurMode checkPreamble(final MemorySegment seg, final boolean checkSetLimits) {
checkBounds(0, 8, seg.byteSize()); //need min 8 bytes
final int preInts = extractPreInts(seg);
checkBounds(0, (long)preInts * Integer.BYTES, seg.byteSize());
final int serVer = extractSerVer(seg);
final int famId = extractFamilyId(seg);
final CurMode curMode = extractCurMode(seg);
if (
(famId != Family.HLL.getID())
|| (serVer != 1)
|| ((preInts != LIST_PREINTS) && (preInts != HASH_SET_PREINTS) && (preInts != HLL_PREINTS))
|| ((curMode == CurMode.LIST) && (preInts != LIST_PREINTS))
|| ((curMode == CurMode.SET) && (preInts != HASH_SET_PREINTS))
|| ((curMode == CurMode.HLL) && (preInts != HLL_PREINTS))
) {
throw new SketchesArgumentException("Possible Corruption, Invalid Preamble:"
+ PreambleUtil.toString(seg));
}
checkSizes(seg, curMode, checkSetLimits);
return curMode;
}
/**
* Checks lgK and the sizes declared by the image.
* LgK must be in the documented range. The coupon hash set and the auxiliary (exception) hash map
* are never larger than K, and a coupon list never holds more than its initial capacity.
* @param seg the image
* @param curMode the current mode of the image
* @param checkSetLimits if true, also check a coupon hash set image with checkSet()
*/
private static void checkSizes(final MemorySegment seg, final CurMode curMode,
final boolean checkSetLimits) {
final int lgK = extractLgK(seg);
checkLgK(lgK);
if (curMode == CurMode.LIST) {
final int count = extractListCount(seg);
if (count > (1 << LG_INIT_LIST_SIZE)) {
throw new SketchesArgumentException("Possible Corruption: list count " + count);
}
} else if (curMode == CurMode.SET) {
final int count = extractHashSetCount(seg);
final int lgArr = extractLgArr(seg);
if ((count < 0) || (count > (1 << lgK)) || (lgArr > lgK)) {
throw new SketchesArgumentException("Possible Corruption: set count " + count + ", lgArr " + lgArr);
}
if (checkSetLimits) { checkSet(seg, lgK, count, lgArr); }
} else { //HLL
final int auxCount = extractAuxCount(seg);
final int lgArr = extractLgArr(seg);
if ((auxCount < 0) || (auxCount > (1 << lgK)) || ((auxCount > 0) && (lgArr > lgK))) {
throw new SketchesArgumentException("Possible Corruption: aux count " + auxCount + ", lgArr " + lgArr);
}
}
}
/**
* Checks a coupon hash set image against the limits a valid sketch keeps.
* A sketch uses a hash set only when lgK is at least 8. The table starts at 2^LG_INIT_SET_SIZE
* ints, grows up to 2^(lgK - 3) ints, and is never more than 3/4 full; the sketch is promoted to
* HLL mode before that would be exceeded. In an updatable image the count must equal the number
* of non-empty slots in the table.
* @param seg the image
* @param lgK the checked lgK of the image
* @param count the coupon count of the image, already checked to be in [0, K]
* @param lgArr the lgArr field of the image, already checked to be at most lgK
*/
private static void checkSet(final MemorySegment seg, final int lgK, final int count, final int lgArr) {
if (lgK < (LG_INIT_SET_SIZE + 3)) {
throw new SketchesArgumentException("Possible Corruption: set mode with lgK " + lgK);
}
final int maxLgArr = lgK - 3;
if (extractCompactFlag(seg)) {
if ((RESIZE_DENOM * (long) count) > (RESIZE_NUMER * (1L << maxLgArr))) {
throw new SketchesArgumentException("Possible Corruption: set count " + count + " for lgK " + lgK);
}
checkBounds(0, HASH_SET_INT_ARR_START + ((long) count << 2), seg.byteSize());
return;
}
//updatable: early images may not have the lgArr field, then it is computed from the count
final int effLgArr = (lgArr < LG_INIT_SET_SIZE) ? computeLgArr(seg, count, lgK) : lgArr;
if ((effLgArr < LG_INIT_SET_SIZE) || (effLgArr > maxLgArr)
|| ((RESIZE_DENOM * (long) count) > (RESIZE_NUMER * (1L << effLgArr)))) {
throw new SketchesArgumentException("Possible Corruption: set count " + count + ", lgArr " + effLgArr
+ " for lgK " + lgK);
}
final int arrInts = 1 << effLgArr;
checkBounds(0, HASH_SET_INT_ARR_START + ((long) arrInts << 2), seg.byteSize());
int nonEmpty = 0;
for (int i = 0; i < arrInts; i++) {
if (extractInt(seg, HASH_SET_INT_ARR_START + (i << 2)) != EMPTY) { nonEmpty++; }
}
if (nonEmpty != count) {
throw new SketchesArgumentException("Possible Corruption: set count " + count + ", non-empty slots "
+ nonEmpty);
}
}
//Exceptions
static void noWriteAccess() {
throw new SketchesReadOnlyException(
"This sketch is compact or does not have write access to the underlying resource.");
}
//Used for thrown exceptions
static String pairString(final int pair) {
return "SlotNo: " + getPairLow26(pair) + ", Value: "
+ getPairValue(pair);
}
//Pairs
static int pair(final int slotNo, final int value) {
return (value << KEY_BITS_26) | (slotNo & KEY_MASK_26);
}
static int getPairLow26(final int coupon) {
return coupon & KEY_MASK_26;
}
static int getPairValue(final int coupon) {
return coupon >>> KEY_BITS_26;
}
}