/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Apr 20, 2011
*/
package com.bigdata.htree;
import it.unimi.dsi.bits.Fast;
import com.bigdata.util.BytesUtil;
/**
* Static utility methods and data for an {@link HTree}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class HTreeUtil {
/**
* Find the first power of two which is GTE the given value. This is used to
* compute the size of the address space (in bits) which is required to
* address a hash table with slots for that many buckets.
*/
static int getMapSize(final int nentries) {
if (nentries <= 0)
throw new IllegalArgumentException();
int i = 1;
while ((1 << i) < nentries)
i++;
return i;
}
/**
* Return <code>2^n</code>.
*
* @param n
* The exponent.
*
* @return The result.
*/
static int pow2(final int n) {
// return (int) Math.pow(2d, n);
return 1 << n;
}
/**
* Return <code>true</code> if the argument is a power of TWO (2).
*
* @param v
* The argument.
*
* @return <code>true</code> if the argument is a power of TWO (2).
*/
static public boolean isPowerOf2(final int v) {
return ((v & -v) == v);
}
/**
* Return the #of entries in the address map for a page having the given
* local depth. This is <code>2^(globalHashBits - localHashBits)</code>. The
* following table shows the relationship between the global hash bits (gb),
* the local hash bits (lb) for a page, and the #of directory entries for
* that page (nentries).
*
* <pre>
* gb lb nentries
* 1 0 2
* 1 1 1
* 2 0 4
* 2 1 2
* 2 2 1
* 3 0 8
* 3 1 4
* 3 2 2
* 3 3 1
* 4 0 16
* 4 1 8
* 4 2 4
* 4 3 2
* 4 4 1
* </pre>
*
* @param globalDepth
* The global depth of the parent.
* @param localDepth
* The local depth of the child.
*
* @return The #of directory entries for that child page.
*
* @throws IllegalArgumentException
* if either argument is less than ZERO (0).
* @throws IllegalArgumentException
* if <i>localHashBits</i> is greater than
* <i>globalHashBits</i>.
*/
public static int getSlotsOnPage(final int globalDepth, final int localDepth) {
if (localDepth < 0)
throw new IllegalArgumentException();
if (globalDepth < 0)
throw new IllegalArgumentException();
if (localDepth > globalDepth)
throw new IllegalArgumentException();
// The #of slots entries for the child page.
final int numSlotsForPage = (1 << (globalDepth - localDepth));
return numSlotsForPage;
}
/**
* Return the local depth of a child page having <i>npointers</i> to that
* page in the parent node. This method is based on the relationship
* <code>2^(n-i) = npointers</code> where <i>n</i> is the global depth of
* the parent and <i>i</i> is the local depth of the child. The value of
* <i>i</i> is the MSB of <code>(npointers / (2^n))</code>. That equation
* always evaluates to a power of two since npointers is a power of two. The
* MSB is the index of the highest (and only) ONE (1) bit in the result for
* that equation.
*
* @param addressBits
* The #of address bits for the hash tree. This is set when the
* tree is configured and is immutable. <i>addressBits</i> is not
* required for the computation, but is used to validate the
* other arguments.
* @param globalDepth
* The global depth of the parent node.
* @param npointers
* The #of pointers to that child in the parent node. All of
* those pointers will be within a single buddy hash table in the
* parent. The maximum value for <i>npointers</i> is
* <code>2^globalDepth</code> for a given <i>globalDepth</i>.
* (The maximum value of <i>globalDepth</i> is the configured
* value of <i>addressBits</code> for the hash tree.)
*
* @return The local depth of that child node.
*
* @throws IllegalArgumentException
* if <i>addressBits</i> is LT ONE (1).
* @throws IllegalArgumentException
* if <i>globalDepth</i> is LT ZERO (0).
* @throws IllegalArgumentException
* if <i>globalDepth</i> is GT <code>2^addressBits</code>.
* @throws IllegalArgumentException
* if <i>npointers</i> is LT ONE (1).
* @throws IllegalArgumentException
* if <i>npointers</i> is greater than
* <code>2^globalDepth</code>.
* @throws IllegalArgumentException
* if <i>npointers</i> is not a power of 2.
*/
public static int getLocalDepth(final int addressBits,
final int globalDepth, final int npointers) {
if (addressBits < 1)
throw new IllegalArgumentException();
if (globalDepth < 0)
throw new IllegalArgumentException();
if (globalDepth > (1 << addressBits)) // aka 2^addressBits
throw new IllegalArgumentException();
if (npointers < 1)
throw new IllegalArgumentException();
if (npointers > (1 << globalDepth)) // aka 2^globalDepth
throw new IllegalArgumentException();
if ((npointers & -npointers) != npointers) // e.g., not a power of 2.
throw new IllegalArgumentException();
final int x = (1 << globalDepth) / npointers;
final int i = Fast.mostSignificantBit(x);
assert 1 << (globalDepth-i) == npointers;
// System.err.println("addressBits=" + addressBits + ", globalDepth="
// + globalDepth + ", npointers=" + npointers + ", x=" + x
// + ", localDepth=" + i);
// Alternative code from Martyn. We have not compared performance. I suspect
// that the relatively "closed" form solution above will do better, but this
// should be tested.
//
// npointers == 2^(g-i)
// 2^i = (2^g)/npointers
// need to find nb st 2^nb == npointers
// then 2^i == 2^g / 2^nb = 2^(g-nb)
// and i == g-nb
//
// int nb = 0;
// while (npointers != (1 << nb)) ++nb;
//
// return globalDepth - nb;
return i;
}
/**
* Find the offset of the buddy hash table or buddy bucket in the child.
* Each page of the hash tree is logically an ordered array of "buddies"
* sharing the same physical page. When the page is a directory page, the
* buddies are buddy hash tables. When the page is a bucket page, the
* buddies are buddy hash buckets. The returned value is the offset required
* to index into the appropriate buddy hash table or buddy hash bucket in
* the child page.
*
* @param hashBits
* The relevant bits of the hash code used to lookup the child
* within the parent.
* @param globalDepth
* The global depth of the parent.
* @param localDepth
* The local depth of the child page within the parent.
*
* @return The offset of the start of the buddy hash table or buddy hash
* bucket within the child. This is an index into the slots of the
* child. There are m:=(2^addressBits)-1 slots in the child, so the
* returned index is in the half open range [0:m).
*
* @throws IllegalArgumentException
* if the <i>globalDepth</i> is negative.
* @throws IllegalArgumentException
* if the <i>localDepth</i> is greater than the
* <i>globalDepth</i>.
*/
public static int getBuddyOffset(final int hashBits, final int globalDepth,
final int localDepth) {
if (globalDepth < 0)
throw new IllegalArgumentException();
if (localDepth > globalDepth)
throw new IllegalArgumentException();
final int nbits = (globalDepth - localDepth);
// #of address slots in each buddy hash table in the child.
final int slotsPerBuddy = (1 << localDepth);
// index of the buddy in [0:nChildBuddies-1].
final int tmp = BytesUtil.maskOffLSB(hashBits, nbits);
// index of the buddy as a slot offset into the child.
final int childBuddyOffset = tmp * slotsPerBuddy;
return childBuddyOffset;
}
}