From f9bd00a6b7939b941fde3fdd239367f4a7d6b137 Mon Sep 17 00:00:00 2001 From: Name <1911860538@qq.com> Date: Sun, 14 Sep 2025 07:29:11 +0800 Subject: [PATCH] perf(context): optimize getMapFromFormData performance (#4339) Co-authored-by: 1911860538 --- context.go | 19 ++++++++--- context_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 842ad2ff..422c2df7 100644 --- a/context.go +++ b/context.go @@ -654,14 +654,23 @@ func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { func getMapFromFormData(m map[string][]string, key string) (map[string]string, bool) { d := make(map[string]string) found := false + keyLen := len(key) + for k, v := range m { - if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { - if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { - found = true - d[k[i+1:][:j]] = v[0] - } + if len(k) < keyLen+3 { // key + "[" + at least one char + "]" + continue + } + + if k[:keyLen] != key || k[keyLen] != '[' { + continue + } + + if j := strings.IndexByte(k[keyLen+1:], ']'); j > 0 { + found = true + d[k[keyLen+1:keyLen+1+j]] = v[0] } } + return d, found } diff --git a/context_test.go b/context_test.go index f51c147f..9b584890 100644 --- a/context_test.go +++ b/context_test.go @@ -3554,3 +3554,88 @@ func TestGetMapFromFormData(t *testing.T) { }) } } + +func BenchmarkGetMapFromFormData(b *testing.B) { + // Test case 1: Small dataset with bracket notation + smallData := map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + } + + // Test case 2: Medium dataset with mixed data + mediumData := map[string][]string{ + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + "ids[c]": {"test"}, + "ids[d]": {"value"}, + "names[a]": {"mike"}, + "names[b]": {"maria"}, + "names[c]": {"john"}, + "names[d]": {"jane"}, + "other[key1]": {"value1"}, + "other[key2]": {"value2"}, + "simple": {"data"}, + "another": {"info"}, + } + + // Test case 3: Large dataset with many bracket keys + largeData := make(map[string][]string) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("ids[%d]", i) + largeData[key] = []string{fmt.Sprintf("value%d", i)} + } + for i := 0; i < 50; i++ { + key := fmt.Sprintf("names[%d]", i) + largeData[key] = []string{fmt.Sprintf("name%d", i)} + } + for i := 0; i < 25; i++ { + key := fmt.Sprintf("other[key%d]", i) + largeData[key] = []string{fmt.Sprintf("other%d", i)} + } + + // Test case 4: Dataset with many non-matching keys (worst case) + worstCaseData := make(map[string][]string) + for i := 0; i < 100; i++ { + key := fmt.Sprintf("nonmatching%d", i) + worstCaseData[key] = []string{fmt.Sprintf("value%d", i)} + } + worstCaseData["ids[a]"] = []string{"hi"} + worstCaseData["ids[b]"] = []string{"3.14"} + + // Test case 5: Dataset with short keys (best case for early exit) + shortKeysData := map[string][]string{ + "a": {"value1"}, + "b": {"value2"}, + "ids[a]": {"hi"}, + "ids[b]": {"3.14"}, + } + + benchmarks := []struct { + name string + data map[string][]string + key string + }{ + {"Small_Bracket", smallData, "ids"}, + {"Small_Names", smallData, "names"}, + {"Medium_Bracket", mediumData, "ids"}, + {"Medium_Names", mediumData, "names"}, + {"Medium_Other", mediumData, "other"}, + {"Large_Bracket", largeData, "ids"}, + {"Large_Names", largeData, "names"}, + {"Large_Other", largeData, "other"}, + {"WorstCase_Bracket", worstCaseData, "ids"}, + {"ShortKeys_Bracket", shortKeysData, "ids"}, + {"Empty_Key", smallData, "notfound"}, + } + + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = getMapFromFormData(bm.data, bm.key) + } + }) + } +}