You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by jo...@apache.org on 2023/09/19 15:03:00 UTC

[impala] branch master updated: IMPALA-12390 (part 2): Enable some clang-tidy performance related checks

This is an automated email from the ASF dual-hosted git repository.

joemcdonnell pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git


The following commit(s) were added to refs/heads/master by this push:
     new 3614a6a77 IMPALA-12390 (part 2): Enable some clang-tidy performance related checks
3614a6a77 is described below

commit 3614a6a776819a1e918ce7fe833cd9e916d6002a
Author: gaurav1086 <ga...@gmail.com>
AuthorDate: Thu Aug 31 10:51:18 2023 -0700

    IMPALA-12390 (part 2): Enable some clang-tidy performance related checks
    
    This enables the clang tidy performance check:
    performance-inefficient-string-concatenation
    "warning: string concatenation results in allocation of unnecessary
    temporary strings"
    Fix: Use StrCat() to concatenate multiple strings
    
    Testing:
     - Ran bin/run_clang_tidy.sh with the new checks
     - Ran GVO
    
    Change-Id: Ibad8bd0f12aab92ad874f5a6b9ec922dce7f3190
    Reviewed-on: http://gerrit.cloudera.org:8080/20445
    Reviewed-by: Impala Public Jenkins <im...@cloudera.com>
    Tested-by: Impala Public Jenkins <im...@cloudera.com>
---
 .clang-tidy                                       |   1 -
 be/src/exprs/expr-test.cc                         | 150 +++++++++++-----------
 be/src/runtime/io/disk-io-mgr-test.cc             |   5 +-
 be/src/runtime/timestamp-test.cc                  |   6 +-
 be/src/scheduling/cluster-membership-test-util.cc |   3 +-
 be/src/util/runtime-profile.cc                    |   3 +-
 bin/run_clang_tidy.sh                             |   6 +-
 7 files changed, 93 insertions(+), 81 deletions(-)

diff --git a/.clang-tidy b/.clang-tidy
index fa075dec2..df86e3bec 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -75,7 +75,6 @@ Checks: "-*,clang*,\
 -clang-diagnostic-weak-template-vtables,\
 -clang-diagnostic-weak-vtables,\
 performance-*,\
--performance-inefficient-string-concatenation,\
 -performance-unnecessary-copy-initialization,\
 -performance-unnecessary-value-param"
 
diff --git a/be/src/exprs/expr-test.cc b/be/src/exprs/expr-test.cc
index e017b1134..a26a5ecf4 100644
--- a/be/src/exprs/expr-test.cc
+++ b/be/src/exprs/expr-test.cc
@@ -75,6 +75,7 @@
 #include "util/string-util.h"
 #include "util/test-info.h"
 #include "utility-functions.h"
+#include "gutil/strings/strcat.h"
 
 #include "common/names.h"
 
@@ -909,7 +910,8 @@ class ExprTest : public testing::TestWithParam<std::tuple<bool, bool>> {
       // Everything IS NOT DISTINCT FROM itself.
       for (int j = 0; j < sizeof(types) / sizeof(string); ++j) {
         const string operand = "cast(NULL as " + types[j] + ")";
-        TestValue(operand + ' ' + operators[i] + ' ' + operand, TYPE_BOOLEAN, is_equal);
+        TestValue(StrCat(operand, " ", operators[i], " ", operand), TYPE_BOOLEAN,
+                  is_equal);
       }
       for (int j = 0; j < sizeof(operands1) / sizeof(string); ++j) {
         TestValue(operands1[j] + ' ' + operators[i] + ' ' + operands1[j], TYPE_BOOLEAN,
@@ -4084,10 +4086,10 @@ TEST_P(ExprTest, InPredicate) {
   for(int_iter = min_int_values_.begin(); int_iter != min_int_values_.end();
       ++int_iter) {
     string& val = default_type_strs_[int_iter->first];
-    TestValue(val + " in (2, 3, " + val + ")", TYPE_BOOLEAN, true);
-    TestValue(val + " in (2, 3, 4)", TYPE_BOOLEAN, false);
-    TestValue(val + " not in (2, 3, " + val + ")", TYPE_BOOLEAN, false);
-    TestValue(val + " not in (2, 3, 4)", TYPE_BOOLEAN, true);
+    TestValue(StrCat(val, " in (2, 3, ", val, ")"), TYPE_BOOLEAN, true);
+    TestValue(StrCat(val, " in (2, 3, 4)"), TYPE_BOOLEAN, false);
+    TestValue(StrCat(val, " not in (2, 3, ", val, ")"), TYPE_BOOLEAN, false);
+    TestValue(StrCat(val, " not in (2, 3, 4)"), TYPE_BOOLEAN, true);
   }
 
   // Test floats.
@@ -4095,10 +4097,10 @@ TEST_P(ExprTest, InPredicate) {
   for(float_iter = min_float_values_.begin(); float_iter != min_float_values_.end();
       ++float_iter) {
     string& val = default_type_strs_[float_iter->first];
-    TestValue(val + " in (2, 3, " + val + ")", TYPE_BOOLEAN, true);
-    TestValue(val + " in (2, 3, 4)", TYPE_BOOLEAN, false);
-    TestValue(val + " not in (2, 3, " + val + ")", TYPE_BOOLEAN, false);
-    TestValue(val + " not in (2, 3, 4)", TYPE_BOOLEAN, true);
+    TestValue(StrCat(val, " in (2, 3, ", val, ")"), TYPE_BOOLEAN, true);
+    TestValue(StrCat(val, " in (2, 3, 4)"), TYPE_BOOLEAN, false);
+    TestValue(StrCat(val, " not in (2, 3, ", val, ")"), TYPE_BOOLEAN, false);
+    TestValue(StrCat(val, " not in (2, 3, 4)"), TYPE_BOOLEAN, true);
   }
 
   // Test bools.
@@ -5000,10 +5002,10 @@ TEST_P(ExprTest, StringBase64Coding) {
       string raw(length, ' ');
       for (int j = 0; j < length; ++j) raw[j] = rand() % 128;
       const string as_octal = StringToOctalLiteral(raw);
-      TestValue("length(base64encode('" + as_octal + "')) > length('" + as_octal + "')",
-          TYPE_BOOLEAN, true);
-      TestValue("base64decode(base64encode('" + as_octal + "')) = '" + as_octal + "'",
-          TYPE_BOOLEAN, true);
+      TestValue(StrCat("length(base64encode('", as_octal, "')) > length('", as_octal,
+           "')"), TYPE_BOOLEAN, true);
+      TestValue(StrCat("base64decode(base64encode('", as_octal + "')) = '", as_octal,
+           "'"), TYPE_BOOLEAN, true);
     }
   }
 }
@@ -6017,50 +6019,52 @@ TEST_P(ExprTest, MathConversionFunctions) {
     // First iteration is with bigint, second with string parameter.
     string q = (i == 0) ? "" : "'";
     // Invalid input: Base below -36 or above 36.
-    TestIsNull("conv(" + q + "10" + q + ", 10, 37)", TYPE_STRING);
-    TestIsNull("conv(" + q + "10" + q + ", 37, 10)", TYPE_STRING);
-    TestIsNull("conv(" + q + "10" + q + ", 10, -37)", TYPE_STRING);
-    TestIsNull("conv(" + q + "10" + q + ", -37, 10)", TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", 10, 37)"), TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", 37, 10)"), TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", 10, -37)"), TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", -37, 10)"), TYPE_STRING);
     // Invalid input: Base between -2 and 2.
-    TestIsNull("conv(" + q + "10" + q + ", 10, 1)", TYPE_STRING);
-    TestIsNull("conv(" + q + "10" + q + ", 1, 10)", TYPE_STRING);
-    TestIsNull("conv(" + q + "10" + q + ", 10, -1)", TYPE_STRING);
-    TestIsNull("conv(" + q + "10" + q + ", -1, 10)", TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", 10, 1)"), TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", 1, 10)"), TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", 10, -1)"), TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", -1, 10)"), TYPE_STRING);
     // Invalid input: Positive number but negative src base.
-    TestIsNull("conv(" + q + "10" + q + ", -10, 10)", TYPE_STRING);
+    TestIsNull(StrCat("conv(", q, "10", q, ", -10, 10)"), TYPE_STRING);
     // Test positive numbers.
-    TestStringValue("conv(" + q + "10" + q + ", 10, 10)", "10");
-    TestStringValue("conv(" + q + "10" + q + ", 2, 10)", "2");
-    TestStringValue("conv(" + q + "11" + q + ", 36, 10)", "37");
-    TestStringValue("conv(" + q + "11" + q + ", 36, 2)", "100101");
-    TestStringValue("conv(" + q + "100101" + q + ", 2, 36)", "11");
-    TestStringValue("conv(" + q + "0" + q + ", 10, 2)", "0");
+    TestStringValue(StrCat("conv(", q, "10", q, ", 10, 10)"), "10");
+    TestStringValue(StrCat("conv(", q, "10", q, ", 2, 10)"), "2");
+    TestStringValue(StrCat("conv(", q, "11", q, ", 36, 10)"), "37");
+    TestStringValue(StrCat("conv(", q, "11", q, ", 36, 2)"), "100101");
+    TestStringValue(StrCat("conv(", q, "100101", q, ", 2, 36)"), "11");
+    TestStringValue(StrCat("conv(", q, "0", q, ", 10, 2)"), "0");
     // Test for very large big int
-    TestStringValue("conv(" + q + "2061013007" + q + ", 16, 10)", "139066421255");
+    TestStringValue(StrCat("conv(", q, "2061013007", q, ", 16, 10)"), "139066421255");
     // Test negative numbers (tests from Hive).
     // If to_base is positive, the number should be handled as a 2's complement (64-bit).
-    TestStringValue("conv(" + q + "-641" + q + ", 10, -10)", "-641");
-    TestStringValue("conv(" + q + "1011" + q + ", 2, -16)", "B");
-    TestStringValue("conv(" + q + "-1" + q + ", 10, 16)", "FFFFFFFFFFFFFFFF");
-    TestStringValue("conv(" + q + "-15" + q + ", 10, 16)", "FFFFFFFFFFFFFFF1");
+    TestStringValue(StrCat("conv(", q, "-641", q, ", 10, -10)"), "-641");
+    TestStringValue(StrCat("conv(", q, "1011", q, ", 2, -16)"), "B");
+    TestStringValue(StrCat("conv(", q, "-1", q, ", 10, 16)"), "FFFFFFFFFFFFFFFF");
+    TestStringValue(StrCat("conv(", q, "-15", q, ", 10, 16)"), "FFFFFFFFFFFFFFF1");
     // Test digits that are not available in srcbase. We expect those digits
     // from left-to-right that can be interpreted in srcbase to form the result
     // (i.e., the paring bails only when it encounters a digit not in srcbase).
-    TestStringValue("conv(" + q + "17" + q + ", 7, 10)", "1");
-    TestStringValue("conv(" + q + "371" + q + ", 7, 10)", "3");
-    TestStringValue("conv(" + q + "371" + q + ", 7, 10)", "3");
-    TestStringValue("conv(" + q + "445" + q + ", 5, 10)", "24");
+    TestStringValue(StrCat("conv(", q, "17", q, ", 7, 10)"), "1");
+    TestStringValue(StrCat("conv(", q, "371", q, ", 7, 10)"), "3");
+    TestStringValue(StrCat("conv(", q, "371", q, ", 7, 10)"), "3");
+    TestStringValue(StrCat("conv(", q, "445", q, ", 5, 10)"), "24");
     // Test overflow (tests from Hive).
     // If a number is two large, the result should be -1 (if signed),
     // or MAX_LONG (if unsigned).
-    TestStringValue("conv(" + q + lexical_cast<string>(numeric_limits<int64_t>::max())
-        + q + ", 36, 16)", "FFFFFFFFFFFFFFFF");
-    TestStringValue("conv(" + q + lexical_cast<string>(numeric_limits<int64_t>::max())
-        + q + ", 36, -16)", "-1");
-    TestStringValue("conv(" + q + lexical_cast<string>(numeric_limits<int64_t>::min()+1)
-        + q + ", 36, 16)", "FFFFFFFFFFFFFFFF");
-    TestStringValue("conv(" + q + lexical_cast<string>(numeric_limits<int64_t>::min()+1)
-        + q + ", 36, -16)", "-1");
+    TestStringValue(StrCat("conv(", q,
+       lexical_cast<string>(numeric_limits<int64_t>::max()), q, ", 36, 16)"),
+       "FFFFFFFFFFFFFFFF");
+    TestStringValue(StrCat("conv(", q,
+       lexical_cast<string>(numeric_limits<int64_t>::max()), q, ", 36, -16)"), "-1");
+    TestStringValue(StrCat("conv(", q,
+       lexical_cast<string>(numeric_limits<int64_t>::min()+1), q, ", 36, 16)"),
+       "FFFFFFFFFFFFFFFF");
+    TestStringValue(StrCat("conv(", q,
+       lexical_cast<string>(numeric_limits<int64_t>::min()+1), q, ", 36, -16)"), "-1");
   }
   // Test invalid input strings that start with an invalid digit.
   // Hive returns "0" in such cases.
@@ -6943,38 +6947,38 @@ TEST_P(ExprTest, TimestampFunctions) {
     const string& lt_max_interval =
         lexical_cast<string>(static_cast<int64_t>(0.9 * it->second));
     // Test that pushing a value beyond the max/min values results in a NULL.
-    TestIsNull(unit + "_add(cast('9999-12-31 23:59:59' as timestamp), "
-        + lt_max_interval + ")", TYPE_TIMESTAMP);
-    TestIsNull(unit + "_sub(cast('1400-01-01 00:00:00' as timestamp), "
-        + lt_max_interval + ")", TYPE_TIMESTAMP);
+    TestIsNull(StrCat(unit, "_add(cast('9999-12-31 23:59:59' as timestamp), "
+       , lt_max_interval, ")"), TYPE_TIMESTAMP);
+    TestIsNull(StrCat(unit, "_sub(cast('1400-01-01 00:00:00' as timestamp), "
+       , lt_max_interval, ")"), TYPE_TIMESTAMP);
 
     // Same as above but with edge case values of max int/long.
-    TestIsNull(unit + "_add(years_add(cast('9999-12-31 23:59:59' as timestamp), 1), "
-        + max_int + ")", TYPE_TIMESTAMP);
-    TestIsNull(unit + "_sub(cast('1400-01-01 00:00:00' as timestamp), " + max_int + ")",
-        TYPE_TIMESTAMP);
-    TestIsNull(unit + "_add(years_add(cast('9999-12-31 23:59:59' as timestamp), 1), "
-        + max_long + ")", TYPE_TIMESTAMP);
-    TestIsNull(unit + "_sub(cast('1400-01-01 00:00:00' as timestamp), " + max_long
-        + ")", TYPE_TIMESTAMP);
+    TestIsNull(StrCat(unit,"_add(years_add(cast('9999-12-31 23:59:59' as timestamp), 1), "
+       , max_int, ")"), TYPE_TIMESTAMP);
+    TestIsNull(StrCat(unit, "_sub(cast('1400-01-01 00:00:00' as timestamp), ",
+        max_int, ")"), TYPE_TIMESTAMP);
+    TestIsNull(StrCat(unit,"_add(years_add(cast('9999-12-31 23:59:59' as timestamp), 1), "
+       , max_long, ")"), TYPE_TIMESTAMP);
+    TestIsNull(StrCat(unit, "_sub(cast('1400-01-01 00:00:00' as timestamp), ", max_long
+       , ")"), TYPE_TIMESTAMP);
 
     // Test that adding/subtracting a value slightly less than the MAX_*_INTERVAL
     // can result in a non-NULL.
-    TestIsNotNull(unit + "_add(cast('1400-01-01 00:00:00' as timestamp), "
-        + lt_max_interval + ")", TYPE_TIMESTAMP);
-    TestIsNotNull(unit + "_sub(cast('9999-12-31 23:59:59' as timestamp), "
-        + lt_max_interval + ")", TYPE_TIMESTAMP);
+    TestIsNotNull(StrCat(unit, "_add(cast('1400-01-01 00:00:00' as timestamp), "
+       , lt_max_interval, ")"), TYPE_TIMESTAMP);
+    TestIsNotNull(StrCat(unit, "_sub(cast('9999-12-31 23:59:59' as timestamp), "
+       , lt_max_interval, ")"), TYPE_TIMESTAMP);
 
     // Test that adding/subtracting either results in NULL or a value more/less than
     // the original value.
-    TestValue("isnull(" + unit + "_add(" + year_5000 + ", " + max_int
-        + "), " + gt_year_5000 + ") > " + year_5000, TYPE_BOOLEAN, true);
-    TestValue("isnull(" + unit + "_sub(" + year_5000 + ", " + max_int
-        + "), " + lt_year_5000 + ") < " + year_5000, TYPE_BOOLEAN, true);
-    TestValue("isnull(" + unit + "_add(" + year_5000 + ", " + max_long
-        + "), " + gt_year_5000 + ") > " + year_5000, TYPE_BOOLEAN, true);
-    TestValue("isnull(" + unit + "_sub(" + year_5000 + ", " + max_long
-        + "), " + lt_year_5000 + ") < " + year_5000, TYPE_BOOLEAN, true);
+    TestValue(StrCat("isnull(", unit, "_add(", year_5000, ", ", max_int
+       , "), ", gt_year_5000, ") > ", year_5000), TYPE_BOOLEAN, true);
+    TestValue(StrCat("isnull(", unit, "_sub(", year_5000, ", ", max_int
+       , "), ", lt_year_5000, ") < ", year_5000), TYPE_BOOLEAN, true);
+    TestValue(StrCat("isnull(", unit, "_add(", year_5000, ", ", max_long
+       , "), ", gt_year_5000, ") > ", year_5000), TYPE_BOOLEAN, true);
+    TestValue(StrCat("isnull(", unit, "_sub(", year_5000, ", ", max_long
+       , "), ", lt_year_5000, ") < ", year_5000), TYPE_BOOLEAN, true);
   }
 
   // Regression test for IMPALA-2260, a seemingly non-edge case value results in an
@@ -6988,10 +6992,10 @@ TEST_P(ExprTest, TimestampFunctions) {
     // magnitude of the real max values so testing can start at max / 10.
     for (int64_t interval = it->second / 10; interval > 0; interval /= 10) {
       const string& sql_interval = lexical_cast<string>(interval);
-      TestIsNotNull(unit + "_add(cast('1400-01-01 00:00:00' as timestamp), "
-          + sql_interval + ")", TYPE_TIMESTAMP);
-      TestIsNotNull(unit + "_sub(cast('9999-12-31 23:59:59' as timestamp), "
-          + sql_interval + ")", TYPE_TIMESTAMP);
+      TestIsNotNull(StrCat(unit, "_add(cast('1400-01-01 00:00:00' as timestamp), "
+         , sql_interval, ")"), TYPE_TIMESTAMP);
+      TestIsNotNull(StrCat(unit, "_sub(cast('9999-12-31 23:59:59' as timestamp), "
+         , sql_interval, ")"), TYPE_TIMESTAMP);
     }
   }
 
diff --git a/be/src/runtime/io/disk-io-mgr-test.cc b/be/src/runtime/io/disk-io-mgr-test.cc
index 645e0962f..09593f705 100644
--- a/be/src/runtime/io/disk-io-mgr-test.cc
+++ b/be/src/runtime/io/disk-io-mgr-test.cc
@@ -40,6 +40,7 @@
 #include "util/histogram-metric.h"
 #include "util/thread.h"
 #include "util/time.h"
+#include "gutil/strings/strcat.h"
 
 #include "common/names.h"
 
@@ -1848,10 +1849,10 @@ TEST_F(DiskIoMgrTest, MetricsOfWriteSizeAndLatency) {
     string i_str = std::to_string(i);
     auto write_size_org =
         ImpaladMetrics::IO_MGR_METRICS->FindMetricForTesting<HistogramMetric>(
-            key_prefix + i_str + write_size_postfix);
+            StrCat(key_prefix, i_str, write_size_postfix));
     auto write_latency_org =
         ImpaladMetrics::IO_MGR_METRICS->FindMetricForTesting<HistogramMetric>(
-            key_prefix + i_str + write_latency_postfix);
+            StrCat(key_prefix, i_str, write_latency_postfix));
     if (write_size_org != nullptr) write_size_org->Reset();
     if (write_latency_org != nullptr) write_latency_org->Reset();
   }
diff --git a/be/src/runtime/timestamp-test.cc b/be/src/runtime/timestamp-test.cc
index 5ef431648..cabd5a94e 100644
--- a/be/src/runtime/timestamp-test.cc
+++ b/be/src/runtime/timestamp-test.cc
@@ -29,6 +29,7 @@
 #include "runtime/timestamp-value.inline.h"
 #include "testutil/gtest-util.h"
 #include "util/string-parser.h"
+#include "gutil/strings/strcat.h"
 
 #include "common/names.h"
 
@@ -182,7 +183,7 @@ void TestTimestampTokens(vector<TimestampToken>* toks, int year, int month,
           val.append(lexical_cast<string>((*toks)[i].val));
         }
       }
-      string fmt_val = "Format: " + fmt + ", Val: " + val;
+      string fmt_val = StrCat("Format: ", fmt, ", Val: ",  val);
       DateTimeFormatContext dt_ctx(fmt.c_str());
       ASSERT_TRUE(SimpleDateFormatTokenizer::Tokenize(&dt_ctx, PARSE)) << fmt_val;
       TimestampValue tv =
@@ -211,7 +212,8 @@ void TestTimestampTokens(vector<TimestampToken>* toks, int year, int month,
           }
           if (i + 1 < toks_len) val.push_back(*separator);
         }
-        string fmt_val = "Format: " + fmt + ", Val: " + val;
+
+        string fmt_val = StrCat("Format: ", fmt, ", Val: ",  val);
         DateTimeFormatContext dt_ctx(fmt.c_str());
         ASSERT_TRUE(SimpleDateFormatTokenizer::Tokenize(&dt_ctx, PARSE)) << fmt_val;
         TimestampValue tv =
diff --git a/be/src/scheduling/cluster-membership-test-util.cc b/be/src/scheduling/cluster-membership-test-util.cc
index 36c9e1ad5..b34d03301 100644
--- a/be/src/scheduling/cluster-membership-test-util.cc
+++ b/be/src/scheduling/cluster-membership-test-util.cc
@@ -21,6 +21,7 @@
 #include "scheduling/executor-group.h"
 #include "service/impala-server.h"
 #include "util/uid-util.h"
+#include "gutil/strings/strcat.h"
 
 static const int BACKEND_PORT = 1000;
 static const int KRPC_PORT = 2000;
@@ -38,7 +39,7 @@ string HostIdxToIpAddr(int host_idx) {
   DCHECK_LT(host_idx, (1 << 24));
   string suffix;
   for (int i = 0; i < 3; ++i) {
-    suffix = "." + std::to_string(host_idx % 256) + suffix; // prepend
+    suffix = StrCat(".", std::to_string(host_idx % 256), suffix); // prepend
     host_idx /= 256;
   }
   DCHECK_EQ(0, host_idx);
diff --git a/be/src/util/runtime-profile.cc b/be/src/util/runtime-profile.cc
index b63cd5c99..9b7c2b407 100644
--- a/be/src/util/runtime-profile.cc
+++ b/be/src/util/runtime-profile.cc
@@ -47,6 +47,7 @@
 #include "util/redactor.h"
 #include "util/scope-exit-trigger.h"
 #include "util/ubsan.h"
+#include "gutil/strings/strcat.h"
 
 #include "common/names.h"
 
@@ -1041,7 +1042,7 @@ void RuntimeProfileBase::AddSkewInfo(RuntimeProfileBase* root, double threshold)
             root->AddInfoStringInternal(SKEW_SUMMARY, name(), true);
             // Log the counter name and skew details in 'this' profile
             this->AddInfoStringInternal(
-                SKEW_DETAILS, counter_name_it + " " + details, true);
+                SKEW_DETAILS, StrCat(counter_name_it, " ", details), true);
           }
         }
       }
diff --git a/bin/run_clang_tidy.sh b/bin/run_clang_tidy.sh
index 585fb6038..4319873f5 100755
--- a/bin/run_clang_tidy.sh
+++ b/bin/run_clang_tidy.sh
@@ -59,9 +59,13 @@ export PATH="${IMPALA_TOOLCHAIN_PACKAGES_HOME}/llvm-${IMPALA_LLVM_VERSION}/share
 :${IMPALA_TOOLCHAIN_PACKAGES_HOME}/llvm-${IMPALA_LLVM_VERSION}/bin/\
 :$PATH"
 TMP_STDERR=$(mktemp)
+STRCAT_MESSAGE="Impala-specific note: This can also be fixed using the StrCat() function \
+from be/src/gutil/strings strcat.h)"
+CLANG_STRING_CONCAT="performance-inefficient-string-concatenation"
 trap "rm $TMP_STDERR" EXIT
 if ! run-clang-tidy.py -quiet -header-filter "${PIPE_DIRS%?}" \
-                       -j"${CORES}" ${DIRS} 2> ${TMP_STDERR};
+                       -j"${CORES}" ${DIRS} 2> ${TMP_STDERR} | \
+  sed "/${CLANG_STRING_CONCAT}/ s#\$# ${STRCAT_MESSAGE}#";
 then
   echo "run-clang-tidy.py hit an error, dumping stderr output"
   cat ${TMP_STDERR} >&2