CMU15-445 24fall Primer HyperLogLog

P1 HyperLogLog注意事项

我的实现地址:CMU15445: 24fall

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<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_位是记录他的寄存器的位置 x
std::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_mutexcpp17的东西

注意寄存器的数量不能为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));
}

2.Hyperloglog_presto.cpp/.h

1.头文件需要添加的元素

int16_t n_leading_bits_; // b
int32_t num_registers_; // 寄存器的数量
std::mutex mtx_; // write 的锁
std::shared_mutex shlock_; // 读的锁

2.初始化注意EdgeCase 可能给出的值是个负数 注意要构造足够大的vector否则会报错

template <typename KeyType>
HyperLogLogPresto<KeyType>::HyperLogLogPresto(int16_t n_leading_bits) {
if (n_leading_bits < 0) {
n_leading_bits = 0;
}
n_leading_bits_ = n_leading_bits;
num_registers_ = 1 << n_leading_bits_;
dense_bucket_.resize(num_registers_);
cardinality_ = 0;
}

3.overflow_bucket_dense_bucket_的计算需要注意 可以直接使用位运算,在查找右侧0的数量时 朴素的写法也可以实现

template <typename KeyType>
auto HyperLogLogPresto<KeyType>::AddElem(KeyType val) -> void {
/** @TODO(student) Implement this function! */
const hash_t hash_val = CalculateHash(val);
std::bitset<64> binary(hash_val);
size_t j = (binary >> (64 - n_leading_bits_)).to_ulong();
int64_t tot = 0;
// 手写一个函数朴素查找从右到左的0的数量的 lambda 函数
auto find_first_set = [&tot, this](const std::bitset<64> &bits) {
for (size_t i = 0; i <= bits.size() - 1 - n_leading_bits_; ++i) {
if (!bits[i]) {
tot++;
} else {
break;
}
}
tot = tot != 64 ? tot : (64 - n_leading_bits_);
return tot;
};
tot = find_first_set(binary);
int64_t old_value = dense_bucket_[j].to_ullong();
// 计算原来的值 也就是低四位加上高三位
if (overflow_bucket_.find(j) != overflow_bucket_.end()) {
old_value += ((overflow_bucket_[j].to_ullong()) << DENSE_BUCKET_SIZE);
}
int64_t new_value = std::max(old_value, tot);
auto overflow_val = (new_value >> DENSE_BUCKET_SIZE);
std::lock_guard<std::mutex> lock(mtx_); // write lock
if (overflow_val > 0) {
overflow_bucket_[j] = overflow_val;
new_value = new_value - (overflow_val << DENSE_BUCKET_SIZE);
dense_bucket_[j] = new_value;
return;
}
dense_bucket_[j] = new_value;
/*
这是一个不太好的写法 没有检查overflow_bucket_[j]是否存在 而是直接进行了调用
old_value += ((overflow_bucket_[j].to_ullong()) << DENSE_BUCKET_SIZE);
int64_t new_value = std::max(old_value, tot);
overflow_bucket_[j] = overflow_val;
new_value = new_value - (overflow_val << DENSE_BUCKET_SIZE);
dense_bucket_[j] = new_value;
同理 计算cardinality_的循环里面也可以不检查 是否存在
不启用这个检查 if(overflolw_bucker_.find(j) != overflolw_bucker_.end() ) 直接调用
for (int j = 0; j < m; ++j) {
int64_t val = dense_bucket_[j].to_ullong();
val += overflow_bucket_[j].to_ullong() << DENSE_BUCKET_SIZE;
sum += 1.0 / std::pow(2, val);
}
*/
}

3.基数的计算和之前一样 把overflow移到高三位然后和dense加起来就出结果了

template <typename T>
auto HyperLogLogPresto<T>::ComputeCardinality() -> void {
/** @TODO(student) Implement this function! */
std::shared_lock<std::shared_mutex> guard(shlock_); // read lock
double sum = 0.0;
int m = dense_bucket_.size();
if (m == 0) {
return;
}
for (int j = 0; j < m; ++j) {
int64_t val = dense_bucket_[j].to_ullong();
if (overflow_bucket_.find(j) != overflow_bucket_.end()) {
val += overflow_bucket_[j].to_ullong() << DENSE_BUCKET_SIZE;
}
sum += 1.0 / std::pow(2, val);
}
cardinality_ = static_cast<size_t>(std::floor(CONSTANT * m * m / sum));
}

3.格式的注意

make check-format
#工具会输出代码格式不符合规范的地方,需要手动修复或运行 make format 自动修正
make format
#format 目标将自动纠正代码。
make check-clang-tidy-p0
#check-lint 和 check-clang-tidy 目标将打印出必须手动修复以符合风格指南的错误。
make check-lint
#运行后,工具会输出不符合编码风格的地方,需要手动修复。

4.提交

make submit-p0
#会生成压缩包 提交到gradescope就好了
#如果是新版本需要修改CmakeList
set(P0_FILES
"src/include/primer/hyperloglog.h"
"src/include/primer/hyperloglog_presto.h"
"src/primer/hyperloglog.cpp"
"src/primer/hyperloglog_presto.cpp"
)
#然后删除zip重新压缩 具体可以去翻官方repo的修改记录
#在discord的P0也提到了这个问题
posted @   phrink  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示