首頁 後端開發 Golang 驚! Go裡面居然有這麼精妙的小函數!

驚! Go裡面居然有這麼精妙的小函數!

Jul 24, 2023 pm 04:23 PM
go

各位哥麻煩騰個道,前面是大型裝逼現場。
驚! Go裡面居然有這麼精妙的小函數!

首先老許要感謝他人的認同,這是我樂此不彼的動力,同時我也需要反思。這位小姐姐還是比較委婉, 但用我們四川話來說,前一篇文章的標題是真的cuo

老許反覆思考後決定譁眾取寵一波,感嘆號雙連取名曰「驚!Go裡面居然有這樣精妙的小函數!」。下面就讓我們來看看和標題沒那麼符合的一些小函數。

返回a/b向上捨入最接近的整數

func divRoundUp(n, a uintptr) uintptr {
 return (n + a - 1) / a
}
登入後複製

這個方法用過的人應該不少,最典型的就是分頁計算。

判斷x是否為2的n次方

func isPowerOfTwo(x uintptr) bool {
 return x&(x-1) == 0
}
登入後複製

這個也挺容易理解的,唯一需要注意的是x需要大於0,因為該等式0也是成立的。

向上/下將x舍入為a的倍數,且a必須是2的n次方

// 向上将x舍入为a的倍数,例如:x=6,a=4则返回值为8
func alignUp(x, a uintptr) uintptr {
 return (x + a - 1) &^ (a - 1)
}

// 向上将x舍入为a的倍数,例如:x=6,a=4则返回值为4
func alignDown(x, a uintptr) uintptr {
 return x &^ (a - 1)
}
登入後複製

在这里老许再次明确一个概念,2的n次幂即为1左移n位。然后上述代码中^为单目运算法按位取反,则^ (a - 1)的运算结果是除了最低n位为0其余位全为1。剩余的部分则是一个简单的加减运算以及按位与。

上述代码分开来看每一部分都认识,合在一起就一脸懵逼了。幸运的是,经过老许的不懈努力终于找到了一种能够理解的方式。

x=10,a=4為例。 a為2的2次方即1左移2位。 x可看作兩部分之和,第一部分x1為0b1000,第二部分x2為0b0011x的拆分方式是1左移n位元可得到a來決定的,即x的最低n位元為x2,x1則為x-x2。因此x1相當於0b10左移2位得到,即x1已經是a的整數倍,此時x2只要大於0則x2 a-1一定會向前進1,x1 1x1 不就是x向上捨入的a的整數倍數,最後和^ (a - 1)進行與運算將最低2位元清零得到最終的回傳結果。

有說一,我肯定是寫不出這樣的邏輯,這也令我不得不感嘆大佬們對電腦的理解簡直出神入化。這樣的函數牛逼歸牛逼,但是在實際開發上還是盡量少用。一是有使用場景的限制(a必須為2的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))))
}
登入後複製

如果让我来写这个函数,一个稀松平常的switch就完事儿,现在我又多了一种装逼的套路。老许在这里特别友情提示,字节切片和字符串也可使用上述方式进行相互转换。

计算不同类型最低位0的位数

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
}
登入後複製

Ctz8Ctz32Ctz64分别计算无符号8、32、64位数最低位为0的个数,即某个数左移的位数。

函数的作用通过翻译倒是能理解,我也能深刻的明白这是典型的空间换时间,然而要问一句为什么我是万万答不上来的。不过老许已经替你们找好了答案,原因就藏在这篇Using de Bruijn Sequences to Index a 1 in a Computer Word论文中。欢迎巨佬们去挑战一下,而我只想坐享其成,那么在巨佬们分析完这篇论文之前就让这些函数安家在我的收藏栏里方便以后炫技。

这里特别说明,术业有专攻,我们不一定要所有东西都会,但要尽可能知道有这么一个东西存在。这即是老许为自己找的一个不去研究此论文的接口,也是写下此篇文章的意义之一(万一有人提到了Bruijn Sequences关键词,我们也不至于显得过分无知)。

math/bits包中的部分函数

如果有人知道这个包,那请原谅我的无知直接跳过本部分即可。老许发现这个包是源于ntz8tab变量所在文件runtime/internal/sys/intrinsics_common.go中的一句注释。

// Copied from math/bits to avoid dependence.
登入後複製

作为一个资深的CV工程师, 看到这句的第一反应就是我终于可以挺直腰杆了。适当Copy代码不丢人!

math/bits这个包函数较多,老许挑几个介绍即可,其余的还请各位读者自行挖掘。

LeadingZeros(x uint) int: 返回x所有高位为0的个数。

TrailingZeros(x uint) int: 返回x最低位为0的个数。

OnesCount(x uint) int:返回x中bit位为1的个数。

Reverse(x uint) uint: 将x按bit位倒序后再返回。

Len(x uint) int: 返回表示x的有效bit位个数(高位中的0不计数)。

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函数中发现此函数的调用,当时就觉着挺有意思。如今再次回顾也依旧佩服不已。读书是和作者的对话,阅读源码是和开发者的对话,看到此函数就仿佛看到Go语言开发者们和编译器斗智斗勇的场景。

让出当前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)
}
登入後複製

让出当前的Processor,允许其他goroutine执行。在实际的开发当中老许还未遇到需要使用此函数的场景,但多了解总是有备无患。

以上是驚! Go裡面居然有這麼精妙的小函數!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1246
24
Go WebSocket 訊息如何發送? Go WebSocket 訊息如何發送? Jun 03, 2024 pm 04:53 PM

在Go中,可以使用gorilla/websocket包發送WebSocket訊息。具體步驟:建立WebSocket連線。傳送文字訊息:呼叫WriteMessage(websocket.TextMessage,[]byte("訊息"))。發送二進位訊息:呼叫WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})。

如何在 Go 中使用正規表示式匹配時間戳記? 如何在 Go 中使用正規表示式匹配時間戳記? Jun 02, 2024 am 09:00 AM

在Go中,可以使用正規表示式比對時間戳記:編譯正規表示式字串,例如用於匹配ISO8601時間戳記的表達式:^\d{4}-\d{2}-\d{2}T \d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ 。使用regexp.MatchString函數檢查字串是否與正規表示式相符。

Golang 與 Go 語言的區別 Golang 與 Go 語言的區別 May 31, 2024 pm 08:10 PM

Go和Go語言是不同的實體,具有不同的特性。 Go(又稱Golang)以其並發性、編譯速度快、記憶體管理和跨平台優點而聞名。 Go語言的缺點包括生態系統不如其他語言豐富、文法更嚴格、缺乏動態類型。

Golang 技術效能優化中如何避免記憶體洩漏? Golang 技術效能優化中如何避免記憶體洩漏? Jun 04, 2024 pm 12:27 PM

記憶體洩漏會導致Go程式記憶體不斷增加,可通過:關閉不再使用的資源,如檔案、網路連線和資料庫連線。使用弱引用防止記憶體洩漏,當物件不再被強引用時將其作為垃圾回收目標。利用go協程,協程棧記憶體會在退出時自動釋放,避免記憶體洩漏。

Golang 函數接收 map 參數時的注意事項 Golang 函數接收 map 參數時的注意事項 Jun 04, 2024 am 10:31 AM

在Go中傳遞map給函數時,預設會建立副本,對副本的修改不影響原map。如果需要修改原始map,可透過指標傳遞。空map需小心處理,因為技術上是nil指針,傳遞空map給期望非空map的函數會發生錯誤。

如何使用 Golang 的錯誤包裝器? 如何使用 Golang 的錯誤包裝器? Jun 03, 2024 pm 04:08 PM

在Golang中,錯誤包裝器允許你在原始錯誤上追加上下文訊息,從而創建新錯誤。這可用於統一不同程式庫或元件拋出的錯誤類型,簡化偵錯和錯誤處理。步驟如下:使用errors.Wrap函數將原有錯誤包裝成新錯誤。新錯誤包含原始錯誤的上下文資訊。使用fmt.Printf輸出包裝後的錯誤,提供更多上下文和可操作性。在處理不同類型的錯誤時,使用errors.Wrap函數統一錯誤類型。

如何在 Go 中創建優先級 Goroutine? 如何在 Go 中創建優先級 Goroutine? Jun 04, 2024 pm 12:41 PM

在Go語言中建立優先權Goroutine有兩步驟:註冊自訂Goroutine建立函數(步驟1)並指定優先權值(步驟2)。這樣,您可以建立不同優先順序的Goroutine,優化資源分配並提高執行效率。

如何在 Golang 單元測試中使用 gomega 進行斷言? 如何在 Golang 單元測試中使用 gomega 進行斷言? Jun 05, 2024 pm 10:48 PM

如何在Golang單元測試中使用Gomega進行斷言在Golang單元測試中,Gomega是一個流行且功能強大的斷言庫,它提供了豐富的斷言方法,使開發人員可以輕鬆驗證測試結果。安裝Gomegagoget-ugithub.com/onsi/gomega使用Gomega進行斷言以下是使用Gomega進行斷言的一些常用範例:1.相等斷言import"github.com/onsi/gomega"funcTest_MyFunction(t*testing.T){

See all articles