Go中過(guò)濾范型集合性能示例詳解
正文
最近,我有機(jī)會(huì)在一個(gè)真實(shí)的 Golang 場(chǎng)景中使用泛型,同時(shí)尋找與 Stream filter(Predicate<? super T> predicate)和 Python list comprehension 等同的函數(shù)。我沒(méi)有依賴(lài)現(xiàn)有的包,而是選擇自己寫(xiě)一個(gè)過(guò)濾函數(shù),以達(dá)到學(xué)習(xí)的目的。
func filterStrings(collection []string, test func(string) bool) (f []string) { for _, s := range collection { if test(s) { f = append(f, s) } } return }
然而,這只適用于字符串。如果我需要過(guò)濾一個(gè)整數(shù)的集合,那么我就需要另一個(gè)極其相似的函數(shù)。
這對(duì)于一個(gè)范型函數(shù)來(lái)說(shuō)似乎是一個(gè)完美的選擇。
func filter[T any](collection []T, test func(T) bool) (f []T) { for _, e := range collection { if test(e) { f = append(f, e) } } return }
分析類(lèi)型化和范型版本之間的(少數(shù))差異
- 函數(shù)名后面是一個(gè)范型T的定義。
- T被定義為任何類(lèi)型。
- 輸入 slice 中元素的類(lèi)型從字符串變成了T
- 輸入、輸出的 clice 類(lèi)型也從字符串變成了T
不多說(shuō)了,讓我們來(lái)寫(xiě)一些單元測(cè)試。首先,我需要一個(gè)隨機(jī)集合(在我的例子中,是字符串)的生成器。
func generateStringCollection(size, strLen int) []string { var collection []string for i := 0; i < size; i++ { collection = append(collection, strings.Repeat(fmt.Sprintf("%c", rune('A'+(i%10))), strLen)) } return collection }
現(xiàn)在我可以寫(xiě)一個(gè)測(cè)試用例,判斷 filterStrings
函數(shù)的輸出與我的過(guò)濾范型器的輸出相同。
func TestFilter(t *testing.T) { c := generateStringCollection(1000, 3) t.Run("the output of the typed and generic functions is the same", func(t *testing.T) { predicate := func(s string) bool { return s == "AAA" } filteredStrings := filterStrings(c, predicate) filteredElements := filter(c, predicate) if !reflect.DeepEqual(filteredStrings, filteredElements) { t.Errorf("the output of the two functions is not the same") } }) }
=== RUN TestFilter === RUN TestFilter/the_output_of_the_typed_and_generic_functions_is_the_same --- PASS: TestFilter (0.00s) --- PASS: TestFilter/the_output_of_the_typed_and_generic_functions_is_the_same (0.00s) PASS
考慮新函數(shù)在處理大的 slice 時(shí)的性能問(wèn)題。我怎樣才能確保它在這種情況下也能表現(xiàn)良好?
答案是:基準(zhǔn)測(cè)試。用Go編寫(xiě)基準(zhǔn)測(cè)試與單元測(cè)試很相似。
const ( CollectionSize = 1000 ElementSize = 3 ) func BenchmarkFilter_Typed_Copying(b *testing.B) { c := generateStringCollection(CollectionSize, ElementSize) b.Run("Equals to AAA", func(b *testing.B) { for i := 0; i < b.N; i++ { filterStrings(c, func(s string) bool { return s == "AAA" }) } }) } func BenchmarkFilter_Generics_Copying(b *testing.B) { c := generateStringCollection(CollectionSize, ElementSize) b.Run("Equals to AAA", func(b *testing.B) { for i := 0; i < b.N; i++ { filter(c, func(s string) bool { return s == "AAA" }) } }) }
go test -bench=. -count=10 -benchmem goos: darwin goarch: arm64 pkg: github.com/timliudream/go-test/generic_test BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 718408 1641 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 718148 1640 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 732939 1655 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 723036 1639 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 699075 1639 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 707232 1643 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 616422 1652 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 730702 1649 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 691488 1700 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 717043 1646 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 428851 2754 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 428437 2762 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 430444 2800 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 429314 2757 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 430938 2754 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 429795 2754 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 426714 2755 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 418152 2755 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 431739 2761 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 412221 2755 ns/op 4080 B/op 8 allocs/op PASS ok github.com/timliudream/go-test/generic_test 25.005s
我對(duì)這個(gè)結(jié)果并不滿意??雌饋?lái)我用可讀性換取了性能。
此外,我對(duì)分配的數(shù)量也有點(diǎn)擔(dān)心。你注意到我的測(cè)試名稱(chēng)中的_Copying后綴了嗎?那是因?yàn)槲业膬蓚€(gè)過(guò)濾函數(shù)都是將過(guò)濾后的項(xiàng)目從輸入集合復(fù)制到輸出集合中。為什么我必須為這樣一個(gè)簡(jiǎn)單的任務(wù)占用內(nèi)存?
到最后,我需要做的是過(guò)濾原始的收集。我決定先解決這個(gè)問(wèn)題。
func filterInPlace[T any](collection []T, test func(T) bool) []T { var position, size = 0, len(collection) for i := 0; i < size; i++ { if test(collection[i]) { collection[position] = collection[i] position++ } } return collection[:position] }
我不是把過(guò)濾后的結(jié)果寫(xiě)到一個(gè)新的集合中,然后再返回,而是把結(jié)果寫(xiě)回原來(lái)的集合中,并保留一個(gè)額外的索引,以便在過(guò)濾后的項(xiàng)目數(shù)上 "切 "出一個(gè)片斷。
我的單元測(cè)試仍然通過(guò),在改變了下面這行之后。
filteredStrings := filterStrings(c, predicate) //filteredElements := filter(c, predicate) filteredElements := filterInPlace(c, predicate) // new memory-savvy function
再添加一個(gè) bench 方法
func BenchmarkFilter_Generics_InPlace(b *testing.B) { c := generateStringCollection(CollectionSize, 3) b.Run("Equals to AAA", func(b *testing.B) { for i := 0; i < b.N; i++ { filterInPlace(c, func(s string) bool { return s == "AAA" }) } }) }
結(jié)果是出色的。
go test -bench=. -benchmem goos: darwin goarch: arm64 pkg: github.com/timliudream/go-test/generic_test BenchmarkFilter_Typed_Copying/Equals_to_AAA-8 713928 1642 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Copying/Equals_to_AAA-8 426055 2787 ns/op 4080 B/op 8 allocs/op BenchmarkFilter_Generics_Inplace/Equals_to_AAA-8 483994 2467 ns/op 0 B/op 0 allocs/op PASS ok github.com/timliudream/go-test/generic_test 4.925s
不僅內(nèi)存分配歸零,而且性能也明顯提高。
以上就是Go中過(guò)濾范型集合性能示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go過(guò)濾范型集合性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
如何使用Golang創(chuàng)建與讀取Excel文件
我最近工作忙于作圖,圖表,需要自己準(zhǔn)備數(shù)據(jù)源,所以經(jīng)常和Excel打交道,下面這篇文章主要給大家介紹了關(guān)于如何使用Golang創(chuàng)建與讀取Excel文件的相關(guān)資料,需要的朋友可以參考下2022-07-07Golang實(shí)現(xiàn)Md5校驗(yàn)的代碼示例
最近項(xiàng)目中有個(gè)需求,就是地圖文件下發(fā)后,接收方需要文件的md5值,和接收到的文件做比對(duì),以免文件不完整,引起bug,于是測(cè)試了下本地文件和遠(yuǎn)程文件的md5計(jì)算,所以本文給大家介紹了Golang實(shí)現(xiàn)Md5校驗(yàn),需要的朋友可以參考下2024-07-07Go語(yǔ)言實(shí)現(xiàn)機(jī)器大小端判斷代碼分享
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)機(jī)器大小端判斷代碼分享,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2014-10-10Golang Cron 定時(shí)任務(wù)的實(shí)現(xiàn)示例
這篇文章主要介紹了Golang Cron 定時(shí)任務(wù)的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05