datadog agent 为每个指标生成唯一 hash 用于聚合。
- 使用 metric_name 计算 hash
- 对指标进行排序
- 对指标进行去重
- 将每个 tag 都用于计算 hash
context = hash(metric_name)
tags = sort(tags)
tags = deduplicate(tags)
for tag in tags:
context = hash(context, tag)
将 context 从 128 位换成 64 位
go 的 map 对于特殊的 key 会有专门的优化。
因此将 context 从 128 位换成 64 位可以有效的提升效率。
使用 murmur3 算法计算 hash
一秒内生成的唯一 hash 最多。
去除排序和重复
通过异或的 hash 算法来去除排序:
context = hash(metric_name)
tags = deduplicate(tags)
for tag in tags:
context = context ^ hash(tag)
通过 hash set 来去重:
- 避免 map 对内存的开销
context = hash(metric_name)
for tag in tags:
htag = hash(tag)
if hashset[htag] == False
context = context ^ htag
hashset[htag] = True
hashset.reset()
当 tags 数量小于或等于 4 的时候:
我们维护一个 seen 的数组。
通过换位将重复的 tag 移动到末尾然后进行 truncate。
tags := tb.data
hashes := tb.hash
ntags := tb.Len()
OUTER:
for i := 0; i < ntags; {
h := hashes[i]
for j := 0; j < i; j++ {
if g.seen[j] == h && tags[j] == tags[i] {
tags[i] = tags[ntags-1]
hashes[i] = hashes[ntags-1]
ntags--
continue OUTER // we do not want to xor multiple times the same tag
}
}
hash ^= h
g.seen[i] = h
i++
}
tb.Truncate(ntags)
当数据量比较大的时候,使用 hashset 算法:
通过 hash & 数组的大小,得到一个下标。
- 如果下标为空,说明没有见过,加入
- 如果下标有值并且相同,跳过
- 如果下标有值并且不相同,加入到
通过 bulk copy 来重置数组。
tags := tb.data
hashes := tb.hash
// reset the `seen` hashset.
// it copies `g.empty` instead of using make because it's faster
// for smaller tag sets, initialize only a portion of the array. when len(tags) is
// close to a power of two, size one up to keep hashset load low.
size := 1 << bits.Len(uint(len(tags)+len(tags)/8))
if size > hashSetSize {
size = hashSetSize
}
mask := uint64(size - 1)
copy(g.seenIdx[:size], g.empty[:size])
ntags := len(tags)
for i := 0; i < ntags; {
h := hashes[i]
j := h & mask // address this hash into the hashset
for {
if g.seenIdx[j] == blank {
// not seen, we will add it to the hash
g.seen[j] = h
g.seenIdx[j] = int16(i)
hash ^= h // add this tag into the hash
i++
break
} else if g.seen[j] == h && tags[g.seenIdx[j]] == tags[i] {
// already seen, we do not want to xor multiple times the same tag
tags[i] = tags[ntags-1]
hashes[i] = hashes[ntags-1]
ntags--
break
} else {
// move 'right' in the hashset because there is already a value,
// in this bucket, which is not the one we're dealing with right now,
// we may have already seen this tag
j = (j + 1) & mask
}
}
}