P1 HyperLogLog注意事项
1.Hyperloglog.cpp/.h
1. 防止内存溢出有关的错误
-
在默认的构造函数中需要定义寄存器的大小
-
//hyperloglog.h /** @todo (student) can add their data structures that support HyperLogLog */ int16_t n_bits_; // b int32_t num_registers_; // m=2^b std::vector<uint8_t> registers_; // m registers or m buckets std::mutex mtx; std::shared_mutex shlock_;
-
//.cpp 定义大小 template <typename KeyType> HyperLogLog<KeyType>::HyperLogLog(int16_t n_bits) {cardinality_ = 0;if (n_bits < 0) n_bits = 0;n_bits_ = n_bits;num_registers_ = (1 << n_bits);registers_.resize(num_registers_, 0); }
2.bitset
的存储问题 和计算问题
-
以下是对
bitset
的介绍 -
bitset<9> bit_(9); 这是他的二进制:000001001 index: 876543210 数值位: 000001001 也就是说他和普通的数组不一样 不是从低到高 而是从高到低 因此PositionOfLeftmostOne函数要求的计算从左到右的1需要倒着计算
-
我在计算
Position
时传入的是完整的bitset
因此他的大小是BITSET_CAPACITY
-
//计算position template <typename KeyType> auto HyperLogLog<KeyType>::PositionOfLeftmostOne(const std::bitset<BITSET_CAPACITY> &bset) const -> uint64_t {/** @TODO(student) Implement this function! *///取消掉前面的nbits_位 直接从有效位开始计算for (int64_t i = BITSET_CAPACITY - 1 - n_bits_; i >= 0; --i) {if (bset[i] == 1) return static_cast<uint64_t>(BITSET_CAPACITY - n_bits_ - i);}return BITSET_CAPACITY - n_bits_ + 1; }
-
虽然
bitset
是从高位到地位进行的存储 但是前面的n_bits_
位依然是当前寄存器的index
,如何计算呢? 我们考虑位运算的左移右移 也就是 -
uint64_t j = (binary >> (BITSET_CAPACITY - n_bits_)).to_ullong(); //桶的编号 //binary是哈希后的64位bitset //to_ullong()是转换为10进制的函数 binary >> (BITSET_CAPACITY - n_bits_) //一共有BITSET_CAPACITY位 出去n_bits_位 剩下的就是有效位 我们把 有效位右移 剩下的就是n_bits_位
3.添加遇到的问题
-
有基本的互斥锁的知识了解到,
AddElem
是一个写入资源的操作 因此我们需要一个互斥锁保护线程 提前在.h
文件添加std::mutex mtx;
成员即可,记得引入头文件#include <mutex>
-
整个文件都会经常用到强制类型转换的操作
static_cast<TYPE>(Value)
的操作必不可少registers_[j] = std::max(registers_[j], static_cast<uint8_t>(p))
更是重要 -
template <typename KeyType> auto HyperLogLog<KeyType>::AddElem(KeyType val) -> void {/** @TODO(student) Implement this function! */hash_t hash = CalculateHash(val);auto binary = ComputeBinary(hash);//保留n_bits 位 即 桶的编号uint64_t j = (binary >> (BITSET_CAPACITY - n_bits_)).to_ullong(); //桶的编号uint64_t p = PositionOfLeftmostOne(binary); //计算1的位置//他的前面的n_bits_位是记录他的寄存器的位置 xstd::lock_guard<std::mutex> lock(mtx);//写入操作registers_[j] = std::max(registers_[j], static_cast<uint8_t>(p)); }
4.计算遇到的问题
-
ComputeCardinality
是一个典型的读操作 需要一个std::shared_mutex
来保护,自然的使用std::shared_lock<std::shared_mutex> guard(shlock_)
,在成员里添加std::shared_mutex shlock_;
即可 不过注意需要引入#include <shared_mutex>
的头文件 ,shared_mutex
是cpp17
的东西 -
注意寄存器的数量不能为0 和为负数
-
//注意强制类型转换和浮点数取整的问题 template <typename KeyType> auto HyperLogLog<KeyType>::ComputeCardinality() -> void {/** @TODO(student) Implement this function! *///读操作std::shared_lock<std::shared_mutex> guard(shlock_);double sum = 0.0;if (num_registers_ == 0) return;for (int32_t j = 0; j < num_registers_; ++j) {sum += 1.00 / std::pow(2, static_cast<double>(registers_[j]));}double E = CONSTANT * num_registers_ * num_registers_ / sum;cardinality_ = static_cast<size_t>(std::floor(E)); }