diff --git a/src/storm/builder/ExplicitModelBuilder.cpp b/src/storm/builder/ExplicitModelBuilder.cpp index 7a552e9bd..48d5b2a75 100644 --- a/src/storm/builder/ExplicitModelBuilder.cpp +++ b/src/storm/builder/ExplicitModelBuilder.cpp @@ -149,7 +149,9 @@ namespace storm { stateRemapping.get()[currentIndex] = currentRowGroup; } - STORM_LOG_TRACE("Exploring state with id " << currentIndex << "."); + if (currentIndex % 100000 == 0) { + STORM_LOG_TRACE("Exploring state with id " << currentIndex << "."); + } generator->load(currentState); storm::generator::StateBehavior<ValueType, StateType> behavior = generator->expand(stateToIdCallback); diff --git a/src/storm/storage/BitVector.cpp b/src/storm/storage/BitVector.cpp index 469fb8441..e5498b82c 100644 --- a/src/storm/storage/BitVector.cpp +++ b/src/storm/storage/BitVector.cpp @@ -999,22 +999,6 @@ namespace storm { out << std::endl; } - std::size_t NonZeroBitVectorHash::operator()(storm::storage::BitVector const& bitvector) const { - STORM_LOG_ASSERT(bitvector.size() > 0, "Cannot hash bit vector of zero size."); - std::size_t result = 0; - - for (uint_fast64_t index = 0; index < bitvector.bucketCount(); ++index) { - result ^= result << 3; - result ^= result >> bitvector.getAsInt(index << 6, 5); - } - - // Erase the last bit and add one to definitely make this hash value non-zero. - result &= ~1ull; - result += 1; - - return result; - } - // All necessary explicit template instantiations. template BitVector::BitVector(uint_fast64_t length, std::vector<uint_fast64_t>::iterator begin, std::vector<uint_fast64_t>::iterator end); template BitVector::BitVector(uint_fast64_t length, std::vector<uint_fast64_t>::const_iterator begin, std::vector<uint_fast64_t>::const_iterator end); @@ -1028,7 +1012,6 @@ namespace storm { } namespace std { - std::size_t hash<storm::storage::BitVector>::operator()(storm::storage::BitVector const& bitvector) const { return boost::hash_range(bitvector.buckets, bitvector.buckets + bitvector.bucketCount()); } diff --git a/src/storm/storage/BitVector.h b/src/storm/storage/BitVector.h index bcb56ae0d..0b5a7670b 100644 --- a/src/storm/storage/BitVector.h +++ b/src/storm/storage/BitVector.h @@ -501,7 +501,6 @@ namespace storm { friend std::ostream& operator<<(std::ostream& out, BitVector const& bitVector); friend struct std::hash<storm::storage::BitVector>; - friend struct NonZeroBitVectorHash; private: /*! @@ -571,11 +570,6 @@ namespace storm { static const uint_fast64_t mod64mask = (1 << 6) - 1; }; - // A hashing functor that guarantees that the result of the hash is never going to be -1. - struct NonZeroBitVectorHash { - std::size_t operator()(storm::storage::BitVector const& bv) const; - }; - } // namespace storage } // namespace storm diff --git a/src/storm/storage/BitVectorHashMap.cpp b/src/storm/storage/BitVectorHashMap.cpp index a522a5863..c1d9d46d1 100644 --- a/src/storm/storage/BitVectorHashMap.cpp +++ b/src/storm/storage/BitVectorHashMap.cpp @@ -9,47 +9,43 @@ namespace storm { namespace storage { - template<class ValueType, class Hash1, class Hash2> - const std::vector<std::size_t> BitVectorHashMap<ValueType, Hash1, Hash2>::sizes = {5, 13, 31, 79, 163, 277, 499, 1021, 2029, 3989, 8059, 16001, 32099, 64301, 127921, 256499, 511111, 1024901, 2048003, 4096891, 8192411, 15485863, 32142191, 64285127, 128572517, 257148523, 514299959, 1028599919, 2057199839, 4114399697, 8228799419, 16457598791, 32915197603, 65830395223}; + template<class ValueType, class Hash> + const std::vector<std::size_t> BitVectorHashMap<ValueType, Hash>::sizes = {5, 13, 31, 79, 163, 277, 499, 1021, 2029, 3989, 8059, 16001, 32099, 64301, 127921, 256499, 511111, 1024901, 2048003, 4096891, 8192411, 15485863, 32142191, 64285127, 128572517, 257148523, 514299959, 1028599919, 2057199839, 4114399697, 8228799419, 16457598791, 32915197603, 65830395223}; - template<class ValueType, class Hash1, class Hash2> - BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator::BitVectorHashMapIterator(BitVectorHashMap const& map, BitVector::const_iterator indexIt) : map(map), indexIt(indexIt) { -#ifndef NDEBUG - for (uint64_t i = 0; i < sizes.size() - 1; ++i) { - STORM_LOG_ASSERT(sizes[i] < sizes[i + 1], "Expected stricly increasing sizes."); - } -#endif + template<class ValueType, class Hash> + BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator::BitVectorHashMapIterator(BitVectorHashMap const& map, BitVector::const_iterator indexIt) : map(map), indexIt(indexIt) { + // Intentionally left empty. } - template<class ValueType, class Hash1, class Hash2> - bool BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator::operator==(BitVectorHashMapIterator const& other) { + template<class ValueType, class Hash> + bool BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator::operator==(BitVectorHashMapIterator const& other) { return &map == &other.map && *indexIt == *other.indexIt; } - template<class ValueType, class Hash1, class Hash2> - bool BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator::operator!=(BitVectorHashMapIterator const& other) { + template<class ValueType, class Hash> + bool BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator::operator!=(BitVectorHashMapIterator const& other) { return !(*this == other); } - template<class ValueType, class Hash1, class Hash2> - typename BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator& BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator::operator++(int) { + template<class ValueType, class Hash> + typename BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator& BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator::operator++(int) { ++indexIt; return *this; } - template<class ValueType, class Hash1, class Hash2> - typename BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator& BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator::operator++() { + template<class ValueType, class Hash> + typename BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator& BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator::operator++() { ++indexIt; return *this; } - template<class ValueType, class Hash1, class Hash2> - std::pair<storm::storage::BitVector, ValueType> BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMapIterator::operator*() const { + template<class ValueType, class Hash> + std::pair<storm::storage::BitVector, ValueType> BitVectorHashMap<ValueType, Hash>::BitVectorHashMapIterator::operator*() const { return map.getBucketAndValue(*indexIt); } - template<class ValueType, class Hash1, class Hash2> - BitVectorHashMap<ValueType, Hash1, Hash2>::BitVectorHashMap(uint64_t bucketSize, uint64_t initialSize, double loadFactor) : loadFactor(loadFactor), bucketSize(bucketSize), numberOfElements(0) { + template<class ValueType, class Hash> + BitVectorHashMap<ValueType, Hash>::BitVectorHashMap(uint64_t bucketSize, uint64_t initialSize, double loadFactor) : loadFactor(loadFactor), bucketSize(bucketSize), numberOfElements(0) { STORM_LOG_ASSERT(bucketSize % 64 == 0, "Bucket size must be a multiple of 64."); currentSizeIterator = std::find_if(sizes.begin(), sizes.end(), [=] (uint64_t value) { return value > initialSize; } ); @@ -57,28 +53,42 @@ namespace storm { buckets = storm::storage::BitVector(bucketSize * *currentSizeIterator); occupied = storm::storage::BitVector(*currentSizeIterator); values = std::vector<ValueType>(*currentSizeIterator); + +#ifndef NDEBUG + for (uint64_t i = 0; i < sizes.size() - 1; ++i) { + STORM_LOG_ASSERT(sizes[i] < sizes[i + 1], "Expected stricly increasing sizes."); + } + numberOfInsertions = 0; + numberOfInsertionProbingSteps = 0; + numberOfFinds = 0; + numberOfFindProbingSteps = 0; +#endif } - template<class ValueType, class Hash1, class Hash2> - bool BitVectorHashMap<ValueType, Hash1, Hash2>::isBucketOccupied(uint_fast64_t bucket) const { + template<class ValueType, class Hash> + bool BitVectorHashMap<ValueType, Hash>::isBucketOccupied(uint_fast64_t bucket) const { return occupied.get(bucket); } - template<class ValueType, class Hash1, class Hash2> - std::size_t BitVectorHashMap<ValueType, Hash1, Hash2>::size() const { + template<class ValueType, class Hash> + std::size_t BitVectorHashMap<ValueType, Hash>::size() const { return numberOfElements; } - template<class ValueType, class Hash1, class Hash2> - std::size_t BitVectorHashMap<ValueType, Hash1, Hash2>::capacity() const { + template<class ValueType, class Hash> + std::size_t BitVectorHashMap<ValueType, Hash>::capacity() const { return *currentSizeIterator; } - template<class ValueType, class Hash1, class Hash2> - void BitVectorHashMap<ValueType, Hash1, Hash2>::increaseSize() { + template<class ValueType, class Hash> + void BitVectorHashMap<ValueType, Hash>::increaseSize() { ++currentSizeIterator; STORM_LOG_ASSERT(currentSizeIterator != sizes.end(), "Hash map became to big."); +#ifndef NDEBUG + STORM_LOG_TRACE("Increasing size of hash map from " << *(currentSizeIterator - 1) << " to " << *currentSizeIterator << ". Stats: " << numberOfFinds << " finds (avg. " << (numberOfFindProbingSteps / static_cast<double>(numberOfFinds)) << " probing steps), " << numberOfInsertions << " insertions (avg. " << (numberOfInsertionProbingSteps / static_cast<double>(numberOfInsertions)) << " probing steps)."); +#else STORM_LOG_TRACE("Increasing size of hash map from " << *(currentSizeIterator - 1) << " to " << *currentSizeIterator << "."); +#endif // Create new containers and swap them with the old ones. numberOfElements = 0; @@ -124,8 +134,8 @@ namespace storm { } } - template<class ValueType, class Hash1, class Hash2> - bool BitVectorHashMap<ValueType, Hash1, Hash2>::insertWithoutIncreasingSize(storm::storage::BitVector const& key, ValueType const& value) { + template<class ValueType, class Hash> + bool BitVectorHashMap<ValueType, Hash>::insertWithoutIncreasingSize(storm::storage::BitVector const& key, ValueType const& value) { std::tuple<bool, std::size_t, bool> flagBucketTuple = this->findBucketToInsert<false>(key); if (std::get<2>(flagBucketTuple)) { return false; @@ -143,18 +153,18 @@ namespace storm { } } - template<class ValueType, class Hash1, class Hash2> - ValueType BitVectorHashMap<ValueType, Hash1, Hash2>::findOrAdd(storm::storage::BitVector const& key, ValueType const& value) { + template<class ValueType, class Hash> + ValueType BitVectorHashMap<ValueType, Hash>::findOrAdd(storm::storage::BitVector const& key, ValueType const& value) { return findOrAddAndGetBucket(key, value).first; } - template<class ValueType, class Hash1, class Hash2> - void BitVectorHashMap<ValueType, Hash1, Hash2>::setOrAdd(storm::storage::BitVector const& key, ValueType const& value) { + template<class ValueType, class Hash> + void BitVectorHashMap<ValueType, Hash>::setOrAdd(storm::storage::BitVector const& key, ValueType const& value) { setOrAddAndGetBucket(key, value); } - template<class ValueType, class Hash1, class Hash2> - std::pair<ValueType, std::size_t> BitVectorHashMap<ValueType, Hash1, Hash2>::findOrAddAndGetBucket(storm::storage::BitVector const& key, ValueType const& value) { + template<class ValueType, class Hash> + std::pair<ValueType, std::size_t> BitVectorHashMap<ValueType, Hash>::findOrAddAndGetBucket(storm::storage::BitVector const& key, ValueType const& value) { // If the load of the map is too high, we increase the size. if (numberOfElements >= loadFactor * *currentSizeIterator) { this->increaseSize(); @@ -174,8 +184,8 @@ namespace storm { } } - template<class ValueType, class Hash1, class Hash2> - std::size_t BitVectorHashMap<ValueType, Hash1, Hash2>::setOrAddAndGetBucket(storm::storage::BitVector const& key, ValueType const& value) { + template<class ValueType, class Hash> + std::size_t BitVectorHashMap<ValueType, Hash>::setOrAddAndGetBucket(storm::storage::BitVector const& key, ValueType const& value) { // If the load of the map is too high, we increase the size. if (numberOfElements >= loadFactor * *currentSizeIterator) { this->increaseSize(); @@ -193,42 +203,51 @@ namespace storm { return std::get<1>(flagBucketTuple); } - template<class ValueType, class Hash1, class Hash2> - ValueType BitVectorHashMap<ValueType, Hash1, Hash2>::getValue(storm::storage::BitVector const& key) const { + template<class ValueType, class Hash> + ValueType BitVectorHashMap<ValueType, Hash>::getValue(storm::storage::BitVector const& key) const { std::pair<bool, std::size_t> flagBucketPair = this->findBucket(key); STORM_LOG_ASSERT(flagBucketPair.first, "Unknown key."); return values[flagBucketPair.second]; } - template<class ValueType, class Hash1, class Hash2> - bool BitVectorHashMap<ValueType, Hash1, Hash2>::contains(storm::storage::BitVector const& key) const { + template<class ValueType, class Hash> + bool BitVectorHashMap<ValueType, Hash>::contains(storm::storage::BitVector const& key) const { return findBucket(key).first; } - template<class ValueType, class Hash1, class Hash2> - typename BitVectorHashMap<ValueType, Hash1, Hash2>::const_iterator BitVectorHashMap<ValueType, Hash1, Hash2>::begin() const { + template<class ValueType, class Hash> + typename BitVectorHashMap<ValueType, Hash>::const_iterator BitVectorHashMap<ValueType, Hash>::begin() const { return const_iterator(*this, occupied.begin()); } - template<class ValueType, class Hash1, class Hash2> - typename BitVectorHashMap<ValueType, Hash1, Hash2>::const_iterator BitVectorHashMap<ValueType, Hash1, Hash2>::end() const { + template<class ValueType, class Hash> + typename BitVectorHashMap<ValueType, Hash>::const_iterator BitVectorHashMap<ValueType, Hash>::end() const { return const_iterator(*this, occupied.end()); } - template<class ValueType, class Hash1, class Hash2> - std::pair<bool, std::size_t> BitVectorHashMap<ValueType, Hash1, Hash2>::findBucket(storm::storage::BitVector const& key) const { - uint_fast64_t initialHash = hasher1(key) % *currentSizeIterator; - uint_fast64_t stride = hasher2(key); + template<class ValueType, class Hash> + uint_fast64_t BitVectorHashMap<ValueType, Hash>::getNextBucketInProbingSequence(uint_fast64_t currentValue, uint_fast64_t step) const { + return (currentValue + step + step*step) % *currentSizeIterator; + } + + template<class ValueType, class Hash> + std::pair<bool, std::size_t> BitVectorHashMap<ValueType, Hash>::findBucket(storm::storage::BitVector const& key) const { +#ifndef NDEBUG + ++numberOfFinds; +#endif + uint_fast64_t initialHash = hasher(key) % *currentSizeIterator; uint_fast64_t bucket = initialHash; - uint_fast64_t counter = 0; + uint_fast64_t i = 0; while (isBucketOccupied(bucket)) { - ++counter; + ++i; +#ifndef NDEBUG + ++numberOfFindProbingSteps; +#endif if (buckets.matches(bucket * bucketSize, key)) { return std::make_pair(true, bucket); } - bucket += stride; - bucket %= *currentSizeIterator; + bucket = getNextBucketInProbingSequence(bucket, i); if (bucket == initialHash) { return std::make_pair(false, bucket); @@ -238,26 +257,30 @@ namespace storm { return std::make_pair(false, bucket); } - template<class ValueType, class Hash1, class Hash2> + template<class ValueType, class Hash> template<bool increaseStorage> - std::tuple<bool, std::size_t, bool> BitVectorHashMap<ValueType, Hash1, Hash2>::findBucketToInsert(storm::storage::BitVector const& key) { - uint_fast64_t initialHash = hasher1(key) % *currentSizeIterator; - uint_fast64_t stride = hasher2(key); + std::tuple<bool, std::size_t, bool> BitVectorHashMap<ValueType, Hash>::findBucketToInsert(storm::storage::BitVector const& key) { +#ifndef NDEBUG + ++numberOfInsertions; +#endif + uint_fast64_t initialHash = hasher(key) % *currentSizeIterator; uint_fast64_t bucket = initialHash; - uint_fast64_t counter = 0; + uint64_t i = 0; while (isBucketOccupied(bucket)) { - ++counter; + ++i; +#ifndef NDEBUG + ++numberOfInsertionProbingSteps; +#endif if (buckets.matches(bucket * bucketSize, key)) { return std::make_tuple(true, bucket, false); } - bucket += stride; - bucket %= *currentSizeIterator; + bucket = getNextBucketInProbingSequence(bucket, i); if (bucket == initialHash) { if (increaseStorage) { this->increaseSize(); - bucket = initialHash = hasher1(key) % *currentSizeIterator; + bucket = initialHash = hasher(key) % *currentSizeIterator; } else { return std::make_tuple(false, bucket, true); } @@ -267,13 +290,13 @@ namespace storm { return std::make_tuple(false, bucket, false); } - template<class ValueType, class Hash1, class Hash2> - std::pair<storm::storage::BitVector, ValueType> BitVectorHashMap<ValueType, Hash1, Hash2>::getBucketAndValue(std::size_t bucket) const { + template<class ValueType, class Hash> + std::pair<storm::storage::BitVector, ValueType> BitVectorHashMap<ValueType, Hash>::getBucketAndValue(std::size_t bucket) const { return std::make_pair(buckets.get(bucket * bucketSize, bucketSize), values[bucket]); } - template<class ValueType, class Hash1, class Hash2> - void BitVectorHashMap<ValueType, Hash1, Hash2>::remap(std::function<ValueType(ValueType const&)> const& remapping) { + template<class ValueType, class Hash> + void BitVectorHashMap<ValueType, Hash>::remap(std::function<ValueType(ValueType const&)> const& remapping) { for (auto pos : occupied) { values[pos] = remapping(values[pos]); } diff --git a/src/storm/storage/BitVectorHashMap.h b/src/storm/storage/BitVectorHashMap.h index a5941e2f8..ab9e6c4be 100644 --- a/src/storm/storage/BitVectorHashMap.h +++ b/src/storm/storage/BitVectorHashMap.h @@ -14,7 +14,7 @@ namespace storm { * queries and insertions are supported. Also, the keys must be bit vectors with a length that is a multiple of * 64. */ - template<typename ValueType, typename Hash1 = std::hash<storm::storage::BitVector>, class Hash2 = storm::storage::NonZeroBitVectorHash> + template<typename ValueType, typename Hash = std::hash<storm::storage::BitVector>> class BitVectorHashMap { public: class BitVectorHashMapIterator { @@ -204,6 +204,11 @@ namespace storm { */ void increaseSize(); + /*! + * Computes the next bucket in the probing sequence. + */ + uint_fast64_t getNextBucketInProbingSequence(uint_fast64_t currentValue, uint_fast64_t step) const; + // The load factor determining when the size of the map is increased. double loadFactor; @@ -229,11 +234,18 @@ namespace storm { std::vector<std::size_t>::const_iterator currentSizeIterator; // Functor object that are used to perform the actual hashing. - Hash1 hasher1; - Hash2 hasher2; + Hash hasher; // A static table that produces the next possible size of the hash table. static const std::vector<std::size_t> sizes; + +#ifndef NDEBUG + // Some performance metrics. + mutable uint64_t numberOfInsertions; + mutable uint64_t numberOfInsertionProbingSteps; + mutable uint64_t numberOfFinds; + mutable uint64_t numberOfFindProbingSteps; +#endif }; }