Skip to content

maps: Performance Regression in maps.Clone after Go 1.24 Refactor #72983

Closed as not planned

Description

@1911860538

Go 1.24 refactored the map runtime using SwissTable.

When I was using Go 1.24, I found that,
compared to using a for loop to copy a map,
the performance of the built-in maps.Clone was actually worse.
At the same time, in versions prior to Go 1.24,
the performance of maps.Clone was the best.

Below is the code I used for performance testing:

package clone

import (
	"maps"
	"testing"
)

func clone1(m map[string]string) map[string]string {
	if m == nil {
		return nil
	}
	m2 := make(map[string]string, len(m))
	for k, v := range m {
		m2[k] = v
	}
	return m2
}

func clone2[M ~map[K]V, K comparable, V any](m M) M {
	if m == nil {
		return nil
	}
	m2 := make(M, len(m))
	for k, v := range m {
		m2[k] = v
	}
	return m2
}

var m = map[string]string{
	"k1": "v1",
	"k2": "v2",
	"k3": "v3",
	"k4": "v4",
}

func BenchmarkClone1(b *testing.B) {
	for i := 0; i < b.N; i++ {
		clone1(m)
	}
}

func BenchmarkClone2(b *testing.B) {
	for i := 0; i < b.N; i++ {
		clone2(m)
	}
}

func BenchmarkStdClone(b *testing.B) {
	for i := 0; i < b.N; i++ {
		maps.Clone(m)
	}
}

Performance test results for different Go versions are as follows:

Go 1.24

goos: darwin
goarch: arm64
pkg: test_code/test_map/clone
cpu: Apple M3 Pro
BenchmarkClone1
BenchmarkClone1-11      	16550962	        72.64 ns/op
BenchmarkClone2
BenchmarkClone2-11      	16820404	        71.59 ns/op
BenchmarkStdClone
BenchmarkStdClone-11    	 9009817	       134.1 ns/op
PASS

Process finished with the exit code 0

Go 1.23

goos: darwin
goarch: arm64
pkg: test_code/test_map/clone
cpu: Apple M3 Pro
BenchmarkClone1
BenchmarkClone1-11      	15726506	        77.62 ns/op
BenchmarkClone2
BenchmarkClone2-11      	14940602	        80.03 ns/op
BenchmarkStdClone
BenchmarkStdClone-11    	20451465	        59.58 ns/op
PASS

Process finished with the exit code 0

The results for Go 1.22 and Go 1.21 are similar to Go 1.23.

Activity

  1. gabyhelp commented on Mar 21, 2025

    @gabyhelp
  2. added
    BugReportIssues describing a possible bug in the Go implementation.
    on Mar 21, 2025
  3. seankhliao commented on Mar 21, 2025

    @seankhliao
    Member

    That benchmark looks invalid, the return value is discarded and can be optimized away.
    Ensuring the value is retained produces more consistent results: https://go.dev/play/p/jBtvROSBTzV

  4. 1911860538 commented on Mar 21, 2025

    @1911860538
    Author

    That benchmark looks invalid, the return value is discarded and can be optimized away. Ensuring the value is retained produces more consistent results: https://go.dev/play/p/jBtvROSBTzV

    I conducted benchmarks for Go 1.23 and Go 1.24 locally based on your code.
    Below is the test code. Using the same test code, I tested it with Go 1.24 and Go 1.23 separately.
    For the same maps.Clone code, the execution speed in Go 1.23 is twice that of Go 1.24.

    clone_test.go

    package clone2
    
    import (
    	"maps"
    	"testing"
    )
    
    var g map[string]string
    
    var m = map[string]string{
    	"k1": "v1",
    	"k2": "v2",
    	"k3": "v3",
    	"k4": "v4",
    }
    
    func BenchmarkStdClone(b *testing.B) {
    	for i := 0; i < b.N; i++ {
    		g = maps.Clone(m)
    	}
    }

    Go1.24 output

    GOROOT=/Users/huangzl/sdk/go1.24.0 #gosetup
    GOPATH=/Users/huangzl/huangzhiwen/about_code/go/test_code/GOPATH #gosetup
    GOPROXY=https://goproxy.cn,direct #gosetup
    /Users/huangzl/sdk/go1.24.0/bin/go test -c -o /Users/huangzl/Library/Caches/JetBrains/GoLand2024.3/tmp/GoLand/___gobench_test_code_test_map_clone2.test test_code/test_map/clone2 #gosetup
    /Users/huangzl/Library/Caches/JetBrains/GoLand2024.3/tmp/GoLand/___gobench_test_code_test_map_clone2.test -test.v -test.paniconexit0 -test.bench . -test.run ^$ #gosetup
    goos: darwin
    goarch: arm64
    pkg: test_code/test_map/clone2
    cpu: Apple M3 Pro
    BenchmarkStdClone
    BenchmarkStdClone-11    	 7470522	       137.7 ns/op
    PASS
    
    Process finished with the exit code 0
    

    Go1.23 output

    GOROOT=/Users/huangzl/sdk/go1.23.1 #gosetup
    GOPATH=/Users/huangzl/huangzhiwen/about_code/go/test_code/GOPATH #gosetup
    GOPROXY=https://goproxy.cn,direct #gosetup
    /Users/huangzl/sdk/go1.23.1/bin/go test -c -o /Users/huangzl/Library/Caches/JetBrains/GoLand2024.3/tmp/GoLand/___gobench_test_code_test_map_clone2.test test_code/test_map/clone2 #gosetup
    /Users/huangzl/Library/Caches/JetBrains/GoLand2024.3/tmp/GoLand/___gobench_test_code_test_map_clone2.test -test.v -test.paniconexit0 -test.bench . -test.run ^$ #gosetup
    goos: darwin
    goarch: arm64
    pkg: test_code/test_map/clone2
    cpu: Apple M3 Pro
    BenchmarkStdClone
    BenchmarkStdClone-11    	20376992	        58.62 ns/op
    PASS
    
    Process finished with the exit code 0
    
  5. mateusz834 commented on Mar 21, 2025

    @mateusz834
    Member

    AFAIAC in go1.24 the maps.Clone is not optimized specially as it was for non-swiss maps in 1.23

    (#70836)

  6. 1911860538 commented on Mar 21, 2025

    @1911860538
    Author

    AFAIAC in go1.24 the maps.Clone is not optimized specially as it was for non-swiss maps in 1.23

    (#70836)

    So the performance regression of maps.Clone is a fact.

  7. locked and limited conversation to collaborators on Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.FrozenDueToAge

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions