diff --git a/benchmark_test.go b/benchmark_test.go deleted file mode 100644 index 97b7389e..00000000 --- a/benchmark_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package lo - -import ( - "math/rand" - "strconv" - "testing" - "time" - - lop "github.com/samber/lo/parallel" - "github.com/thoas/go-funk" -) - -func sliceGenerator(size uint) []int64 { - r := rand.New(rand.NewSource(time.Now().Unix())) - - result := make([]int64, size) - - for i := uint(0); i < size; i++ { - result[i] = r.Int63() - } - - return result -} - -func BenchmarkMap(b *testing.B) { - arr := sliceGenerator(1000000) - - b.Run("lo.Map", func(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = Map(arr, func(x int64, i int) string { - return strconv.FormatInt(x, 10) - }) - } - }) - - b.Run("lop.Map", func(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = lop.Map(arr, func(x int64, i int) string { - return strconv.FormatInt(x, 10) - }) - } - }) - - b.Run("reflect", func(b *testing.B) { - for n := 0; n < b.N; n++ { - _ = funk.Map(arr, func(x int64) string { - return strconv.FormatInt(x, 10) - }) - } - }) - - b.Run("for", func(b *testing.B) { - for n := 0; n < b.N; n++ { - results := make([]string, len(arr)) - - for i, item := range arr { - result := strconv.FormatInt(item, 10) - results[i] = result - } - } - }) -} diff --git a/map.go b/map.go index 27b00752..d8feb434 100644 --- a/map.go +++ b/map.go @@ -21,7 +21,12 @@ func Keys[K comparable, V any](in ...map[K]V) []K { // UniqKeys creates an array of unique keys in the map. // Play: https://go.dev/play/p/TPKAb6ILdHk func UniqKeys[K comparable, V any](in ...map[K]V) []K { - seen := make(map[K]struct{}) + size := 0 + for i := range in { + size += len(in[i]) + } + + seen := make(map[K]struct{}, size) result := make([]K, 0) for i := range in { @@ -65,7 +70,12 @@ func Values[K comparable, V any](in ...map[K]V) []V { // UniqValues creates an array of unique values in the map. // Play: https://go.dev/play/p/nf6bXMh7rM3 func UniqValues[K comparable, V comparable](in ...map[K]V) []V { - seen := make(map[V]struct{}) + size := 0 + for i := range in { + size += len(in[i]) + } + + seen := make(map[V]struct{}, size) result := make([]V, 0) for i := range in { diff --git a/map_benchmark_test.go b/map_benchmark_test.go new file mode 100644 index 00000000..81f47df7 --- /dev/null +++ b/map_benchmark_test.go @@ -0,0 +1,124 @@ +package lo + +import ( + "math/rand" + "strconv" + "testing" + "time" + + lop "github.com/samber/lo/parallel" + "github.com/thoas/go-funk" +) + +func sliceGenerator(size uint) []int64 { + r := rand.New(rand.NewSource(time.Now().Unix())) + + result := make([]int64, size) + + for i := uint(0); i < size; i++ { + result[i] = r.Int63() + } + + return result +} + +func mapGenerator(size uint) map[int64]int64 { + r := rand.New(rand.NewSource(time.Now().Unix())) + + result := make(map[int64]int64, size) + + for i := uint(0); i < size; i++ { + result[int64(i)] = r.Int63() + } + + return result +} + +func BenchmarkMap(b *testing.B) { + arr := sliceGenerator(1000000) + + b.Run("lo.Map", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = Map(arr, func(x int64, i int) string { + return strconv.FormatInt(x, 10) + }) + } + }) + + b.Run("lop.Map", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = lop.Map(arr, func(x int64, i int) string { + return strconv.FormatInt(x, 10) + }) + } + }) + + b.Run("reflect", func(b *testing.B) { + for n := 0; n < b.N; n++ { + _ = funk.Map(arr, func(x int64) string { + return strconv.FormatInt(x, 10) + }) + } + }) + + b.Run("for", func(b *testing.B) { + for n := 0; n < b.N; n++ { + results := make([]string, len(arr)) + + for i, item := range arr { + result := strconv.FormatInt(item, 10) + results[i] = result + } + } + }) +} + +// also apply to UniqValues +func BenchmarkUniqKeys(b *testing.B) { + m := []map[int64]int64{ + mapGenerator(100000), + mapGenerator(100000), + mapGenerator(100000), + } + + // allocate just in time + ordered + b.Run("lo.UniqKeys.jit-alloc", func(b *testing.B) { + for n := 0; n < b.N; n++ { + seen := make(map[int64]struct{}) + result := make([]int64, 0) + + for i := range m { + for k := range m[i] { + if _, exists := seen[k]; exists { + continue + } + seen[k] = struct{}{} + result = append(result, k) + } + } + } + }) + + // preallocate + unordered + b.Run("lo.UniqKeys.preallocate", func(b *testing.B) { + for n := 0; n < b.N; n++ { + size := 0 + for i := range m { + size += len(m[i]) + } + seen := make(map[int64]struct{}, size) + + for i := range m { + for k := range m[i] { + seen[k] = struct{}{} + } + } + + result := make([]int64, 0, len(seen)) + + for k := range seen { + result = append(result, k) + } + } + }) +} diff --git a/map_test.go b/map_test.go index 25319811..fb02bf9a 100644 --- a/map_test.go +++ b/map_test.go @@ -53,6 +53,10 @@ func TestUniqKeys(t *testing.T) { r5 := UniqKeys(map[string]int{"foo": 1, "bar": 2}, map[string]int{"foo": 1, "bar": 3}) sort.Strings(r5) is.Equal(r5, []string{"bar", "foo"}) + + // check order + r6 := UniqKeys(map[string]int{"foo": 1}, map[string]int{"bar": 3}) + is.Equal(r6, []string{"foo", "bar"}) } func TestHasKey(t *testing.T) { @@ -114,6 +118,10 @@ func TestUniqValues(t *testing.T) { r6 := UniqValues(map[string]int{"foo": 1, "bar": 1}, map[string]int{"foo": 1, "bar": 3}) sort.Ints(r6) is.Equal(r6, []int{1, 3}) + + // check order + r7 := UniqValues(map[string]int{"foo": 1}, map[string]int{"bar": 3}) + is.Equal(r7, []int{1, 3}) } func TestValueOr(t *testing.T) {