You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ya...@apache.org on 2026/04/15 06:45:19 UTC
(doris) branch master updated: [opt](function) simplify FunctionStringPad fast path (#62359)
This is an automated email from the ASF dual-hosted git repository.
yanxuecheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 5cbaa4f8b97 [opt](function) simplify FunctionStringPad fast path (#62359)
5cbaa4f8b97 is described below
commit 5cbaa4f8b97b409c5b8d065de3ead563c47c76f0
Author: Mryange <ya...@selectdb.com>
AuthorDate: Wed Apr 15 14:45:13 2026 +0800
[opt](function) simplify FunctionStringPad fast path (#62359)
This PR refactors BE `lpad` / `rpad` implementation in
`FunctionStringPad`.
The previous version mixed multiple optimization dimensions in one
heavily-templated path, which made the code hard to read and maintain.
This change simplifies the execution model into:
- a fast path for `length const && pad const`
- a general path for all other cases
The const/const path keeps the key optimizations:
- direct write to `ColumnString::Chars`
- one-time pad preprocessing
- output size estimation
- optional ASCII fast path
---
be/src/exprs/function/function_string_concat.h | 378 +++++++++++++++++++------
1 file changed, 284 insertions(+), 94 deletions(-)
diff --git a/be/src/exprs/function/function_string_concat.h b/be/src/exprs/function/function_string_concat.h
index 2bbefd43919..fe6171dfbeb 100644
--- a/be/src/exprs/function/function_string_concat.h
+++ b/be/src/exprs/function/function_string_concat.h
@@ -663,6 +663,92 @@ public:
}
};
+/// PaddingChars pre-processes the pad string for efficient padding.
+/// When is_utf8=false, character count equals byte count — no UTF-8 decoding needed.
+/// When is_utf8=true, we build a byte-offset table for code points.
+/// In both cases, the pad string is pre-expanded (doubled) until it has >= 16 characters,
+/// so that each memcpy in append_to copies at least 16 bytes at a time.
+template <bool is_utf8>
+struct PaddingChars {
+ std::string pad_string;
+ /// utf8_byte_offsets[i] = byte offset of i-th code point in pad_string.
+ /// utf8_byte_offsets has (num_chars + 1) entries, with [0]=0 and [num_chars]=pad_string.size().
+ std::vector<size_t> utf8_byte_offsets;
+
+ explicit PaddingChars(const uint8_t* data, size_t len)
+ : pad_string(reinterpret_cast<const char*>(data), len) {
+ init();
+ }
+
+ size_t num_chars() const {
+ if constexpr (is_utf8) {
+ return utf8_byte_offsets.size() - 1;
+ } else {
+ return pad_string.size();
+ }
+ }
+
+ size_t chars_to_bytes(size_t n) const {
+ if constexpr (is_utf8) {
+ return utf8_byte_offsets[n];
+ } else {
+ return n;
+ }
+ }
+
+ /// Append `num_chars_to_pad` padding characters to dst, return bytes written.
+ size_t append_to(uint8_t* dst, size_t num_chars_to_pad) const {
+ if (num_chars_to_pad == 0) {
+ return 0;
+ }
+ const auto* src = reinterpret_cast<const uint8_t*>(pad_string.data());
+ const size_t step = num_chars();
+ uint8_t* dst_start = dst;
+ while (num_chars_to_pad > step) {
+ size_t bytes = chars_to_bytes(step);
+ memcpy(dst, src, bytes);
+ dst += bytes;
+ num_chars_to_pad -= step;
+ }
+ size_t bytes = chars_to_bytes(num_chars_to_pad);
+ memcpy(dst, src, bytes);
+ dst += bytes;
+ return dst - dst_start;
+ }
+
+private:
+ void init() {
+ if (pad_string.empty()) {
+ return;
+ }
+
+ if constexpr (is_utf8) {
+ // Build byte-offset table for each code point.
+ size_t offset = 0;
+ utf8_byte_offsets.reserve(pad_string.size() + 1);
+ while (offset < pad_string.size()) {
+ utf8_byte_offsets.push_back(offset);
+ offset += get_utf8_byte_length(static_cast<uint8_t>(pad_string[offset]));
+ offset = std::min(offset, pad_string.size());
+ }
+ utf8_byte_offsets.push_back(pad_string.size());
+ }
+
+ // Pre-expand pad_string until it has >= 16 characters.
+ // This ensures append_to() copies at least 16 bytes per iteration.
+ while (num_chars() < 16) {
+ if constexpr (is_utf8) {
+ size_t old_count = utf8_byte_offsets.size();
+ size_t base = utf8_byte_offsets.back();
+ for (size_t i = 1; i < old_count; ++i) {
+ utf8_byte_offsets.push_back(utf8_byte_offsets[i] + base);
+ }
+ }
+ pad_string += pad_string;
+ }
+ }
+};
+
template <typename Impl>
class FunctionStringPad : public IFunction {
public:
@@ -679,8 +765,6 @@ public:
uint32_t result, size_t input_rows_count) const override {
DCHECK_GE(arguments.size(), 3);
auto null_map = ColumnUInt8::create(input_rows_count, 0);
- // we create a zero column to simply implement
- auto const_null_map = ColumnUInt8::create(input_rows_count, 0);
auto res = ColumnString::create();
ColumnPtr col[3];
@@ -695,117 +779,223 @@ public:
res_offsets.resize(input_rows_count);
const auto* strcol = assert_cast<const ColumnString*>(col[0].get());
- const auto& strcol_offsets = strcol->get_offsets();
- const auto& strcol_chars = strcol->get_chars();
-
const auto* col_len = assert_cast<const ColumnInt32*>(col[1].get());
const auto& col_len_data = col_len->get_data();
const auto* padcol = assert_cast<const ColumnString*>(col[2].get());
- const auto& padcol_offsets = padcol->get_offsets();
- const auto& padcol_chars = padcol->get_chars();
- std::visit(
- [&](auto str_const, auto len_const, auto pad_const) {
- execute_utf8<str_const, len_const, pad_const>(
- strcol_offsets, strcol_chars, col_len_data, padcol_offsets,
- padcol_chars, res_offsets, res_chars, null_map_data, input_rows_count);
- },
- make_bool_variant(col_const[0]), make_bool_variant(col_const[1]),
- make_bool_variant(col_const[2]));
+
+ if (col_const[1] && col_const[2]) {
+ auto pad = padcol->get_data_at(0);
+ const bool pad_all_ascii =
+ simd::VStringFunctions::is_ascii({pad.data, static_cast<size_t>(pad.size)});
+ const bool all_ascii = pad_all_ascii && strcol->is_ascii();
+ std::visit(
+ [&](auto str_const) {
+ if (all_ascii) {
+ execute_const_len_const_pad<true, str_const>(
+ *strcol, col_len_data, *padcol, res_offsets, res_chars,
+ null_map_data, input_rows_count);
+ } else {
+ execute_const_len_const_pad<false, str_const>(
+ *strcol, col_len_data, *padcol, res_offsets, res_chars,
+ null_map_data, input_rows_count);
+ }
+ },
+ make_bool_variant(col_const[0]));
+ } else {
+ std::visit(
+ [&](auto str_const) {
+ execute_general<str_const>(*strcol, col_len_data, col_const[1], *padcol,
+ col_const[2], res_offsets, res_chars,
+ null_map_data, input_rows_count);
+ },
+ make_bool_variant(col_const[0]));
+ }
block.get_by_position(result).column =
ColumnNullable::create(std::move(res), std::move(null_map));
return Status::OK();
}
- template <bool str_const, bool len_const, bool pad_const>
- void execute_utf8(const ColumnString::Offsets& strcol_offsets,
- const ColumnString::Chars& strcol_chars,
- const ColumnInt32::Container& col_len_data,
- const ColumnString::Offsets& padcol_offsets,
- const ColumnString::Chars& padcol_chars, ColumnString::Offsets& res_offsets,
- ColumnString::Chars& res_chars, ColumnUInt8::Container& null_map_data,
- size_t input_rows_count) const {
- std::vector<size_t> pad_index;
- size_t const_pad_char_size = 0;
- // If pad_const = true, initialize pad_index only once.
- // The same logic applies to the if constexpr (!pad_const) condition below.
- if constexpr (pad_const) {
- const_pad_char_size = simd::VStringFunctions::get_char_len(
- (const char*)padcol_chars.data(), padcol_offsets[0], pad_index);
+private:
+ template <bool is_utf8>
+ static size_t get_char_length(const uint8_t* str_data, size_t str_byte_len) {
+ if constexpr (is_utf8) {
+ return simd::VStringFunctions::get_char_len(reinterpret_cast<const char*>(str_data),
+ str_byte_len);
}
+ return str_byte_len;
+ }
- fmt::memory_buffer buffer;
- buffer.resize(strcol_chars.size());
- size_t buffer_len = 0;
+ template <bool is_utf8>
+ static size_t get_truncated_byte_length(const uint8_t* str_data, size_t str_byte_len,
+ size_t str_char_len, size_t target_len) {
+ if constexpr (!is_utf8) {
+ return target_len;
+ }
+ if (str_char_len == target_len) {
+ return str_byte_len;
+ }
+ auto [byte_len, _] = simd::VStringFunctions::iterate_utf8_with_limit_length(
+ reinterpret_cast<const char*>(str_data),
+ reinterpret_cast<const char*>(str_data) + str_byte_len, target_len);
+ return byte_len;
+ }
- for (size_t i = 0; i < input_rows_count; ++i) {
- if constexpr (!pad_const) {
- pad_index.clear();
- }
- const auto len = col_len_data[index_check_const<len_const>(i)];
- if (len < 0) {
- // return NULL when input length is invalid number
- null_map_data[i] = true;
- res_offsets[i] = buffer_len;
- } else {
- const auto str_idx = index_check_const<str_const>(i);
- const int str_len = strcol_offsets[str_idx] - strcol_offsets[str_idx - 1];
- const auto* str_data = &strcol_chars[strcol_offsets[str_idx - 1]];
- const auto pad_idx = index_check_const<pad_const>(i);
- const int pad_len = padcol_offsets[pad_idx] - padcol_offsets[pad_idx - 1];
- const auto* pad_data = &padcol_chars[padcol_offsets[pad_idx - 1]];
-
- auto [iterate_byte_len, iterate_char_len] =
- simd::VStringFunctions::iterate_utf8_with_limit_length(
- (const char*)str_data, (const char*)str_data + str_len, len);
- // If iterate_char_len equals len, it indicates that the str length is greater than or equal to len
- if (iterate_char_len == len) {
- buffer.resize(buffer_len + iterate_byte_len);
- memcpy(buffer.data() + buffer_len, str_data, iterate_byte_len);
- buffer_len += iterate_byte_len;
- res_offsets[i] = buffer_len;
- continue;
- }
- size_t pad_char_size;
- if constexpr (!pad_const) {
- pad_char_size = simd::VStringFunctions::get_char_len((const char*)pad_data,
- pad_len, pad_index);
- } else {
- pad_char_size = const_pad_char_size;
- }
+ static void ensure_capacity(ColumnString::Chars& res_chars, size_t needed, size_t row) {
+ if (needed <= res_chars.size()) {
+ return;
+ }
+ ColumnString::check_chars_length(needed, row);
+ res_chars.resize(std::max(needed, res_chars.size() * 3 / 2));
+ }
- // make compatible with mysql. return empty string if pad is empty
- if (pad_char_size == 0) {
- res_offsets[i] = buffer_len;
- continue;
- }
- const size_t str_char_size = iterate_char_len;
- const size_t pad_times = (len - str_char_size) / pad_char_size;
- const size_t pad_remainder_len = pad_index[(len - str_char_size) % pad_char_size];
- const size_t new_capacity = str_len + size_t(pad_times + 1) * pad_len;
- ColumnString::check_chars_length(buffer_len + new_capacity, i);
- buffer.resize(buffer_len + new_capacity);
- if constexpr (!Impl::is_lpad) {
- memcpy(buffer.data() + buffer_len, str_data, str_len);
- buffer_len += str_len;
- }
- // Prepend chars of pad.
- StringOP::fast_repeat((uint8_t*)buffer.data() + buffer_len, pad_data, pad_len,
- pad_times);
- buffer_len += pad_times * pad_len;
+ template <bool is_utf8>
+ static size_t estimate_const_output_bytes(const ColumnString::Chars& strcol_chars,
+ int target_len, size_t input_rows_count,
+ const PaddingChars<is_utf8>* padding) {
+ if (target_len <= 0) {
+ return 0;
+ }
+ if constexpr (!is_utf8) {
+ return static_cast<size_t>(target_len) * input_rows_count;
+ }
+ if (padding != nullptr && padding->num_chars() > 0) {
+ size_t pad_bytes_per_char =
+ (padding->pad_string.size() + padding->num_chars() - 1) / padding->num_chars();
+ return strcol_chars.size() +
+ static_cast<size_t>(target_len) * pad_bytes_per_char * input_rows_count;
+ }
+ return strcol_chars.size();
+ }
+
+ template <bool is_utf8>
+ static void append_result_row(const uint8_t* str_data, size_t str_byte_len, int target_len,
+ const PaddingChars<is_utf8>* padding,
+ ColumnString::Chars& res_chars,
+ ColumnString::Offsets& res_offsets,
+ ColumnUInt8::Container& null_map_data, size_t row,
+ size_t& dst_offset) {
+ if (target_len < 0) {
+ null_map_data[row] = true;
+ res_offsets[row] = dst_offset;
+ return;
+ }
+
+ const size_t str_char_len = get_char_length<is_utf8>(str_data, str_byte_len);
+ const size_t target_char_len = static_cast<size_t>(target_len);
+ if (str_char_len >= target_char_len) {
+ const size_t truncated_byte_len = get_truncated_byte_length<is_utf8>(
+ str_data, str_byte_len, str_char_len, target_char_len);
+ const size_t needed = dst_offset + truncated_byte_len;
+ ensure_capacity(res_chars, needed, row);
+ memcpy(res_chars.data() + dst_offset, str_data, truncated_byte_len);
+ dst_offset += truncated_byte_len;
+ res_offsets[row] = dst_offset;
+ return;
+ }
+
+ if (padding == nullptr || padding->num_chars() == 0) {
+ res_offsets[row] = dst_offset;
+ return;
+ }
- memcpy(buffer.data() + buffer_len, pad_data, pad_remainder_len);
- buffer_len += pad_remainder_len;
+ const size_t pad_char_count = target_char_len - str_char_len;
+ const size_t full_cycles = pad_char_count / padding->num_chars();
+ const size_t remainder_chars = pad_char_count % padding->num_chars();
+ const size_t pad_bytes =
+ full_cycles * padding->pad_string.size() + padding->chars_to_bytes(remainder_chars);
+ const size_t needed = dst_offset + str_byte_len + pad_bytes;
+ ensure_capacity(res_chars, needed, row);
+
+ if constexpr (Impl::is_lpad) {
+ dst_offset += padding->append_to(res_chars.data() + dst_offset, pad_char_count);
+ memcpy(res_chars.data() + dst_offset, str_data, str_byte_len);
+ dst_offset += str_byte_len;
+ } else {
+ memcpy(res_chars.data() + dst_offset, str_data, str_byte_len);
+ dst_offset += str_byte_len;
+ dst_offset += padding->append_to(res_chars.data() + dst_offset, pad_char_count);
+ }
+ res_offsets[row] = dst_offset;
+ }
+
+ template <bool all_ascii, bool str_const>
+ static void execute_const_len_const_pad(const ColumnString& strcol,
+ const ColumnInt32::Container& col_len_data,
+ const ColumnString& padcol,
+ ColumnString::Offsets& res_offsets,
+ ColumnString::Chars& res_chars,
+ ColumnUInt8::Container& null_map_data,
+ size_t input_rows_count) {
+ constexpr bool is_utf8 = !all_ascii;
+ using PadChars = PaddingChars<is_utf8>;
+
+ const int target_len = col_len_data[0];
+ std::optional<PadChars> padding;
+ const auto pad = padcol.get_data_at(0);
+ if (!pad.empty()) {
+ padding.emplace(reinterpret_cast<const uint8_t*>(pad.data), pad.size);
+ }
- if constexpr (Impl::is_lpad) {
- memcpy(buffer.data() + buffer_len, str_data, str_len);
- buffer_len += str_len;
+ const PadChars* padding_ptr = padding ? &*padding : nullptr;
+ const size_t estimated_total = estimate_const_output_bytes<is_utf8>(
+ strcol.get_chars(), target_len, input_rows_count, padding_ptr);
+ if (estimated_total > 0) {
+ ColumnString::check_chars_length(estimated_total, 0, input_rows_count);
+ }
+ res_chars.resize(estimated_total);
+
+ size_t dst_offset = 0;
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ auto str = strcol.get_data_at(index_check_const<str_const>(i));
+ append_result_row<is_utf8>(reinterpret_cast<const uint8_t*>(str.data), str.size,
+ target_len, padding_ptr, res_chars, res_offsets,
+ null_map_data, i, dst_offset);
+ }
+ res_chars.resize(dst_offset);
+ }
+
+ template <bool str_const>
+ static void execute_general(const ColumnString& strcol,
+ const ColumnInt32::Container& col_len_data, bool len_const,
+ const ColumnString& padcol, bool pad_const,
+ ColumnString::Offsets& res_offsets, ColumnString::Chars& res_chars,
+ ColumnUInt8::Container& null_map_data, size_t input_rows_count) {
+ using PadChars = PaddingChars<true>;
+ std::optional<PadChars> const_padding;
+ const PadChars* const_padding_ptr = nullptr;
+ if (pad_const) {
+ auto pad = padcol.get_data_at(0);
+ if (!pad.empty()) {
+ const_padding.emplace(reinterpret_cast<const uint8_t*>(pad.data), pad.size);
+ const_padding_ptr = &*const_padding;
+ }
+ }
+
+ res_chars.resize(strcol.get_chars().size());
+ size_t dst_offset = 0;
+ for (size_t i = 0; i < input_rows_count; ++i) {
+ auto str = strcol.get_data_at(index_check_const<str_const>(i));
+ const int target_len = col_len_data[len_const ? 0 : i];
+
+ const PadChars* padding_ptr = const_padding_ptr;
+ std::optional<PadChars> row_padding;
+ if (!pad_const) {
+ auto pad = padcol.get_data_at(i);
+ if (!pad.empty()) {
+ row_padding.emplace(reinterpret_cast<const uint8_t*>(pad.data), pad.size);
+ padding_ptr = &*row_padding;
+ } else {
+ padding_ptr = nullptr;
}
- res_offsets[i] = buffer_len;
}
+
+ append_result_row<true>(reinterpret_cast<const uint8_t*>(str.data), str.size,
+ target_len, padding_ptr, res_chars, res_offsets, null_map_data,
+ i, dst_offset);
}
- res_chars.insert(buffer.data(), buffer.data() + buffer_len);
+ res_chars.resize(dst_offset);
}
};
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org