From 13ef2c9e1d199a59525d8bdac0c67659df786fb5 Mon Sep 17 00:00:00 2001 From: xinghuayu007 <1450306854@qq.com> Date: Mon, 26 Jul 2021 09:38:07 +0800 Subject: [PATCH] [Function][Enhance] lower/upper case transfer function vectorized (#6253) Currently, the function lower()/upper() can only handle one char at a time. A vectorized function has been implemented, it makes performance 2 times faster. Here is the performance test: The length of char: 26, test 100 times vectorized-function-cost: 99491 ns normal-function-cost: 134766 ns The length of char: 260, test 100 times vectorized-function-cost: 179341 ns normal-function-cost: 344995 ns --- be/src/exprs/string_functions.cpp | 14 ++--- be/src/util/vectorized-tool/lower.h | 37 +++++++++++ .../util/vectorized-tool/lower_upper_impl.h | 61 +++++++++++++++++++ be/src/util/vectorized-tool/upper.h | 36 +++++++++++ be/test/exprs/string_functions_test.cpp | 19 ++++++ 5 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 be/src/util/vectorized-tool/lower.h create mode 100644 be/src/util/vectorized-tool/lower_upper_impl.h create mode 100644 be/src/util/vectorized-tool/upper.h diff --git a/be/src/exprs/string_functions.cpp b/be/src/exprs/string_functions.cpp index c805519aad..0ede46edf8 100644 --- a/be/src/exprs/string_functions.cpp +++ b/be/src/exprs/string_functions.cpp @@ -16,6 +16,8 @@ // under the License. #include "exprs/string_functions.h" +#include "util/vectorized-tool/lower.h" +#include "util/vectorized-tool/upper.h" #include @@ -346,15 +348,11 @@ StringVal StringFunctions::lower(FunctionContext* context, const StringVal& str) if (str.is_null) { return StringVal::null(); } - // TODO pengyubing - // StringVal result = StringVal::create_temp_string_val(context, str.len); StringVal result(context, str.len); if (UNLIKELY(result.is_null)) { return result; } - for (int i = 0; i < str.len; ++i) { - result.ptr[i] = ::tolower(str.ptr[i]); - } + Lower::to_lower(str.ptr, str.len, result.ptr); return result; } @@ -362,15 +360,11 @@ StringVal StringFunctions::upper(FunctionContext* context, const StringVal& str) if (str.is_null) { return StringVal::null(); } - // TODO pengyubing - // StringVal result = StringVal::create_temp_string_val(context, str.len); StringVal result(context, str.len); if (UNLIKELY(result.is_null)) { return result; } - for (int i = 0; i < str.len; ++i) { - result.ptr[i] = ::toupper(str.ptr[i]); - } + Upper::to_upper(str.ptr, str.len, result.ptr); return result; } diff --git a/be/src/util/vectorized-tool/lower.h b/be/src/util/vectorized-tool/lower.h new file mode 100644 index 0000000000..c28308f365 --- /dev/null +++ b/be/src/util/vectorized-tool/lower.h @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#pragma once +#ifndef BE_LOWER_H +#define BE_LOWER_H + +#include "lower_upper_impl.h" +#include + +namespace doris { +class Lower { +public: + static void to_lower(uint8_t * src, int64_t len, uint8_t * dst) { + if (len <= 0) { + return; + } + LowerUpperImpl<'A', 'Z'> lowerUpper; + lowerUpper.transfer(src, src + len, dst); + } +}; +} + +#endif //BE_LOWER_H diff --git a/be/src/util/vectorized-tool/lower_upper_impl.h b/be/src/util/vectorized-tool/lower_upper_impl.h new file mode 100644 index 0000000000..11612dd0f4 --- /dev/null +++ b/be/src/util/vectorized-tool/lower_upper_impl.h @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#pragma once +#include +#include +#include +#include + +// the code refer: https://clickhouse.tech/codebrowser/html_report//ClickHouse/src/Functions/LowerUpperImpl.h.html +// Doris only handle one character at a time, this function use SIMD to more characters at a time +namespace doris { + +template +class LowerUpperImpl { +public: + static void transfer(const uint8_t * src, const uint8_t * src_end, uint8_t * dst) + { + const auto flip_case_mask = 'A' ^ 'a'; + +#ifdef __SSE2__ + const auto bytes_sse = sizeof(__m128i); + const auto src_end_sse = src_end - (src_end - src) % bytes_sse; + + const auto v_not_case_lower_bound = _mm_set1_epi8(not_case_lower_bound - 1); + const auto v_not_case_upper_bound = _mm_set1_epi8(not_case_upper_bound + 1); + const auto v_flip_case_mask = _mm_set1_epi8(flip_case_mask); + + for (; src < src_end_sse; src += bytes_sse, dst += bytes_sse) + { + const auto chars = _mm_loadu_si128(reinterpret_cast(src)); + const auto is_not_case + = _mm_and_si128(_mm_cmpgt_epi8(chars, v_not_case_lower_bound), _mm_cmplt_epi8(chars, v_not_case_upper_bound)); + const auto xor_mask = _mm_and_si128(v_flip_case_mask, is_not_case); + const auto cased_chars = _mm_xor_si128(chars, xor_mask); + _mm_storeu_si128(reinterpret_cast<__m128i *>(dst), cased_chars); + } +#endif + + for (; src < src_end; ++src, ++dst) + if (*src >= not_case_lower_bound && *src <= not_case_upper_bound) + *dst = *src ^ flip_case_mask; + else + *dst = *src; + } +}; +} \ No newline at end of file diff --git a/be/src/util/vectorized-tool/upper.h b/be/src/util/vectorized-tool/upper.h new file mode 100644 index 0000000000..a1b7b7a72d --- /dev/null +++ b/be/src/util/vectorized-tool/upper.h @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +#pragma once +#ifndef BE_UPPER_H +#define BE_UPPER_H + +#include "lower_upper_impl.h" +#include + +namespace doris { + class Upper { + public: + static void to_upper(uint8_t * src, int64_t len, uint8_t * dst) { + if (len <= 0) { + return; + } + LowerUpperImpl<'a', 'z'> lowerUpper; + lowerUpper.transfer(src, src + len, dst); + } + }; +} +#endif //BE_UPPER_H diff --git a/be/test/exprs/string_functions_test.cpp b/be/test/exprs/string_functions_test.cpp index abfa96928c..a5240c3a54 100644 --- a/be/test/exprs/string_functions_test.cpp +++ b/be/test/exprs/string_functions_test.cpp @@ -623,6 +623,25 @@ TEST_F(StringFunctionsTest, bit_length) { delete context; } +TEST_F(StringFunctionsTest, lower) { + ASSERT_EQ(StringVal("hello"), StringFunctions::lower(ctx, StringVal("hello"))); + ASSERT_EQ(StringVal("hello"), StringFunctions::lower(ctx, StringVal("HELLO"))); + ASSERT_EQ(StringVal("hello123"), StringFunctions::lower(ctx, StringVal("HELLO123"))); + ASSERT_EQ(StringVal("hello, 123"), StringFunctions::lower(ctx, StringVal("HELLO, 123"))); + ASSERT_EQ(StringVal::null(), StringFunctions::lower(ctx, StringVal::null())); + ASSERT_EQ(StringVal(""), StringFunctions::lower(ctx, StringVal(""))); +} + +TEST_F(StringFunctionsTest, upper) { + // function test + ASSERT_EQ(StringVal("HELLO"), StringFunctions::upper(ctx, StringVal("HELLO"))); + ASSERT_EQ(StringVal("HELLO"), StringFunctions::upper(ctx, StringVal("hello"))); + ASSERT_EQ(StringVal("HELLO123"), StringFunctions::upper(ctx, StringVal("hello123"))); + ASSERT_EQ(StringVal("HELLO, 123"), StringFunctions::upper(ctx, StringVal("hello, 123"))); + ASSERT_EQ(StringVal::null(), StringFunctions::upper(ctx, StringVal::null())); + ASSERT_EQ(StringVal(""), StringFunctions::upper(ctx, StringVal(""))); +} + } // namespace doris int main(int argc, char** argv) {