驚!Go里面居然有這樣精妙的小函數(shù)!
本文轉(zhuǎn)載自微信公眾號「Gopher指北」,作者新世界雜貨鋪。轉(zhuǎn)載本文請聯(lián)系Gopher指北公眾號。
各位哥麻煩騰個道,前面是大型裝逼現(xiàn)場。
首先老許要感謝他人的認(rèn)同,這是我樂此不彼的動力,同時我也需要反思。這位小姐姐還是比較委婉, 但用我們四川話來說,前一篇文章的標(biāo)題是真的cuo。
老許反復(fù)思考后決定嘩眾取寵一波,感嘆號雙連取名曰“驚!Go里面居然有這樣精妙的小函數(shù)!”。下面就讓我們來看看和標(biāo)題沒那么符合的一些小函數(shù)。
返回a/b向上舍入最接近的整數(shù)
- func divRoundUp(n, a uintptr) uintptr {
- return (n + a - 1) / a
- }
這個方法用過的人應(yīng)該不少,最典型的就是分頁計算。
判斷x是否為2的n次冪
- func isPowerOfTwo(x uintptr) bool {
- return x&(x-1) == 0
- }
這個也挺容易理解的,唯一需要注意的是x需要大于0,因為該等式0也是成立的。
向上/下將x舍入為a的倍數(shù),且a必須是2的n次冪
- // 向上將x舍入為a的倍數(shù),例如:x=6,a=4則返回值為8
- func alignUp(x, a uintptr) uintptr {
- return (x + a - 1) &^ (a - 1)
- }
- // 向上將x舍入為a的倍數(shù),例如:x=6,a=4則返回值為4
- func alignDown(x, a uintptr) uintptr {
- return x &^ (a - 1)
- }
在這里老許再次明確一個概念,2的n次冪即為1左移n位。然后上述代碼中^為單目運算法按位取反,則^ (a - 1)的運算結(jié)果是除了最低n位為0其余位全為1。剩余的部分則是一個簡單的加減運算以及按位與。
上述代碼分開來看每一部分都認(rèn)識,合在一起就一臉懵逼了。幸運的是,經(jīng)過老許的不懈努力終于找到了一種能夠理解的方式。
以x=10,a=4為例。a為2的2次冪即1左移2位。x可看作兩部分之和,第一部分x1為0b1000,第二部分x2為0b0011。x的拆分方式是1左移n位可得到a來決定的,即x的最低n位為x2,x1則為x-x2。因此x1相當(dāng)于0b10左移2位得到,即x1已經(jīng)是a的整數(shù)倍,此時x2只要大于0則x2+a-1一定會向前進(jìn)1,x1+1或x1不就是x向上舍入的a的整數(shù)倍嘛,最后和^ (a - 1)進(jìn)行與運算將最低2位清零得到最終的返回結(jié)果。
有一說一,我肯定是寫不出這樣的邏輯,這也令我不得不感嘆大佬們對計算機(jī)的理解簡直出神入化。這樣的函數(shù)牛逼歸牛逼,但是在實際開發(fā)中還是盡量少用。一是有使用場景的限制(a必須為2的n次冪),二是不易理解,當(dāng)然炫技和裝逼除外(性能要求極高也除外)。
布爾轉(zhuǎn)整形
- // bool2int returns 0 if x is false or 1 if x is true.
- func bool2int(x bool) int {
- return int(uint8(*(*uint8)(unsafe.Pointer(&x))))
- }
如果讓我來寫這個函數(shù),一個稀松平常的switch就完事兒,現(xiàn)在我又多了一種裝逼的套路。老許在這里特別友情提示,字節(jié)切片和字符串也可使用上述方式進(jìn)行相互轉(zhuǎn)換。
計算不同類型最低位0的位數(shù)
- var ntz8tab = [256]uint8{
- 0x08, ..., 0x00,
- }
- // Ctz8 returns the number of trailing zero bits in x; the result is 8 for x == 0.
- func Ctz8(x uint8) int {
- return int(ntz8tab[x])
- }
- const deBruijn32ctz = 0x04653adf
- var deBruijnIdx32ctz = [32]byte{
- 0, 1, 2, 6, 3, 11, 7, 16,
- 4, 14, 12, 21, 8, 23, 17, 26,
- 31, 5, 10, 15, 13, 20, 22, 25,
- 30, 9, 19, 24, 29, 18, 28, 27,
- }
- // Ctz32 counts trailing (low-order) zeroes,
- // and if all are zero, then 32.
- func Ctz32(x uint32) int {
- x &= -x // isolate low-order bit
- y := x * deBruijn32ctz >> 27 // extract part of deBruijn sequence
- i := int(deBruijnIdx32ctz[y]) // convert to bit index
- z := int((x - 1) >> 26 & 32) // adjustment if zero
- return i + z
- }
- const deBruijn64ctz = 0x0218a392cd3d5dbf
- var deBruijnIdx64ctz = [64]byte{
- 0, 1, 2, 7, 3, 13, 8, 19,
- 4, 25, 14, 28, 9, 34, 20, 40,
- 5, 17, 26, 38, 15, 46, 29, 48,
- 10, 31, 35, 54, 21, 50, 41, 57,
- 63, 6, 12, 18, 24, 27, 33, 39,
- 16, 37, 45, 47, 30, 53, 49, 56,
- 62, 11, 23, 32, 36, 44, 52, 55,
- 61, 22, 43, 51, 60, 42, 59, 58,
- }
- // Ctz64 counts trailing (low-order) zeroes,
- // and if all are zero, then 64.
- func Ctz64(x uint64) int {
- x &= -x // isolate low-order bit
- y := x * deBruijn64ctz >> 58 // extract part of deBruijn sequence
- i := int(deBruijnIdx64ctz[y]) // convert to bit index
- z := int((x - 1) >> 57 & 64) // adjustment if zero
- return i + z
- }
Ctz8、Ctz32和Ctz64分別計算無符號8、32、64位數(shù)最低位為0的個數(shù),即某個數(shù)左移的位數(shù)。
函數(shù)的作用通過翻譯倒是能理解,我也能深刻的明白這是典型的空間換時間,然而要問一句為什么我是萬萬答不上來的。不過老許已經(jīng)替你們找好了答案,原因就藏在這篇Using de Bruijn Sequences to Index a 1 in a Computer Word論文中。歡迎巨佬們?nèi)ヌ魬?zhàn)一下,而我只想坐享其成,那么在巨佬們分析完這篇論文之前就讓這些函數(shù)安家在我的收藏欄里方便以后炫技。
這里特別說明,術(shù)業(yè)有專攻,我們不一定要所有東西都會,但要盡可能知道有這么一個東西存在。這即是老許為自己找的一個不去研究此論文的接口,也是寫下此篇文章的意義之一(萬一有人提到了Bruijn Sequences關(guān)鍵詞,我們也不至于顯得過分無知)。
math/bits包中的部分函數(shù)
如果有人知道這個包,那請原諒我的無知直接跳過本部分即可。老許發(fā)現(xiàn)這個包是源于ntz8tab變量所在文件runtime/internal/sys/intrinsics_common.go中的一句注釋。
- // Copied from math/bits to avoid dependence.
作為一個資深的CV工程師, 看到這句的第一反應(yīng)就是我終于可以挺直腰桿了。適當(dāng)Copy代碼不丟人!
math/bits這個包函數(shù)較多,老許挑幾個介紹即可,其余的還請各位讀者自行挖掘。
LeadingZeros(x uint) int: 返回x所有高位為0的個數(shù)。
TrailingZeros(x uint) int: 返回x最低位為0的個數(shù)。
OnesCount(x uint) int:返回x中bit位為1的個數(shù)。
Reverse(x uint) uint: 將x按bit位倒序后再返回。
Len(x uint) int: 返回表示x的有效bit位個數(shù)(高位中的0不計數(shù))。
ReverseBytes(x uint) uint: 將x按照每8位一組倒序后返回。
將x逃逸至堆
- // Dummy annotation marking that the value x escapes,
- // for use in cases where the reflect code is so clever that
- // the compiler cannot follow.
- func escapes(x interface{}) {
- if dummy.b {
- dummy.x = x
- }
- }
- var dummy struct {
- b bool
- x interface{}
- }
老許是在reflect.ValueOf函數(shù)中發(fā)現(xiàn)此函數(shù)的調(diào)用,當(dāng)時就覺著挺有意思。如今再次回顧也依舊佩服不已。讀書是和作者的對話,閱讀源碼是和開發(fā)者的對話,看到此函數(shù)就仿佛看到Go語言開發(fā)者們和編譯器斗智斗勇的場景。
讓出當(dāng)前Processor
- // Gosched yields the processor, allowing other goroutines to run. It does not
- // suspend the current goroutine, so execution resumes automatically.
- func Gosched() {
- checkTimeouts()
- mcall(gosched_m)
- }
讓出當(dāng)前的Processor,允許其他goroutine執(zhí)行。在實際的開發(fā)當(dāng)中老許還未遇到需要使用此函數(shù)的場景,但多了解總是有備無患。
最后,衷心希望本文能夠?qū)Ω魑蛔x者有一定的幫助。