From 84858640dc9c3032219380885283b995d4f2b0d1 Mon Sep 17 00:00:00 2001 From: Dmitriy Matrenichev Date: Sun, 19 May 2024 23:57:53 +0300 Subject: [PATCH] chore: optimize maps.Values and maps.Keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR optimizes the performance of maps.Values and maps.Keys (around 125 usages in sidero code) by using the internal runtime functions runtime.keys and runtime.values. There is strong desire in Go Team to disable `go:linkname` usage in user code [^1], but this is a special case where we use `Push` `linkname` pattern which is said to be supported in the future. It is tested with both Go 1.22 and latest Go with CL 585556 [^2] applied. To further future-proof this code, we have added a build tag `go1.22 && !go1.24` to ensure this code is only compiled with Go 1.22 and 1.23 and falls back to the old implementation for Go 1.24 and above. This is because we don't know yet if `runtime.keys` and `runtime.values` are going to be present in Go 1.24. We will update this code when Go 1.24 freeze happens. Benchstat results below (overall 26% CPU usage reduction): ```bash ~ benchstat old.txt new.txt goos: darwin goarch: arm64 pkg: github.com/siderolabs/gen/maps │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ Keys/small-10 111.05n ± 0% 80.74n ± 3% -27.29% (p=0.000 n=10) Keys/mid-10 837.8n ± 1% 607.8n ± 2% -27.46% (p=0.000 n=10) Keys/large-10 7.717µ ± 4% 5.711µ ± 0% -26.00% (p=0.000 n=10) Values/small-10 110.40n ± 0% 83.69n ± 0% -24.19% (p=0.000 n=10) Values/mid-10 835.9n ± 1% 583.5n ± 1% -30.19% (p=0.000 n=10) Values/large-10 7.720µ ± 2% 5.696µ ± 1% -26.22% (p=0.000 n=10) geomean 894.3n 653.6n -26.92% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ Keys/small-10 80.00 ± 0% 80.00 ± 0% ~ (p=1.000 n=10) ¹ Keys/mid-10 896.0 ± 0% 896.0 ± 0% ~ (p=1.000 n=10) ¹ Keys/large-10 8.000Ki ± 0% 8.000Ki ± 0% ~ (p=1.000 n=10) ¹ Values/small-10 80.00 ± 0% 80.00 ± 0% ~ (p=1.000 n=10) ¹ Values/mid-10 896.0 ± 0% 896.0 ± 0% ~ (p=1.000 n=10) ¹ Values/large-10 8.000Ki ± 0% 8.000Ki ± 0% ~ (p=1.000 n=10) ¹ geomean 837.4 837.4 +0.00% ¹ all samples are equal │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ Keys/small-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Keys/mid-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Keys/large-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Values/small-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Values/mid-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ Values/large-10 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ geomean 1.000 1.000 +0.00% ¹ all samples are equal ``` [^1]: https://github.com/golang/go/issues/67401 [^2]: https://go-review.googlesource.com/c/go/+/585556 Signed-off-by: Dmitriy Matrenichev --- .dockerignore | 2 +- .github/workflows/ci.yaml | 14 ++++-- .golangci.yml | 83 ++++++++++++------------------- Dockerfile | 14 ++---- Makefile | 30 ++++++------ containers/map_test.go | 10 ++-- go.mod | 3 +- maps/maps.go | 28 ----------- maps/maps_linkname.go | 46 ++++++++++++++++++ maps/maps_nolinkname.go | 39 +++++++++++++++ maps/maps_test.go | 94 ++++++++++++++++++++++++++++++------ pair/ordered/ordered_test.go | 11 +++-- xslices/xslices_test.go | 4 -- 13 files changed, 238 insertions(+), 140 deletions(-) create mode 100644 maps/maps_linkname.go create mode 100644 maps/maps_nolinkname.go diff --git a/.dockerignore b/.dockerignore index b2db58a..c5fa12a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-11T19:57:58Z by kres latest. +# Generated on 2024-05-19T20:59:37Z by kres dccd292. * !channel diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2dc54b6..4a89d69 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-11T19:57:58Z by kres latest. +# Generated on 2024-05-19T18:27:53Z by kres dccd292. name: default concurrency: @@ -31,7 +31,7 @@ jobs: if: (!startsWith(github.head_ref, 'renovate/') && !startsWith(github.head_ref, 'dependabot/')) services: buildkitd: - image: moby/buildkit:v0.12.5 + image: moby/buildkit:v0.13.2 options: --privileged ports: - 1234:1234 @@ -45,11 +45,12 @@ jobs: run: | git fetch --prune --unshallow - name: Set up Docker Buildx + id: setup-buildx uses: docker/setup-buildx-action@v3 with: driver: remote endpoint: tcp://127.0.0.1:1234 - timeout-minutes: 1 + timeout-minutes: 10 - name: base run: | make base @@ -60,8 +61,11 @@ jobs: run: | make unit-tests-race - name: coverage - run: | - make coverage + uses: codecov/codecov-action@v4 + with: + files: _out/coverage-unit-tests.txt + token: ${{ secrets.CODECOV_TOKEN }} + timeout-minutes: 3 - name: lint run: | make lint diff --git a/.golangci.yml b/.golangci.yml index 41af67e..f0035e5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,21 +1,20 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-11T19:57:58Z by kres latest. +# Generated on 2024-05-19T20:58:19Z by kres dccd292. # options for analysis running run: timeout: 10m issues-exit-code: 1 tests: true - build-tags: [] - skip-dirs: [] - skip-dirs-use-default: true - skip-files: [] + build-tags: [ ] modules-download-mode: readonly # output configuration options output: - format: colored-line-number + formats: + - format: colored-line-number + path: stdout print-issued-lines: true print-linter-name: true uniq-by-line: true @@ -32,54 +31,35 @@ linters-settings: check-blank: true exhaustive: default-signifies-exhaustive: false - funlen: - lines: 60 - statements: 40 gci: - local-prefixes: github.com/siderolabs/gen/ + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - localmodule # Imports from the same module. gocognit: min-complexity: 30 - ireturn: - allow: - - anon - - error - - empty - - stdlib - - github.com\/talos-systems\/kres\/internal\/dag.Node nestif: min-complexity: 5 goconst: min-len: 3 min-occurrences: 3 gocritic: - disabled-checks: [] + disabled-checks: [ ] gocyclo: min-complexity: 20 godot: - check-all: false - godox: - keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting - - NOTE - - OPTIMIZE # marks code that should be optimized before merging - - HACK # marks hack-arounds that should be removed before merging + scope: declarations gofmt: simplify: true - goimports: - local-prefixes: github.com/siderolabs/gen/ - golint: - min-confidence: 0.8 - gomnd: - settings: {} - gomodguard: {} + gomodguard: { } govet: - check-shadowing: true enable-all: true lll: line-length: 200 tab-width: 4 misspell: locale: US - ignore-words: [] + ignore-words: [ ] nakedret: max-func-lines: 30 prealloc: @@ -88,16 +68,15 @@ linters-settings: for-loops: false # Report preallocation suggestions on for loops, false by default nolintlint: allow-unused: false - allow-leading-space: false - allow-no-explanation: [] + allow-no-explanation: [ ] require-explanation: false require-specific: true - rowserrcheck: {} - testpackage: {} + rowserrcheck: { } + testpackage: { } unparam: check-exported: false unused: - check-exported: false + local-variables-are-used: false whitespace: multi-if: false # Enforces newlines (or comments) after every multi-line if statement multi-func: false # Enforces newlines (or comments) after every multi-line function signature @@ -113,8 +92,8 @@ linters-settings: gofumpt: extra-rules: false cyclop: - # the maximal code complexity to report - max-complexity: 20 + # the maximal code complexity to report + max-complexity: 20 # depguard: # Main: # deny: @@ -125,48 +104,50 @@ linters: disable-all: false fast: false disable: - - exhaustruct - exhaustivestruct + - exhaustruct + - err113 - forbidigo - funlen - - gas - gochecknoglobals - gochecknoinits - godox - - goerr113 - gomnd - gomoddirectives + - gosec + - inamedparam - ireturn + - mnd - nestif - nonamedreturns - nosnakecase - paralleltest + - tagalign - tagliatelle - thelper - typecheck - varnamelen - wrapcheck - depguard # Disabled because starting with golangci-lint 1.53.0 it doesn't allow denylist alone anymore - - tagalign - - inamedparam - testifylint # complains about our assert recorder and has a number of false positives for assert.Greater(t, thing, 1) - protogetter # complains about us using Value field on typed spec, instead of GetValue which has a different signature - perfsprint # complains about us using fmt.Sprintf in non-performance critical code, updating just kres took too long # abandoned linters for which golangci shows the warning that the repo is archived by the owner + - deadcode + - golint + - ifshort - interfacer - maligned - - golint - scopelint - - varcheck - - deadcode - structcheck - - ifshort + - varcheck # disabled as it seems to be broken - goes into imported libraries and reports issues there - musttag + - goimports # same as gci issues: - exclude: [] - exclude-rules: [] + exclude: [ ] + exclude-rules: [ ] exclude-use-default: false exclude-case-sensitive: false max-issues-per-linter: 10 diff --git a/Dockerfile b/Dockerfile index 1474d75..8ab988e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -# syntax = docker/dockerfile-upstream:1.7.0-labs +# syntax = docker/dockerfile-upstream:1.7.1-labs # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-11T19:57:58Z by kres latest. +# Generated on 2024-05-19T20:59:37Z by kres dccd292. ARG TOOLCHAIN @@ -10,7 +10,7 @@ ARG TOOLCHAIN FROM scratch AS generate # runs markdownlint -FROM docker.io/node:21.6.2-alpine3.19 AS lint-markdown +FROM docker.io/node:21.7.3-alpine3.19 AS lint-markdown WORKDIR /src RUN npm i -g markdownlint-cli@0.39.0 RUN npm i sentences-per-line@0.2.1 @@ -40,9 +40,6 @@ RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/g && mv /go/bin/golangci-lint /bin/golangci-lint RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/vuln/cmd/govulncheck@latest \ && mv /go/bin/govulncheck /bin/govulncheck -ARG GOIMPORTS_VERSION -RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/tools/cmd/goimports@${GOIMPORTS_VERSION} \ - && mv /go/bin/goimports /bin/goimports ARG GOFUMPT_VERSION RUN go install mvdan.cc/gofumpt@${GOFUMPT_VERSION} \ && mv /go/bin/gofumpt /bin/gofumpt @@ -71,15 +68,12 @@ RUN --mount=type=cache,target=/go/pkg go list -mod=readonly all >/dev/null FROM base AS lint-gofumpt RUN FILES="$(gofumpt -l .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'gofumpt -w .':\n${FILES}"; exit 1) -# runs goimports -FROM base AS lint-goimports -RUN FILES="$(goimports -l -local github.com/siderolabs/gen/ .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'goimports -w -local github.com/siderolabs/gen/ .':\n${FILES}"; exit 1) - # runs golangci-lint FROM base AS lint-golangci-lint WORKDIR /src COPY .golangci.yml . ENV GOGC 50 +RUN golangci-lint config verify --config .golangci.yml RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/root/.cache/golangci-lint --mount=type=cache,target=/go/pkg golangci-lint run --config .golangci.yml # runs govulncheck diff --git a/Makefile b/Makefile index a60d7d8..b4979d2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT. # -# Generated on 2024-03-11T19:51:28Z by kres latest. +# Generated on 2024-05-19T18:27:53Z by kres dccd292. # common variables @@ -9,6 +9,9 @@ TAG := $(shell git describe --tag --always --dirty --match v[0-9]\*) ABBREV_TAG := $(shell git describe --tags >/dev/null 2>/dev/null && git describe --tag --always --match v[0-9]\* --abbrev=0 || echo 'undefined') BRANCH := $(shell git rev-parse --abbrev-ref HEAD) ARTIFACTS := _out +IMAGE_TAG ?= $(TAG) +OPERATING_SYSTEM := $(shell uname -s | tr '[:upper:]' '[:lower:]') +GOARCH := $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/') WITH_DEBUG ?= false WITH_RACE ?= false REGISTRY ?= ghcr.io @@ -18,11 +21,11 @@ PROTOBUF_GO_VERSION ?= 1.33.0 GRPC_GO_VERSION ?= 1.3.0 GRPC_GATEWAY_VERSION ?= 2.19.1 VTPROTOBUF_VERSION ?= 0.6.0 +GOIMPORTS_VERSION ?= 0.21.0 DEEPCOPY_VERSION ?= v0.5.6 -GOLANGCILINT_VERSION ?= v1.56.2 +GOLANGCILINT_VERSION ?= v1.58.0 GOFUMPT_VERSION ?= v0.6.0 -GO_VERSION ?= 1.22.1 -GOIMPORTS_VERSION ?= v0.19.0 +GO_VERSION ?= 1.22.3 GO_BUILDFLAGS ?= GO_LDFLAGS ?= CGO_ENABLED ?= 0 @@ -59,9 +62,9 @@ COMMON_ARGS += --build-arg=PROTOBUF_GO_VERSION="$(PROTOBUF_GO_VERSION)" COMMON_ARGS += --build-arg=GRPC_GO_VERSION="$(GRPC_GO_VERSION)" COMMON_ARGS += --build-arg=GRPC_GATEWAY_VERSION="$(GRPC_GATEWAY_VERSION)" COMMON_ARGS += --build-arg=VTPROTOBUF_VERSION="$(VTPROTOBUF_VERSION)" +COMMON_ARGS += --build-arg=GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)" COMMON_ARGS += --build-arg=DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)" COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)" -COMMON_ARGS += --build-arg=GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)" COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)" COMMON_ARGS += --build-arg=TESTPKGS="$(TESTPKGS)" TOOLCHAIN ?= docker.io/golang:1.22-alpine @@ -110,7 +113,7 @@ If you already have a compatible builder instance, you may use that instead. ## Artifacts All artifacts will be output to ./$(ARTIFACTS). Images will be tagged with the -registry "$(REGISTRY)", username "$(USERNAME)", and a dynamic tag (e.g. $(IMAGE):$(TAG)). +registry "$(REGISTRY)", username "$(USERNAME)", and a dynamic tag (e.g. $(IMAGE):$(IMAGE_TAG)). The registry and username can be overridden by exporting REGISTRY, and USERNAME respectively. @@ -130,6 +133,9 @@ endif all: unit-tests lint +$(ARTIFACTS): ## Creates artifacts directory. + @mkdir -p $(ARTIFACTS) + .PHONY: clean clean: ## Cleans up all artifacts. @rm -rf $(ARTIFACTS) @@ -157,9 +163,6 @@ fmt: ## Formats the source code lint-govulncheck: ## Runs govulncheck linter. @$(MAKE) target-$@ -lint-goimports: ## Runs goimports linter. - @$(MAKE) target-$@ - .PHONY: base base: ## Prepare base toolchain @$(MAKE) target-$@ @@ -172,16 +175,12 @@ unit-tests: ## Performs unit tests unit-tests-race: ## Performs unit tests with race detection enabled. @$(MAKE) target-$@ -.PHONY: coverage -coverage: ## Upload coverage data to codecov.io. - bash -c "bash <(curl -s https://codecov.io/bash) -f $(ARTIFACTS)/coverage-unit-tests.txt -X fix" - .PHONY: lint-markdown lint-markdown: ## Runs markdownlint. @$(MAKE) target-$@ .PHONY: lint -lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-goimports lint-markdown ## Run all linters for the project. +lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-markdown ## Run all linters for the project. .PHONY: rekres rekres: @@ -194,8 +193,7 @@ help: ## This help menu. @grep -E '^[a-zA-Z%_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: release-notes -release-notes: - mkdir -p $(ARTIFACTS) +release-notes: $(ARTIFACTS) @ARTIFACTS=$(ARTIFACTS) ./hack/release.sh $@ $(ARTIFACTS)/RELEASE_NOTES.md $(TAG) .PHONY: conformance diff --git a/containers/map_test.go b/containers/map_test.go index 3e67885..57b8cd4 100644 --- a/containers/map_test.go +++ b/containers/map_test.go @@ -6,7 +6,7 @@ package containers_test import ( "fmt" - "math/rand" + "math/rand/v2" "sync" "testing" @@ -145,8 +145,8 @@ func parallelGetOrCall(t *testing.T, m *containers.ConcurrentMap[int, int], our, oneAnotherGet := false - for i := 0; i < 10000; i++ { - key := int(rand.Int63n(10000)) + for range 10000 { + key := int(rand.Int64N(10000)) res, ok := m.GetOrCall(key, func() int { return key * our }) if ok { @@ -182,8 +182,8 @@ func parallelGetOrCreate(t *testing.T, m *containers.ConcurrentMap[int, int], ou oneAnotherGet := false - for i := 0; i < 10000; i++ { - key := int(rand.Int63n(10000)) + for range 10000 { + key := int(rand.Int64N(10000)) res, ok := m.GetOrCreate(key, key*our) if ok { diff --git a/go.mod b/go.mod index 11d642c..c9b7941 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,7 @@ module github.com/siderolabs/gen -go 1.22.0 // Starting with Go 1.21 you have to provide the third digit too. +// Starting with Go 1.21 you have to provide the third digit too. +go 1.22.0 require github.com/stretchr/testify v1.9.0 diff --git a/maps/maps.go b/maps/maps.go index 6804718..1b36d55 100644 --- a/maps/maps.go +++ b/maps/maps.go @@ -42,22 +42,6 @@ func Map[K comparable, V any, K1 comparable, V1 any](m map[K]V, fn func(K, V) (K return r } -// Keys returns the keys of the map m. -// The keys will be in an indeterminate order. -func Keys[K comparable, V any](m map[K]V) []K { - if len(m) == 0 { - return nil - } - - r := make([]K, 0, len(m)) - - for k := range m { - r = append(r, k) - } - - return r -} - // KeysFunc applies the function fn to each key of the map m and returns a new slice with the results. // The keys will be in an indeterminate order. func KeysFunc[K comparable, V, R any](m map[K]V, fn func(K) R) []R { @@ -74,18 +58,6 @@ func KeysFunc[K comparable, V, R any](m map[K]V, fn func(K) R) []R { return r } -// Values returns the values of the map m. -// The values will be in an indeterminate order. -func Values[K comparable, V any](m map[K]V) []V { - r := make([]V, 0, len(m)) - - for _, v := range m { - r = append(r, v) - } - - return r -} - // ValuesFunc applies the function fn to each value of the map m and returns a new slice with the results. // The values will be in an indeterminate order. func ValuesFunc[K comparable, V, R any](m map[K]V, fn func(V) R) []R { diff --git a/maps/maps_linkname.go b/maps/maps_linkname.go new file mode 100644 index 0000000..7847c19 --- /dev/null +++ b/maps/maps_linkname.go @@ -0,0 +1,46 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//go:build go1.22 && !go1.24 && !nolinkname + +//nolint:revive +package maps + +import "unsafe" + +//go:linkname runtime_keys maps.keys +//go:noescape +func runtime_keys(m any, p unsafe.Pointer) + +// Keys returns the keys of the map m. +// The keys will be in an indeterminate order. +func Keys[K comparable, V any](m map[K]V) []K { + if len(m) == 0 { + return nil + } + + result := make([]K, 0, len(m)) + + runtime_keys(m, unsafe.Pointer(&result)) + + return result +} + +//go:linkname runtime_values maps.values +//go:noescape +func runtime_values(m any, p unsafe.Pointer) + +// Values returns the values of the map m. +// The values will be in an indeterminate order. +func Values[K comparable, V any](m map[K]V) []V { + if len(m) == 0 { + return nil + } + + result := make([]V, 0, len(m)) + + runtime_values(m, unsafe.Pointer(&result)) + + return result +} diff --git a/maps/maps_nolinkname.go b/maps/maps_nolinkname.go new file mode 100644 index 0000000..e2230b0 --- /dev/null +++ b/maps/maps_nolinkname.go @@ -0,0 +1,39 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +//go:build go1.24 || nolinkname + +package maps + +// Keys returns the keys of the map m. +// The keys will be in an indeterminate order. +func Keys[K comparable, V any](m map[K]V) []K { + if len(m) == 0 { + return nil + } + + r := make([]K, 0, len(m)) + + for k := range m { + r = append(r, k) + } + + return r +} + +// Values returns the values of the map m. +// The values will be in an indeterminate order. +func Values[K comparable, V any](m map[K]V) []V { + if len(m) == 0 { + return nil + } + + r := make([]V, 0, len(m)) + + for _, v := range m { + r = append(r, v) + } + + return r +} diff --git a/maps/maps_test.go b/maps/maps_test.go index 03ad901..dca2c59 100644 --- a/maps/maps_test.go +++ b/maps/maps_test.go @@ -57,8 +57,6 @@ func TestFilterInPlace(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -113,8 +111,6 @@ func TestFilter(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -163,8 +159,6 @@ func TestKeys(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -214,8 +208,6 @@ func TestKeysFunc(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -265,8 +257,6 @@ func TestToSlice(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -316,8 +306,6 @@ func TestValuesFunc(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -422,8 +410,6 @@ func TestIntersection(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -434,3 +420,83 @@ func TestIntersection(t *testing.T) { }) } } + +func TestKeysAddtional(t *testing.T) { + m := generateMap(6) + + keys := maps.Keys(m) + + assert.Equal(t, 6, len(keys)) + slices.Sort(keys) + assert.EqualValues(t, []int{0, 1, 2, 3, 4, 5}, keys) +} + +func TestValuesAddtional(t *testing.T) { + m := generateMap(6) + + values := maps.Values(m) + + assert.Equal(t, 6, len(values)) + slices.Sort(values) + assert.EqualValues(t, []int{-5, -4, -3, -2, -1, 0}, values) +} + +var Sink []int + +func BenchmarkKeys(b *testing.B) { + smallMap := generateMap(10) + midMap := generateMap(100) + largeMap := generateMap(1000) + + b.Run("small", func(b *testing.B) { + for range b.N { + Sink = maps.Keys(smallMap) + } + }) + + b.Run("mid", func(b *testing.B) { + for range b.N { + Sink = maps.Keys(midMap) + } + }) + + b.Run("large", func(b *testing.B) { + for range b.N { + Sink = maps.Keys(largeMap) + } + }) +} + +func BenchmarkValues(b *testing.B) { + smallMap := generateMap(10) + midMap := generateMap(100) + largeMap := generateMap(1000) + + b.Run("small", func(b *testing.B) { + for range b.N { + Sink = maps.Values(smallMap) + } + }) + + b.Run("mid", func(b *testing.B) { + for range b.N { + Sink = maps.Values(midMap) + } + }) + + b.Run("large", func(b *testing.B) { + for range b.N { + Sink = maps.Values(largeMap) + } + }) +} + +func generateMap(num int) map[int]int { + result := make(map[int]int, num) + + for i := range num { + result[i] = -i + } + + return result +} diff --git a/pair/ordered/ordered_test.go b/pair/ordered/ordered_test.go index 34fd00d..c46601e 100644 --- a/pair/ordered/ordered_test.go +++ b/pair/ordered/ordered_test.go @@ -6,7 +6,7 @@ package ordered_test import ( "math" - "math/rand" + "math/rand/v2" "slices" "testing" "time" @@ -32,14 +32,15 @@ func TestTriple(t *testing.T) { ordered.MakeTriple(math.MaxInt64, "", 69.0), } - seed := time.Now().Unix() - rnd := rand.New(rand.NewSource(seed)) + seed1 := time.Now().UnixNano() + seed2 := time.Now().UnixNano() + rnd := rand.New(rand.NewPCG(uint64(seed1), uint64(seed2))) - for i := 0; i < 1000; i++ { + for i := range 1000 { a := append([]ordered.Triple[int, string, float64](nil), expectedSlice...) rnd.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] }) slices.SortFunc(a, func(i, j ordered.Triple[int, string, float64]) int { return i.Compare(j) }) - require.Equal(t, expectedSlice, a, "failed with seed %d iteration %d", seed, i) + require.Equal(t, expectedSlice, a, "failed with seed1 %d seed2 %d iteration %d", seed1, seed2, i) } } diff --git a/xslices/xslices_test.go b/xslices/xslices_test.go index 38a1f0b..fbd685c 100644 --- a/xslices/xslices_test.go +++ b/xslices/xslices_test.go @@ -68,8 +68,6 @@ func TestFilterInPlace(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel() @@ -138,8 +136,6 @@ func TestFilter(t *testing.T) { } for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { t.Parallel()