Murmurhash-go源码阅读

源码地址:
https://github.com/spaolacci/murmur3
如何理解它说的原生go实现:底层实现不依赖c和c++的库,只用go的标准库实现。

分析一下TestRefStrings中的对于128位的murmur3算法进行分析

// New128WithSeed returns a 128-bit hasher set with explicit seed value
func New128WithSeed(seed uint32) Hash128 {
	d := new(digest128)
	d.seed = seed
	d.bmixer = d   // 这里有点奇怪
	d.Reset()
	return d
}

type digest128 struct {
	digest
	h1 uint64 // Unfinalized running hash part 1.
	h2 uint64 // Unfinalized running hash part 2.
}

type digest struct {
	clen int      // Digested input cumulative length.
	tail []byte   // 0 to Size()-1 bytes view of `buf'.
	buf  [16]byte // Expected (but not required) to be Size() large.
	seed uint32   // Seed for initializing the hash.
	bmixer
}
// 另外digest重写了bmixer的函数,先省略,等用的时候看。

type bmixer interface {
	bmix(p []byte) (tail []byte)
	Size() (n int)
	reset()
}

type Hash128 interface {
	hash.Hash
	Sum128() (uint64, uint64)
}


看下这一段代码,很有意思,用var来保证digest128实现了所有的方法。

// Make sure interfaces are correctly implemented.
var (
	_ hash.Hash = new(digest128)
	_ Hash128   = new(digest128)
	_ bmixer    = new(digest128)
)

可以看到我们创建一个hasher,type=interface, 然后实现了各种各样的function。
关于

d.bmixer = d

这一行代码,这么写,猜测只是用来实现bmixer的方法。方便digest128 重写一系列函数。
murmur128.go

func (d *digest128) bmix(p []byte) (tail []byte) {
    //...
}
//等

总之,我们现在new得到了一个hasher,并且做了一些初始化工作:

func (d *digest) Reset() {
	d.clen = 0
	d.tail = nil
	d.bmixer.reset()
}
func (d *digest128) reset() { 
    d.h1, d.h2 = uint64(d.seed), uint64(d.seed) 
}


这里可以看到digest中bmixer里面的digest指针指向的就是digest本身。。。这是个俄罗斯套娃。

接着把需要hash的string写进去:

h128.Write([]byte(elem.s))

更新了digest中的buf、tail、clen等内容。然后就进行hash

v1, v2 := h128.Sum128()

func (d *digest128) Sum128() (h1, h2 uint64) {

	h1, h2 = d.h1, d.h2

	var k1, k2 uint64
	switch len(d.tail) & 15 {  
	case 15:
		k2 ^= uint64(d.tail[14]) << 48
		fallthrough
	case 14:
		k2 ^= uint64(d.tail[13]) << 40
		fallthrough
	case 13:
		k2 ^= uint64(d.tail[12]) << 32
		fallthrough
	case 12:
		k2 ^= uint64(d.tail[11]) << 24
		fallthrough
	case 11:
		k2 ^= uint64(d.tail[10]) << 16
		fallthrough
	case 10:
		k2 ^= uint64(d.tail[9]) << 8
		fallthrough
	case 9:
		k2 ^= uint64(d.tail[8]) << 0

		k2 *= c2_128
		k2 = bits.RotateLeft64(k2, 33)
		k2 *= c1_128
		h2 ^= k2

		fallthrough

	case 8:
		k1 ^= uint64(d.tail[7]) << 56
		fallthrough
	case 7:
		k1 ^= uint64(d.tail[6]) << 48
		fallthrough
	case 6:
		k1 ^= uint64(d.tail[5]) << 40
		fallthrough
	case 5:
		k1 ^= uint64(d.tail[4]) << 32
		fallthrough
	case 4:
		k1 ^= uint64(d.tail[3]) << 24
		fallthrough
	case 3:
		k1 ^= uint64(d.tail[2]) << 16
		fallthrough
	case 2:
		k1 ^= uint64(d.tail[1]) << 8
		fallthrough
	case 1:
		k1 ^= uint64(d.tail[0]) << 0
		k1 *= c1_128
		k1 = bits.RotateLeft64(k1, 31)
		k1 *= c2_128
		h1 ^= k1
	}

	h1 ^= uint64(d.clen)
	h2 ^= uint64(d.clen)

	h1 += h2
	h2 += h1

	h1 = fmix64(h1)
	h2 = fmix64(h2)

	h1 += h2
	h2 += h1

	return h1, h2
}

func fmix64(k uint64) uint64 {
	k ^= k >> 33
	k *= 0xff51afd7ed558ccd
	k ^= k >> 33
	k *= 0xc4ceb9fe1a85ec53
	k ^= k >> 33
	return k
}

golang 里面的switch case 满足其中一条分支的话会选择其分支执行,执行完毕会自动break,但fallthrough指令会强制执行下一个分支。
注意位运算的^和&

算法思路:
1 拿string的len & 15, 其实就是对16取膜运算。
拿前7位分别左移48、40、32、24、16、8、0位然后一起做或运算。得到k2. 然后用k2✖️一个很大的数,然后左移33位,然后再乘一个很大的数得到h1.
拿后8位分别左移56、48、40、32、24、16、8、0位然后一起做或运算,✖️一个很大的数,左移31位然后在✖️一个很大的数得到k1.
然后对string.len 取或运算。
互相加
最后得到h1和h2.

参考一下64和32位的实现

func (d *digest64) Sum64() uint64 {
	h1, _ := (*digest128)(d).Sum128()
	return h1
}

func (d *digest32) Sum32() (h1 uint32) {

	h1 = d.h1

	var k1 uint32
	switch len(d.tail) & 3 {
	case 3:
		k1 ^= uint32(d.tail[2]) << 16
		fallthrough
	case 2:
		k1 ^= uint32(d.tail[1]) << 8
		fallthrough
	case 1:
		k1 ^= uint32(d.tail[0])
		k1 *= c1_32
		k1 = bits.RotateLeft32(k1, 15)
		k1 *= c2_32
		h1 ^= k1
	}

	h1 ^= uint32(d.clen)

	h1 ^= h1 >> 16
	h1 *= 0x85ebca6b
	h1 ^= h1 >> 13
	h1 *= 0xc2b2ae35
	h1 ^= h1 >> 16

	return h1
}

所以murmur快是因为位运算,那为什么这一堆位运算能够使碰撞低呢。
参考一下:https://kknews.cc/zh-hk/code/naglky2.html
核心就是不断进行左移,至于某些const数字为什么是那个值,是通过计算、实验得出来的。。。然后具体的也没资料说,难受啊。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
grpc-go是一款受欢迎的Go语言实现的高性能RPC框架,用于构建分布式系统。它基于Google的开源框架gRPC,并为Go语言提供了相应的接口和库。下面我将对grpc-go源码进行简要剖析。 在grpc-go源码中,最重要的是其核心组件:Transport、Balancer、Resolver和Server。Transport负责网络传输,提供基于TCP、HTTP/2和TLS的通信功能。Balancer用于负载均衡,可根据策略将请求分配到不同的服务节点。Resolver负责服务发现,帮助客户端找到可用的服务实例。Server则是用于接收和处理来自客户端的请求。 在Transport层,grpc-go使用了高效的HTTP/2协议作为底层通信协议。通过HTTP2Transport接口,它可以方便地与底层通信协议进行交互。此外,Transport还利用了框架提供的拦截器机制,可以实现一些额外的功能,比如认证、日志记录等。 在Server端,grpc-go提供了一个灵活的框架,可以通过定义服务接口和实现服务方法来处理请求。它还支持多种传输模式,包括独立的HTTP/2、TCP以及TLS加密等。Server还提供了流式调用和双向流式调用的支持,可以处理大量并行请求。 在Client端,grpc-go提供了方便的接口和功能,用于与服务端进行通信。客户端可以根据服务接口定义的方法来发起请求,并且可以设置超时时间、重试机制等。此外,客户端还支持流式和双向流式调用,可以实现高效的数据交互。 总结来说,grpc-go源码剖析主要集中在核心组件的实现,包括Transport、Balancer、Resolver和Server等。通过这些组件的协同工作,grpc-go能够实现高性能、高效的RPC通信。同时,grpc-go还提供了丰富的功能和接口,方便开发人员使用和扩展。通过理解grpc-go源码,开发人员可以更好地利用这个框架构建高效的分布式系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值