Files
mfem/general/hash.hpp
T

1029 lines
31 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#ifndef MFEM_HASH
#define MFEM_HASH
#include "../config/config.hpp"
#include "array.hpp"
#include "globals.hpp"
#include "hash_util.hpp"
#include <cstdint>
#include <type_traits>
#include <utility>
namespace mfem
{
/** A concept for items that should be used in HashTable and be accessible by
* hashing two IDs.
*/
struct Hashed2
{
int p1, p2;
int next;
};
/** A concept for items that should be used in HashTable and be accessible by
* hashing four IDs.
*/
struct Hashed4
{
int p1, p2, p3; // NOTE: p4 is neither hashed nor stored
int next;
};
/** HashTable is a container for items that require associative access through
* pairs (or quadruples) of indices:
*
* (p1, p2) -> item
* (p1, p2, p3, p4) -> item
*
* An example of this are edges and faces in a mesh. Each edge is uniquely
* identified by two parent vertices and so can be easily accessed from
* different elements using this class. Similarly for faces.
*
* The order of the p1, p2, ... indices is not relevant as they are sorted
* each time this class is invoked.
*
* There are two main methods this class provides. The Get(...) methods always
* return an item given the two or four indices. If the item did not previously
* exist, the methods creates a new one. The Find(...) methods, on the other
* hand, just return NULL or -1 if the item does not exist.
*
* Each new item is automatically assigned a unique ID - the index of the item
* inside the BlockArray. The IDs may (but need not) be used as p1, p2, ... of
* other items.
*
* The item type (T) needs to follow either the Hashed2 or the Hashed4
* concept. It is easiest to just inherit from these structs.
*
* All items in the container can also be accessed sequentially using the
* provided iterator.
*
* Notes:
* The data structure and implementation is based on a BlockArray<T> which
* provides an efficient item storage that avoids heap fragmentation, and
* index-based item access. The hash table implemented on top of the
* BlockArray provides fast associative (key -> value) access by grouping
* items into bins (buckets) of O(1) size.
* - "id" denotes the index of an item in the underlying BlockArray<T>,
* - "idx" denotes the index of a bin, determined by hashing a key with
* the function `Hash`.
*/
template<typename T>
class HashTable : public BlockArray<T>
{
protected:
typedef BlockArray<T> Base;
public:
/** @brief Main constructor of the HashTable class.
@param[in] block_size The size of the storage blocks of the underlying
BlockArray<T>.
@param[in] init_hash_size The initial size of the hash table. Must be
a power of 2. */
HashTable(int block_size = 16*1024, int init_hash_size = 32*1024);
/// Deep copy
HashTable(const HashTable& other);
/// Copy assignment not supported
HashTable& operator=(const HashTable&) = delete;
~HashTable();
/** @brief Item accessor with key (or parents) the pair p1, p2. Default
construct an item of type T if no value corresponds to the requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed2. */
T* Get(int p1, int p2);
/** @brief Item accessor with key (or parents) the quadruplet p1, p2, p3, p4.
The key p4 is optional. Default construct an item of type T if no value
corresponds to the requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@param[in] p4 Fourth part of the key (optional).
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed4. */
T* Get(int p1, int p2, int p3, int p4 = -1 /* p4 optional */);
/** @brief Get the "id" of the item whose parents are p1, p2, this "id"
corresponding to the index of the item in the underlying BlockArray<T>
object. Default construct an item and "id" if no value corresponds to the
requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed2. */
int GetId(int p1, int p2);
/** @brief Get the "id" of an item, this "id" corresponding to the index of
the item in the underlying BlockArray<T> object. Default construct an item
and "id" if no value corresponds to the requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@param[in] p4 Fourth part of the key (optional).
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed4. */
int GetId(int p1, int p2, int p3, int p4 = -1);
/** @brief Item accessor with key (or parents) the pair p1, p2. Return
NULL if no value corresponds to the requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The item associated to the key (p1,p2).
@warning This method should only be called if T inherits from Hashed2. */
T* Find(int p1, int p2);
/** @brief Item accessor with key (or parents) the quadruplet p1, p2, p3, p4.
The key p4 is optional. Return NULL if no value corresponds to the
requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@param[in] p4 Fourth part of the key (optional).
@return The item associated to the key (p1,p2,p3,p4).
@warning This method should only be called if T inherits from Hashed4. */
T* Find(int p1, int p2, int p3, int p4 = -1);
/** @brief Item const accessor with key (or parents) the pair p1, p2.
Return NULL if no value corresponds to the requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The item associated to the key (p1,p2).
@warning This method should only be called if T inherits from Hashed2. */
const T* Find(int p1, int p2) const;
/** @brief Item const accessor with key (or parents) the quadruplet p1, p2,
p3, p4. The key p4 is optional. Return NULL if no value corresponds to the
requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@param[in] p4 Fourth part of the key (optional).
@return The item associated to the key (p1,p2,p3,p4).
@warning This method should only be called if T inherits from Hashed4. */
const T* Find(int p1, int p2, int p3, int p4 = -1) const;
/** @brief Find the "id" of an item whose parents are p1, p2. Return -1 if it
does not exist.
This "id" corresponds to the index of the item in the underlying
BlockArray<T> object. Default construct an item and "id" if no value
corresponds to the requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed2. */
int FindId(int p1, int p2) const;
/** @brief Find the "id" of an item, this "id" corresponding to the index of
the item in the underlying BlockArray<T> object. Return -1 if it does not
exist. Default construct an item and "id" if no value corresponds to the
requested key.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@param[in] p4 Fourth part of the key (optional).
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed4. */
int FindId(int p1, int p2, int p3, int p4 = -1) const;
/// Return the number of elements currently stored in the HashTable.
int Size() const { return Base::Size() - unused.Size(); }
/// Return the total number of ids (used and unused) in the HashTable.
int NumIds() const { return Base::Size(); }
/// Return the number of free/unused ids in the HashTable.
int NumFreeIds() const { return unused.Size(); }
/** @brief Return true if item @a id exists in (is used by) the container.
@param[in] id Index of the item in the underlying BlockArray<T>.
@warning It is assumed that 0 <= id < NumIds(). */
bool IdExists(int id) const { return (Base::At(id).next != -2); }
/** @brief Remove an item from the hash table.
@param[in] id Index of the item in the underlying BlockArray<T>.
@warning Its @a id will be reused by newly added items. */
void Delete(int id);
/// Remove all items.
void DeleteAll();
/** @brief Allocate an item at @a id. Enlarge the underlying BlockArray if
necessary.
@param[in] id Index of the item in the underlying BlockArray<T>.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@warning This is a special purpose method used when loading data from a
file. Does nothing if the slot @a id has already been allocated. */
void Alloc(int id, int p1, int p2);
/** @brief Reinitialize the internal list of unallocated items.
@warning This is a special purpose method used when loading data from a file. */
void UpdateUnused();
/** @brief Change the key associated with an item.
In other words, makes an item hashed under different parent IDs.
@param[in] id Index of the item in the underlying BlockArray<T>.
@param[in] new_p1 First part of the new key.
@param[in] new_p2 Second part of the new key.
@warning This method should only be called if T inherits from Hashed2. */
void Reparent(int id, int new_p1, int new_p2);
/** @brief Change the key associated with an item.
In other words, makes an item hashed under different parent IDs.
@param[in] id Index of the item in the underlying BlockArray<T>.
@param[in] new_p1 First part of the new key.
@param[in] new_p2 Second part of the new key.
@param[in] new_p3 Third part of the new key.
@param[in] new_p4 Fourth part of the new key (optional).
@warning This method should only be called if T inherits from Hashed4. */
void Reparent(int id, int new_p1, int new_p2, int new_p3, int new_p4 = -1);
/// Return total size of allocated memory (tables plus items), in bytes.
std::size_t MemoryUsage() const;
/// Write details of the memory usage to the mfem output stream.
void PrintMemoryDetail() const;
/// Print a histogram of bin sizes for debugging purposes.
void PrintStats() const;
class iterator : public Base::iterator
{
protected:
friend class HashTable;
typedef typename Base::iterator base;
iterator() { }
iterator(const base &it) : base(it)
{
while (base::good() && (*this)->next == -2) { base::next(); }
}
public:
iterator &operator++()
{
while (base::next(), base::good() && (*this)->next == -2) { }
return *this;
}
};
class const_iterator : public Base::const_iterator
{
protected:
friend class HashTable;
typedef typename Base::const_iterator base;
const_iterator() { }
const_iterator(const base &it) : base(it)
{
while (base::good() && (*this)->next == -2) { base::next(); }
}
public:
const_iterator &operator++()
{
while (base::next(), base::good() && (*this)->next == -2) { }
return *this;
}
};
iterator begin() { return iterator(Base::begin()); }
iterator end() { return iterator(); }
const_iterator begin() const { return const_iterator(Base::cbegin()); }
const_iterator end() const { return const_iterator(); }
const_iterator cbegin() const { return const_iterator(Base::cbegin()); }
const_iterator cend() const { return const_iterator(); }
protected:
/** The hash table: each bin is a linked list of items. For each non-empty
bin, this arrays stores the "id" of the first item in the list, or -1
if the bin is empty. */
int* table;
/** mask = table_size-1. Used for fast modulo operation in Hash(), to wrap
the raw hashed index around the current table size (which must be a power
of two). */
int mask;
/** List of deleted items in the BlockArray<T>. New items are created with
these ids first, before they are appended to the block array. */
Array<int> unused;
/** @brief hash function for Hashed2 items.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The hash key "idx" identifying a bin/bucket.
NOTE: the constants are arbitrary
@warning This method should only be called if T inherits from Hashed2. */
inline int Hash(size_t p1, size_t p2) const
{ return (984120265ul*p1 + 125965121ul*p2) & mask; }
/** @brief hash function for Hashed4 items.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@return The hash key "idx" identifying a bin/bucket.
NOTE: The constants are arbitrary.
NOTE: p4 is not hashed nor stored as p1, p2, p3 identify a face uniquely.
@warning This method should only be called if T inherits from Hashed4. */
inline int Hash(size_t p1, size_t p2, size_t p3) const
{ return (984120265ul*p1 + 125965121ul*p2 + 495698413ul*p3) & mask; }
// Delete() and Reparent() use one of these:
/// Hash function for items of type T that inherit from Hashed2.
inline int Hash(const Hashed2& item) const
{ return Hash(item.p1, item.p2); }
/// Hash function for items of type T that inherit from Hashed4.
inline int Hash(const Hashed4& item) const
{ return Hash(item.p1, item.p2, item.p3); }
/** @brief Search the index of the item associated to the key (p1,p2)
starting from the item with index @a id.
@param[in] id Index of the item in the underlying BlockArray<T>.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed2. */
int SearchList(int id, int p1, int p2) const;
/** @brief Search the index of the item associated to the key (p1,p2,p3,(p4))
starting from the item with index @a id.
@param[in] id Index of the item in the underlying BlockArray<T>.
@param[in] p1 First part of the key.
@param[in] p2 Second part of the key.
@param[in] p3 Third part of the key.
@return The index "id" of the key in the BlockArray<T>.
@warning This method should only be called if T inherits from Hashed4. */
int SearchList(int id, int p1, int p2, int p3) const;
/** @brief Insert the item @a id into bin @a idx.
@param[in] idx The bin/bucket index.
@param[in] id The index of the item in the BlockArray<T>.
@param[in] item The item to insert at the beginning of the linked list.
@warning The method only works with bin @a idx and does not check the
overall fill factor of the hash table. If appropriate, use
CheckRehash() for that. */
inline void Insert(int idx, int id, T &item);
/** @brief Unlink an item @a id from the linked list of bin @a idx.
@param[in] idx The bin/bucket index.
@param[in] id The index of the item in the BlockArray<T>.
@warning The method aborts if the item is not found. */
void Unlink(int idx, int id);
/** @brief Check table fill factor and resize if necessary.
The method checks the average size of the bins (i.e., the fill factor).
If the fill factor is > 2, the table is enlarged (see DoRehash()). */
inline void CheckRehash();
/** @brief Double the size of the hash table (i.e., double the number of bins)
and reinsert all items into the new bins.
NOTE: Rehashing is computationally expensive (O(N) in the number of items),
but since it is only done rarely (when the number of items doubles), the
amortized complexity of inserting an item is still O(1). */
void DoRehash();
/** @brief Return the size of the bin @a idx.
@param[in] idx The index of the bin.
@return The size of the bin. */
int BinSize(int idx) const;
};
/// Hash function for data sequences.
/** Depends on GnuTLS for SHA-256 hashing. */
class HashFunction
{
protected:
void *hash_data;
/// Add a sequence of bytes for hashing
void HashBuffer(const void *buffer, size_t num_bytes);
/// Integer encoding method; result is independent of endianness and type
template <typename int_type_const_iter>
HashFunction &EncodeAndHashInts(int_type_const_iter begin,
int_type_const_iter end);
/// Double encoding method: encode in little-endian byte-order
template <typename double_const_iter>
HashFunction &EncodeAndHashDoubles(double_const_iter begin,
double_const_iter end);
public:
/// Default constructor: initialize the hash function
HashFunction();
/// Destructor
~HashFunction();
/// Add a sequence of bytes for hashing
HashFunction &AppendBytes(const void *seq, size_t num_bytes)
{ HashBuffer(seq, num_bytes); return *this; }
/// Add a sequence of integers for hashing, given as a c-array.
/** Before hashing the sequence is encoded so that the result is independent
of endianness and type: int, long, unsigned, etc. */
template <typename int_type>
HashFunction &AppendInts(const int_type *ints, size_t num_ints)
{ return EncodeAndHashInts(ints, ints + num_ints); }
/// Add a sequence of integers for hashing, given as a fixed-size c-array.
/** Before hashing the sequence is encoded so that the result is independent
of endianness and type: int, long, unsigned, etc. */
template <typename int_type, size_t num_ints>
HashFunction &AppendInts(const int_type (&ints)[num_ints])
{ return EncodeAndHashInts(ints, ints + num_ints); }
/// Add a sequence of integers for hashing, given as a container.
/** Before hashing the sequence is encoded so that the result is independent
of endianness and type: int, long, unsigned, etc. */
template <typename int_type_container>
HashFunction &AppendInts(const int_type_container &ints)
{ return EncodeAndHashInts(ints.begin(), ints.end()); }
/// Add a sequence of doubles for hashing, given as a c-array.
/** Before hashing the sequence is encoded so that the result is independent
of endianness. */
HashFunction &AppendDoubles(const real_t *doubles, size_t num_doubles)
{ return EncodeAndHashDoubles(doubles, doubles + num_doubles); }
/// Add a sequence of doubles for hashing, given as a fixed-size c-array.
/** Before hashing the sequence is encoded so that the result is independent
of endianness. */
template <size_t num_doubles>
HashFunction &AppendDoubles(const real_t (&doubles)[num_doubles])
{ return EncodeAndHashDoubles(doubles, doubles + num_doubles); }
/// Add a sequence of doubles for hashing, given as a container.
/** Before hashing the sequence is encoded so that the result is independent
of endianness. */
template <typename double_container>
HashFunction &AppendDoubles(const double_container &doubles)
{ return EncodeAndHashDoubles(doubles.begin(), doubles.end()); }
/** @brief Return the hash string for the current sequence and reset (clear)
the sequence. */
std::string GetHash() const;
};
// implementation
template<typename T>
HashTable<T>::HashTable(int block_size, int init_hash_size)
: Base(block_size)
{
mask = init_hash_size-1;
MFEM_VERIFY(!(init_hash_size & mask), "init_size must be a power of two.");
table = new int[init_hash_size];
for (int i = 0; i < init_hash_size; i++)
{
table[i] = -1;
}
}
template<typename T>
HashTable<T>::HashTable(const HashTable& other)
: Base(other), mask(other.mask)
{
int size = mask+1;
table = new int[size];
memcpy(table, other.table, size*sizeof(int));
other.unused.Copy(unused);
}
template<typename T>
HashTable<T>::~HashTable()
{
delete [] table;
}
namespace internal
{
inline void sort3(int &a, int &b, int &c)
{
if (a > b) { std::swap(a, b); }
if (a > c) { std::swap(a, c); }
if (b > c) { std::swap(b, c); }
}
inline void sort4(int &a, int &b, int &c, int &d)
{
if (a > b) { std::swap(a, b); }
if (a > c) { std::swap(a, c); }
if (a > d) { std::swap(a, d); }
sort3(b, c, d);
}
inline void sort4_ext(int &a, int &b, int &c, int &d)
{
if (d < 0) // support optional last index
{
sort3(a, b, c);
}
else
{
sort4(a, b, c, d);
}
}
} // internal
template<typename T>
inline T* HashTable<T>::Get(int p1, int p2)
{
return &(Base::At(GetId(p1, p2)));
}
template<typename T>
inline T* HashTable<T>::Get(int p1, int p2, int p3, int p4)
{
return &(Base::At(GetId(p1, p2, p3, p4)));
}
template<typename T>
int HashTable<T>::GetId(int p1, int p2)
{
// search for the item in the hashtable
if (p1 > p2) { std::swap(p1, p2); }
int idx = Hash(p1, p2);
int id = SearchList(table[idx], p1, p2);
if (id >= 0) { return id; }
// not found - use an unused item or create a new one
int new_id;
if (unused.Size())
{
new_id = unused.Last();
unused.DeleteLast();
}
else
{
new_id = Base::Append();
}
T& item = Base::At(new_id);
item.p1 = p1;
item.p2 = p2;
// insert into hashtable
Insert(idx, new_id, item);
CheckRehash();
return new_id;
}
template<typename T>
int HashTable<T>::GetId(int p1, int p2, int p3, int p4)
{
// search for the item in the hashtable
internal::sort4_ext(p1, p2, p3, p4);
int idx = Hash(p1, p2, p3);
int id = SearchList(table[idx], p1, p2, p3);
if (id >= 0) { return id; }
// not found - use an unused item or create a new one
int new_id;
if (unused.Size())
{
new_id = unused.Last();
unused.DeleteLast();
}
else
{
new_id = Base::Append();
}
T& item = Base::At(new_id);
item.p1 = p1;
item.p2 = p2;
item.p3 = p3;
// insert into hashtable
Insert(idx, new_id, item);
CheckRehash();
return new_id;
}
template<typename T>
inline T* HashTable<T>::Find(int p1, int p2)
{
int id = FindId(p1, p2);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
inline T* HashTable<T>::Find(int p1, int p2, int p3, int p4)
{
int id = FindId(p1, p2, p3, p4);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
inline const T* HashTable<T>::Find(int p1, int p2) const
{
int id = FindId(p1, p2);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
inline const T* HashTable<T>::Find(int p1, int p2, int p3, int p4) const
{
int id = FindId(p1, p2, p3, p4);
return (id >= 0) ? &(Base::At(id)) : NULL;
}
template<typename T>
int HashTable<T>::FindId(int p1, int p2) const
{
if (p1 > p2) { std::swap(p1, p2); }
return SearchList(table[Hash(p1, p2)], p1, p2);
}
template<typename T>
int HashTable<T>::FindId(int p1, int p2, int p3, int p4) const
{
internal::sort4_ext(p1, p2, p3, p4);
return SearchList(table[Hash(p1, p2, p3)], p1, p2, p3);
}
template<typename T>
int HashTable<T>::SearchList(int id, int p1, int p2) const
{
while (id >= 0)
{
const T& item = Base::At(id);
if (item.p1 == p1 && item.p2 == p2) { return id; }
id = item.next;
}
return -1;
}
template<typename T>
int HashTable<T>::SearchList(int id, int p1, int p2, int p3) const
{
while (id >= 0)
{
const T& item = Base::At(id);
if (item.p1 == p1 && item.p2 == p2 && item.p3 == p3) { return id; }
id = item.next;
}
return -1;
}
template<typename T>
inline void HashTable<T>::CheckRehash()
{
const int fill_factor = 2;
// is the table overfull?
if (Base::Size() > (mask+1) * fill_factor)
{
DoRehash();
}
}
template<typename T>
void HashTable<T>::DoRehash()
{
delete [] table;
// double the table size
int new_table_size = 2*(mask+1);
table = new int[new_table_size];
for (int i = 0; i < new_table_size; i++) { table[i] = -1; }
mask = new_table_size-1;
#if defined(MFEM_DEBUG) && !defined(MFEM_USE_MPI)
mfem::out << _MFEM_FUNC_NAME << ": rehashing to size " << new_table_size
<< std::endl;
#endif
// reinsert all items
for (iterator it = begin(); it != end(); ++it)
{
Insert(Hash(*it), it.index(), *it);
}
}
template<typename T>
inline void HashTable<T>::Insert(int idx, int id, T &item)
{
// add item at the beginning of the linked list
item.next = table[idx];
table[idx] = id;
}
template<typename T>
void HashTable<T>::Unlink(int idx, int id)
{
// remove item from the linked list
int* p_id = table + idx;
while (*p_id >= 0)
{
T& item = Base::At(*p_id);
if (*p_id == id)
{
*p_id = item.next;
return;
}
p_id = &(item.next);
}
MFEM_ABORT("HashTable<>::Unlink: item not found!");
}
template<typename T>
void HashTable<T>::Delete(int id)
{
T& item = Base::At(id);
Unlink(Hash(item), id);
item.next = -2; // mark item as unused
unused.Append(id); // add its id to the unused ids
}
template<typename T>
void HashTable<T>::DeleteAll()
{
Base::DeleteAll();
for (int i = 0; i <= mask; i++) { table[i] = -1; }
unused.DeleteAll();
}
template<typename T>
void HashTable<T>::Alloc(int id, int p1, int p2)
{
// enlarge the BlockArray to hold 'id'
while (id >= Base::Size())
{
Base::At(Base::Append()).next = -2; // append "unused" items
}
T& item = Base::At(id);
if (item.next == -2)
{
item.next = -1;
item.p1 = p1;
item.p2 = p2;
Insert(Hash(p1, p2), id, item);
CheckRehash();
}
}
template<typename T>
void HashTable<T>::UpdateUnused()
{
unused.DeleteAll();
for (int i = 0; i < Base::Size(); i++)
{
if (Base::At(i).next == -2) { unused.Append(i); }
}
}
template<typename T>
void HashTable<T>::Reparent(int id, int new_p1, int new_p2)
{
T& item = Base::At(id);
Unlink(Hash(item), id);
if (new_p1 > new_p2) { std::swap(new_p1, new_p2); }
item.p1 = new_p1;
item.p2 = new_p2;
// reinsert under new parent IDs
int new_idx = Hash(new_p1, new_p2);
Insert(new_idx, id, item);
}
template<typename T>
void HashTable<T>::Reparent(int id,
int new_p1, int new_p2, int new_p3, int new_p4)
{
T& item = Base::At(id);
Unlink(Hash(item), id);
internal::sort4_ext(new_p1, new_p2, new_p3, new_p4);
item.p1 = new_p1;
item.p2 = new_p2;
item.p3 = new_p3;
// reinsert under new parent IDs
int new_idx = Hash(new_p1, new_p2, new_p3);
Insert(new_idx, id, item);
}
template<typename T>
std::size_t HashTable<T>::MemoryUsage() const
{
return (mask+1) * sizeof(int) + Base::MemoryUsage() + unused.MemoryUsage();
}
template<typename T>
void HashTable<T>::PrintMemoryDetail() const
{
mfem::out << Base::MemoryUsage() << " + " << (mask+1) * sizeof(int)
<< " + " << unused.MemoryUsage();
}
template<typename T>
int HashTable<T>::BinSize(int idx) const
{
int count = 0;
int id = table[idx];
while (id >= 0)
{
const T& item = Base::At(id);
id = item.next;
count++;
}
return count;
}
template<typename T>
void HashTable<T>::PrintStats() const
{
int table_size = mask+1;
mfem::out << "Hash table size: " << table_size << "\n";
mfem::out << "Item count: " << Size() << "\n";
mfem::out << "BlockArray size: " << Base::Size() << "\n";
const int H = 16;
int hist[H];
for (int i = 0; i < H; i++) { hist[i] = 0; }
for (int i = 0; i < table_size; i++)
{
int bs = BinSize(i);
if (bs >= H) { bs = H-1; }
hist[bs]++;
}
mfem::out << "Bin size histogram:\n";
for (int i = 0; i < H; i++)
{
mfem::out << " size " << i << ": "
<< hist[i] << " bins" << std::endl;
}
}
template <typename int_type_const_iter>
HashFunction &HashFunction::EncodeAndHashInts(int_type_const_iter begin,
int_type_const_iter end)
{
// For hashing, an integer k is encoded as follows:
// * 1 byte = sign_bit(k) + num_bytes(k), where
// - sign_bit(k) = (k >= 0) ? 0 : 128
// - num_bytes(k) = minimum number of bytes needed to represent abs(k)
// with the convention that num_bytes(0) = 0.
// * num_bytes(k) bytes = the bytes of abs(k), starting with the least
// significant byte.
static_assert(
std::is_integral<
/**/ typename std::remove_reference<decltype(*begin)>::type
/**/ >::value,
"invalid iterator type");
// Skip encoding if hashing is not available:
if (hash_data == nullptr) { return *this; }
constexpr int max_buffer_bytes = 64*1024;
unsigned char buffer[max_buffer_bytes];
int buffer_counter = 0;
while (begin != end)
{
int byte_counter = 0;
auto k = *begin;
buffer[buffer_counter] = (k >= 0) ? 0 : (k = -k, 128);
while (k != 0)
{
byte_counter++;
buffer[buffer_counter + byte_counter] = (unsigned char)(k % 256);
k /= 256; // (k >>= 8) results in error, e.g. for 'char'
}
buffer[buffer_counter] |= byte_counter;
buffer_counter += (byte_counter + 1);
++begin;
if (begin == end ||
buffer_counter + (1 + sizeof(*begin)) > max_buffer_bytes)
{
HashBuffer(buffer, buffer_counter);
buffer_counter = 0;
}
}
return *this;
}
template <typename double_const_iter>
HashFunction &HashFunction::EncodeAndHashDoubles(double_const_iter begin,
double_const_iter end)
{
// For hashing, a double is encoded in little endian byte-order.
static_assert(
std::is_same<decltype(*begin), const real_t &>::value,
"invalid iterator type");
// Skip encoding if hashing is not available:
if (hash_data == nullptr) { return *this; }
constexpr int max_buffer_bytes = 64*1024;
unsigned char buffer[max_buffer_bytes];
int buffer_counter = 0;
while (begin != end)
{
auto k = reinterpret_cast<const uint64_t &>(*begin);
for (int i = 0; i != 7; i++)
{
buffer[buffer_counter++] = (unsigned char)(k & 255); k >>= 8;
}
buffer[buffer_counter++] = (unsigned char)k;
++begin;
if (begin == end || buffer_counter + 8 > max_buffer_bytes)
{
HashBuffer(buffer, buffer_counter);
buffer_counter = 0;
}
}
return *this;
}
} // namespace mfem
#endif