mzh/blog

Go Contributor Summit 2019参会记

今年有幸去美国圣地亚哥参加了Go Contributor Summit 2019,主要是Go的代码贡献者和核心开发团队在一起讨论关于Go相关的话题。

紧跟着的GopherCon也是第一次换城市到了圣地亚哥,还在中途岛号航母(USS Midway)上搞了欢迎party,真是毕生难忘了。

破冰环节

刚进门,就发现Russ Cox站在门口,跟他打了打招呼,顺便吐槽了一番他的范型设计在国内引起的巨大讨论,不过大佬很快就被其他人抓去讨论别的话题了。 这时热情的Joe Tsai(Protobuf维护者)跟我打招呼,然后给我介绍他认识的Google同事, 但毕竟是大型网友见面会,除了Github上见过照片的,基本都名字对不上脸,大家很腼腆,当然也有像Möhrmann(Google SRE)热心地跑来跑去介绍自己的人。

这么尴尬怎么玩?没事,Google组织方已经想好了,接下来就是破冰环节。

图1

图1: 会议日程表,左spf13 右rsc

其实很简单,就是一桌人按生日的月排序,小的介绍大的。介绍内容除了名字公司以外还要介绍个自己的fun fact,这下真难倒我了, 幸好我的介绍人Julie Qiu(Go文档维护)给我想到了一个,坐了14个小时的飞机第一次来美国(虽然我没get到fun是啥…… 轮到我介绍了,正好介绍的是Joe,他的fun fact是一家3姐弟都在Google上班,又一次没get到。 最让我震惊的是Cherry Zhang(ARM/MIPS 维护者)是个妹子!主要是看代码和说话风格,感觉都是个男的,轮到她介绍的时候了才发现是个妹子。

因为是贡献者大会,所以主办方希望大家自选话题并分组讨论各自感兴趣的话题。 大家先头脑风暴,讲出了自己关心的话题,然后经过投票之后,上午的话题分成以下4组:

  1. 编译器
  2. 范型和错误处理
  3. 社区相关问题,发展和规划
  4. 工具相关问题,proxy mod这类

讨论环节

每个组讨论都要选一个负责人来组织讨论,防止一言堂,然后有个两个志愿者来记录一下大家说的话题。 我参加了编译器组,见识到了Keith Randell(编译器维护),Austin Clements(编译器组负责人)和其他大牛们讨论的问题:

后来的事实证明,我适合去社区相关组,毕竟我在组里几乎没发言,而我又是唯一一个从中国来的开发者,而Go的核心团队相当关心Go在中国的发展。

图2

图2:社区组讨论如何提Bug和proposal,大佬云集

杂事体会

国外开会真心体验到了什么叫人性化,每40分钟就停下来休息,然后每2次休息就有茶歇。这样开会一点都不累。 由于是Contributor Summit,所以是管午饭的,吃的嘛……真不敢恭维,都是三明治+沙拉。 在午饭时,Joe Tsai跟我聊了聊中国用户时怎么使用文档的,我跟他说大家基本都能看懂英语的或者代码, 所以一般不用操心中国用户看文档,当然也聊了protobuf维护的坑啊,中国美国社会、政治问题之类的。 期间还有些其他开发者看到我衣服上的公司名字,跑过来问某鹅怎么用Go啊,中国开发者怎么盗用weather.com的API的这类的事。

还有就是我真心不建议英语不好的朋友来参加,我雅思听力6.5、平时基本能不看字幕看美剧也扛不住各种口音的攻击,特别是8小时的大型听力考试+技术能力考试+时差, 脑子基本转不动了,而且很多俚语和文化确实需要浸入式地学习才能了解,出现了几次全场大笑,我还很懵逼的状态。

总体还是收获特别大,不仅是跟一堆核心开发面对面,而且还认识了不少好朋友哈~

沈阳美签记

水一篇。

念念不忘,必有回响。

这真是对我一直想去美国看看的真实写照。 本来Gopher Contributor Summit并没有邀请我,但是Ben不去参加,把名额转给我了。 我想着我的贡献度远比他低,只是10个左右的CL而已,没想到询问了主办方后2周后,突然就收到了同意参加的邮件。 6月22号填完查户口般的DS160后发现,我应该去的广州领事馆面签预约时间竟然到了一个月以后,可是这个会就在7月23号开了啊。这样肯定是不行的, 四处找啊找,上海、北京也是1个多月,最后在预约栏里发现了“沈阳”这个选项。点进去一看,只要7天!! 最快就是7月1号办!!赶紧买了机票。

以前都是看文章,说什么东三省衰落,落后啥的,没想到飞到沈阳以后发现,压根不是这么回事! 要啥有啥!地铁还比南宁还多! 真是应验了主席的“没有调查就没有发言权”。 坐了出租车到订的“公寓式酒店”,发现真是公寓改的酒店……因为这酒店就在一栋40多层高的高档居民楼里。 不过到的时候凌晨1点了,所以也没看清城市啥样,虽然确实不能和深圳的灯火通明比,但也不算差了。

因为看网上攻略美签不能带手机啥的,只能拿个透明资料袋,又懒得取现金来打车,所以订的酒店就隔了20分钟走路时间。第二天醒了之后时间还早,所以走着去看了看,9点就已经好多人呆在领事馆外头等叫号了。 不过领事馆安保太严了,不让拍照,我前面有个人拍了一张,就被保安追过去,站在身后要求他删掉。

走回酒店的路上发现有个饺子馆,想着午饭就吃个正宗东北酸菜饺子好了。 早饭就匆匆吃了个面包,然后等到时间去做面签。

到了以后,坐在领事馆对面的椅子上,边上的一个妈妈跟我攀谈了起来,问我为啥去,我说开个会,我礼貌性地反问她,她很自豪地说是送女儿上普渡大学。听着很野鸡,但回头一查……常青藤呵呵 就在尴尬的时候,幸好边上一对夫妇听见她也是送小孩上学,就跟她聊了起来,自曝一年要花60万学杂费,好可怕。

轮到我了,跟着前面的人做,拿资料,录指纹。 真的面签了,一个白人大叔,中文问我为啥去美国,我答开会。 然后跟网上攻略一样,他直接英语问工作在哪里,多久了,bla bla,特意问了我是不是做AI相关的开发,还没问我要全家福和资产证明时,就把我的护照收走了,丢了张通过的红纸出来……果然是大厂牌子响么。

离晚上的飞机时间还早,我就吃了个酸菜饺子做午饭,嗯……跟我们家楼下的东北人夫妇开的店里卖的是一个味道,就是皮得多,吃起来有嚼劲。

东北酸菜饺子

然后在浑河边上转了转,名不副实,这河清着呢,沈阳还有个比深圳好的一个地方就是,特别凉快~ 最后水几张沈阳的照片

沈阳电视塔

沈阳电视塔

好像是图书馆

好像是图书馆

电车

电车

NABHash介绍及原理

项目地址

github.com/mengzhuo/nabhash

什么是NABHash?

NABHash 是一种 超级快的 基于AES的非密码学安全哈希算法(Hash Algorithm)

有以下特点:

性能对比图

NABHash性能对比图

可以看到,在65536字节时,NABHash的速度是xxhash、murmur3这些常见快速哈希算法的10倍左右。

为啥可以这么快?

因为“简约”。

一般哈希算法是使用加法、异或、移位完成的(Shift Add XOR) 而哈希算法为了满足雪崩性和分布性,会多计算几轮,导致就算使用SIMD技术,也无可避免性能下降。

这就是NABHash优化的地方,NABHash对于一个16字节数据块,在Intel平台上仅仅使用了1个指令:

AESENC

即AES加密。而选择AES的主要原因是,现在主流的CPU都支持了AES硬件加速(ARM、PPC),而SHA系列虽然有硬件支持,但指令复杂,往往一个数据块还需要多个指令才能完成计算。

主要流程可见下图: NABHash流程图

相当于用数据不停地加密初始向量值(0x5A827999 0x6ED9EBA1),最终得到一个哈希值。

可能有些小朋友就会问了,为啥初始向量是这个值,是随机生成的么?不,这是sha1的初始key值。

最后,为啥叫NABHash

因为这是Non-Crypto-Safe AES Based Hash,简写NABHash,基于AES的非密码学安全哈希算法。

Go开启UDP SO_REUSEPORT支持

Linux的bind REUSEPORT选项是为了充分利用多核CPU,允许不同进程绑定同一个地址+端口号,避免出现仅仅有一个核在处理数据包,其他核闲着的问题。

启用前后的对比图(借用nginx的 reuseport before after

不过这么好的特性,Go默认是不开启的,需要自己手动调用net.ListenConfig(Go1.11以上)

一看API就懵了,咋是一个interface……

type ListenConfig struct {
        // If Control is not nil, it is called after creating the network
        // connection but before binding it to the operating system.
        //
        // Network and address parameters passed to Control method are not
        // necessarily the ones passed to Listen. For example, passing "tcp" to
        // Listen will cause the Control function to be called with "tcp4" or "tcp6".
        Control func(network, address string, c syscall.RawConn) error
}

这是因为Go有近乎强迫症的向下兼容|backward compatible,不会在已有的接口上添加选项,所以肯定是新开一个API了。但是这个API跟常见POSIX API差太远了,仔细阅读了文档才摸索出下面这个例子:

import (
	"net"
	"syscall"
	"golang.org/x/sys/unix"
)

func newUDPListener(address string) (conn *net.UDPConn, err error) {

	// 构造Control闭包
	cfgFn := func(network, address string, conn syscall.RawConn) (err error) {
		// 构造fd控制函数
		fn := func(fd uintptr) {
			// 设置REUSEPORT
			err = syscall.SetsockoptInt(int(fd),
				syscall.SOL_SOCKET,
				unix.SO_REUSEPORT, 1)
			if err != nil {
				return
			}
		}

		if err = conn.Control(fn); err != nil {
			return
		}
		return
	}

	lc := net.ListenConfig{Control: cfgFn}
	lp, err := lc.ListenPacket(context.Background(), "udp", address)
	if err != nil {
		return
	}
	conn = lp.(*net.UDPConn)
	return
}

效果方面,我的GoNTPD项目一到高峰期就没办法响应太多的请求,导致UDP recv buf 过高,图中的绿线是网卡吞吐量,红线就是UDP recv buf的使用量,越高就说明无法立即处理的请求越多,可以看到更新后,就没有出现UDP recv buf堆积的情况了。

UDP recv buf VS throughput

Go ARM64 Base64编码优化小记

Goost库终于发布了,最开始只有base64和adler32(其实就是捡Go官方废弃的汇编加速代码来) 这次我添加了base64 arm64加速的优化,性能提升不高,也就2x,arm处理器羸弱的流水线这锅是跑不了了。 代码如下:

goost.org/encoding/base64

本文记录一下这两天的思路和总结

base64编码基础

base64很简单,数据按6bit(2^6=64)分隔并转换成可见ASCII编码内的字符就可以了,不是我们常见的8bit分隔。具体可以看Wikipedia,这里就不赘述了。

如何加速?

其实早就人研究好了Base64 encoding with SIMD instructions,只不过没有arm64版本的文章。 这里就大致翻译下上面文章的思路,一些解释和背景讲解:

在内存里,3个字节数据是这样排布的。

[aaaaaabb][bbbbcccc][ccdddddd]

而我们的目标是:

[__aaaaaa][__bbbbbb][__cccccc][__dddddd]

接下来大致分三步:首先借助的是arm64 的ld3指令将上面的3个*连续*字符载入至三个不同的向量寄存器v0, v1, v2

v0: [aaaaaabb]....
v1: [bbbbcccc]....
v2: [ccdddddd]....

通过VSHL,VSHR指令,转到四个对应的向量寄存器,以bbbbbb为例:

aaaaaabb << 4 = aabb____
bbbbcccc >> 4 = ____bbbb

接下来用or,mask将bbbbbb保留

最后这步最好玩。arm64支持VTBL(向量查表操作),恰好最多支持4个128bit的表查询(正好64个字节),所以只需要把base64的码表塞进查询向量寄存器中即可 VTBL指令示意

最后st4保存四个向量寄存器到目的地址即可。

总结一下

写的时候还是碰到了一个坑,因为对VSRI和VSHR区别不熟悉,加上Go编译器只支持VSRI(vector shift right and insert),这个insert会保留原有向量寄存器的数据,导致总是在3/27/53这三个位置的数据不对。浪费了不少时间,下次有不熟悉的指令还是多读读手册。不过误打误撞倒是比原来的作者少了2个指令哈哈。

看原文的时候总是沉不下心,看不懂,下次还是要带着问题读。

解码感觉有些复杂,而且原文的实现不太方便,回头再研究一下。