Go中過濾范型集合性能示例詳解
正文
最近,我有機會在一個真實的 Golang 場景中使用泛型,同時尋找與 Stream filter(Predicate<? super T> predicate)和 Python list comprehension 等同的函數(shù)。我沒有依賴現(xiàn)有的包,而是選擇自己寫一個過濾函數(shù),以達到學習的目的。
func filterStrings(collection []string, test func(string) bool) (f []string) {
for _, s := range collection {
if test(s) {
f = append(f, s)
}
}
return
}
然而,這只適用于字符串。如果我需要過濾一個整數(shù)的集合,那么我就需要另一個極其相似的函數(shù)。
這對于一個范型函數(shù)來說似乎是一個完美的選擇。
func filter[T any](collection []T, test func(T) bool) (f []T) {
for _, e := range collection {
if test(e) {
f = append(f, e)
}
}
return
}
分析類型化和范型版本之間的(少數(shù))差異
- 函數(shù)名后面是一個范型T的定義。
- T被定義為任何類型。
- 輸入 slice 中元素的類型從字符串變成了T
- 輸入、輸出的 clice 類型也從字符串變成了T
不多說了,讓我們來寫一些單元測試。首先,我需要一個隨機集合(在我的例子中,是字符串)的生成器。
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)在我可以寫一個測試用例,判斷 filterStrings 函數(shù)的輸出與我的過濾范型器的輸出相同。
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 時的性能問題。我怎樣才能確保它在這種情況下也能表現(xiàn)良好?
答案是:基準測試。用Go編寫基準測試與單元測試很相似。
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
我對這個結果并不滿意??雌饋砦矣每勺x性換取了性能。
此外,我對分配的數(shù)量也有點擔心。你注意到我的測試名稱中的_Copying后綴了嗎?那是因為我的兩個過濾函數(shù)都是將過濾后的項目從輸入集合復制到輸出集合中。為什么我必須為這樣一個簡單的任務占用內存?
到最后,我需要做的是過濾原始的收集。我決定先解決這個問題。
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]
}
我不是把過濾后的結果寫到一個新的集合中,然后再返回,而是把結果寫回原來的集合中,并保留一個額外的索引,以便在過濾后的項目數(shù)上 "切 "出一個片斷。
我的單元測試仍然通過,在改變了下面這行之后。
filteredStrings := filterStrings(c, predicate) //filteredElements := filter(c, predicate) filteredElements := filterInPlace(c, predicate) // new memory-savvy function
再添加一個 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" })
}
})
}
結果是出色的。
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
不僅內存分配歸零,而且性能也明顯提高。
以上就是Go中過濾范型集合性能示例詳解的詳細內容,更多關于Go過濾范型集合性的資料請關注腳本之家其它相關文章!
相關文章
Go通過goroutine實現(xiàn)多協(xié)程文件上傳的基本流程
多協(xié)程文件上傳是指利用多線程或多協(xié)程技術,同時上傳一個或多個文件,以提高上傳效率和速度,本文給大家介紹了Go通過goroutine實現(xiàn)多協(xié)程文件上傳的基本流程,需要的朋友可以參考下2024-05-05
Golang實現(xiàn)smtp郵件發(fā)送的示例代碼
這篇文章主要為大家詳細介紹了Golang實現(xiàn)smtp郵件發(fā)送的相關知識,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-03-03
Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法
這篇文章主要介紹了Go語言編程中判斷文件是否存在是創(chuàng)建目錄的方法,示例都是使用os包下的函數(shù),需要的朋友可以參考下2015-10-10

