From 7a4c566061ee7b9cc44dc1fbe4e502007061698c Mon Sep 17 00:00:00 2001 From: Yuanjia Zhang Date: Mon, 4 Sep 2023 14:56:13 +0800 Subject: [PATCH] planner: optimize the performance of `PlanCacheParamList.String()` (#46592) close pingcap/tidb#46579 --- executor/show_stats_test.go | 2 +- types/datum.go | 49 +++++++++++++++++------ types/datum_test.go | 77 +++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 13 deletions(-) diff --git a/executor/show_stats_test.go b/executor/show_stats_test.go index 3528bca4fef33..7a24d4a693df3 100644 --- a/executor/show_stats_test.go +++ b/executor/show_stats_test.go @@ -53,7 +53,7 @@ func TestShowStatsLocked(t *testing.T) { tk.MustExec("create table t (a int, b int)") tk.MustExec("create table t1 (a int, b int)") tk.MustExec("lock stats t, t1") - result := tk.MustQuery("show stats_locked") + result := tk.MustQuery("show stats_locked").Sort() require.Len(t, result.Rows(), 2) require.Equal(t, "t", result.Rows()[0][1]) require.Equal(t, "t1", result.Rows()[1][1]) diff --git a/types/datum.go b/types/datum.go index 8d08f73d9798b..3c33f7aa24fdb 100644 --- a/types/datum.go +++ b/types/datum.go @@ -21,6 +21,7 @@ import ( "sort" "strconv" "strings" + "sync" "time" "unicode/utf8" "unsafe" @@ -2283,20 +2284,33 @@ func (ds *datumsSorter) Swap(i, j int) { ds.datums[i], ds.datums[j] = ds.datums[j], ds.datums[i] } +var strBuilderPool = sync.Pool{New: func() interface{} { return &strings.Builder{} }} + // DatumsToString converts several datums to formatted string. func DatumsToString(datums []Datum, handleSpecialValue bool) (string, error) { - strs := make([]string, 0, len(datums)) - for _, datum := range datums { + n := len(datums) + builder := strBuilderPool.Get().(*strings.Builder) + defer func() { + builder.Reset() + strBuilderPool.Put(builder) + }() + if n > 1 { + builder.WriteString("(") + } + for i, datum := range datums { + if i > 0 { + builder.WriteString(", ") + } if handleSpecialValue { switch datum.Kind() { case KindNull: - strs = append(strs, "NULL") + builder.WriteString("NULL") continue case KindMinNotNull: - strs = append(strs, "-inf") + builder.WriteString("-inf") continue case KindMaxValue: - strs = append(strs, "+inf") + builder.WriteString("+inf") continue } } @@ -2304,18 +2318,29 @@ func DatumsToString(datums []Datum, handleSpecialValue bool) (string, error) { if err != nil { return "", errors.Trace(err) } + const logDatumLen = 2048 + originalLen := -1 + if len(str) > logDatumLen { + originalLen = len(str) + str = str[:logDatumLen] + } if datum.Kind() == KindString { - strs = append(strs, fmt.Sprintf("%q", str)) + builder.WriteString(`"`) + builder.WriteString(str) + builder.WriteString(`"`) } else { - strs = append(strs, str) + builder.WriteString(str) + } + if originalLen != -1 { + builder.WriteString(" len(") + builder.WriteString(strconv.Itoa(originalLen)) + builder.WriteString(")") } } - size := len(datums) - if size > 1 { - strs[0] = "(" + strs[0] - strs[size-1] = strs[size-1] + ")" + if n > 1 { + builder.WriteString(")") } - return strings.Join(strs, ", "), nil + return builder.String(), nil } // DatumsToStrNoErr converts some datums to a formatted string. diff --git a/types/datum_test.go b/types/datum_test.go index 86e91dcf48245..09107b15c9328 100644 --- a/types/datum_test.go +++ b/types/datum_test.go @@ -20,6 +20,7 @@ import ( "math" "reflect" "strconv" + "strings" "testing" "time" @@ -704,3 +705,79 @@ func TestNULLNotEqualWithOthers(t *testing.T) { require.NotEqual(t, 0, result) } } + +func TestDatumsToString(t *testing.T) { + datums := []Datum{ + NewIntDatum(1), + NewUintDatum(2), + NewFloat32Datum(-3.1111111), + NewFloat64Datum(4.123), + NewDatum(math.Inf(5)), + NewDecimalDatum(NewDecFromStringForTest("6.6")), + NewStringDatum("abc"), + NewCollationStringDatum("", charset.CollationBin), + NewDurationDatum(Duration{Duration: time.Duration(11111)}), + NewTimeDatum(ZeroTime), + NewBytesDatum([]byte("xxx")), + NewBinaryLiteralDatum([]byte{}), + NewJSONDatum(CreateBinaryJSON(nil)), + MinNotNullDatum(), + MaxValueDatum(), + } + str, err := DatumsToString(datums, true) + require.NoError(t, err) + require.Equal(t, str, `(1, 2, -3.1111112, 4.123, +Inf, 6.6, "abc", "", 00:00:00, 0000-00-00 00:00:00, xxx, , null, -inf, +inf)`) +} + +func BenchmarkDatumsToString(b *testing.B) { + datums := []Datum{ + NewIntDatum(1), + NewUintDatum(2), + NewFloat32Datum(-3.1111111), + NewFloat64Datum(4.123), + NewDatum(math.Inf(5)), + NewDecimalDatum(NewDecFromStringForTest("6.66666")), + NewStringDatum("dklsfjkaslnfwoiewlkfjaslkfjljs"), + NewCollationStringDatum("1234567890-=12345656789", charset.CollationBin), + NewDurationDatum(Duration{Duration: time.Duration(11111)}), + NewTimeDatum(ZeroTime), + NewBytesDatum([]byte("xxxxxxxxxxxxxxxxxxxxxxx")), + NewBinaryLiteralDatum([]byte{}), + NewJSONDatum(CreateBinaryJSON(nil)), + MinNotNullDatum(), + MaxValueDatum(), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := DatumsToString(datums, true) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDatumsToStringStr(b *testing.B) { + datums := []Datum{ + NewStringDatum(strings.Repeat("1", 512)), + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := DatumsToString(datums, true) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDatumsToStringLongStr(b *testing.B) { + datums := []Datum{ + NewStringDatum(strings.Repeat("1", 1024*10)), // 10KB + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := DatumsToString(datums, true) + if err != nil { + b.Fatal(err) + } + } +}