From b35b586f1053af248b1a8ce8293403924b08f8cc Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 30 Jun 2022 17:05:50 +0800 Subject: [PATCH 01/14] Add options to support `Profile Guided Optimization` --- CMakeLists.txt | 52 +++++ dbms/src/Common/TiFlashBuildInfo.cpp | 17 ++ format-diff.py | 3 - .../include/common/config_common.h.in | 4 + release-centos7-llvm/env/prepare-sysroot.sh | 1 + release-centos7-llvm/scripts/perf-tpch.py | 211 ++++++++++++++++++ 6 files changed, 285 insertions(+), 3 deletions(-) create mode 100755 release-centos7-llvm/scripts/perf-tpch.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e14c205f18..98eca3e3ef4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,48 @@ if (COMPILER_CLANG) endif () endif () +option (ENABLE_LLVM_PROFILE_INSTR "Generate instrumented code to collect execution counts" OFF) +option (ENABLE_LLVM_PGO "Enables flags for Profile Guided Optimization (PGO)" OFF) +option (ENABLE_LLVM_PGO_USE_SAMPLE "Enables flags for Profile Guided Optimization (PGO) and use sampling profilers" OFF) +set (USE_LLVM_FDO OFF CACHE BOOL "" FORCE) + +if (ENABLE_LLVM_PGO) + if (ENABLE_LLVM_PROFILE_INSTR) + message (FATAL_ERROR "`ENABLE_LLVM_PROFILE_INSTR` can not be used with `ENABLE_LLVM_PGO`") + endif () + if (ENABLE_LLVM_PGO_USE_SAMPLE) + + # Follow https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers + # Use https://github.com/google/autofdo + + set (_LLVM_PGO_USE_SAMPLE_FLAGS "-gline-tables-only -fdebug-info-for-profiling -funique-internal-linkage-names") + + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_LLVM_PGO_USE_SAMPLE_FLAGS}") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_LLVM_PGO_USE_SAMPLE_FLAGS}") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-rosegment") + message (STATUS "Add flags `${_LLVM_PGO_USE_SAMPLE_FLAGS}` for profiling") + + if (NOT "$ENV{TIFLASH_LLVM_PROFDATA}" STREQUAL "") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-sample-use=$ENV{TIFLASH_LLVM_PROFDATA}") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-sample-use=$ENV{TIFLASH_LLVM_PROFDATA}") + message (STATUS "Use sample profile data `$ENV{TIFLASH_LLVM_PROFDATA}` for profile-guided optimization") + set (USE_LLVM_FDO ON CACHE BOOL "" FORCE) + else () + message (STATUS "NOT use sample profile data") + endif () + + unset (_LLVM_PGO_USE_SAMPLE_FLAGS) + else () + if ("$ENV{TIFLASH_LLVM_PROFDATA}" STREQUAL "") + message (FATAL_ERROR "Please set env var `TIFLASH_LLVM_PROFDATA`") + endif () + + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-use=$ENV{TIFLASH_LLVM_PROFDATA} -Wno-profile-instr-unprofiled") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-instr-use=$ENV{TIFLASH_LLVM_PROFDATA} -Wno-profile-instr-unprofiled") + message (STATUS "Use instrumentation data `$ENV{TIFLASH_LLVM_PROFDATA}` for profile-guided optimization") + endif () +endif () + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # clang: warning: argument unused during compilation: '-stdlib=libc++' # clang: warning: argument unused during compilation: '-specs=/usr/share/dpkg/no-pie-compile.specs' [-Wunused-command-line-argument] @@ -448,6 +490,16 @@ if (TEST_LLVM_COVERAGE AND CMAKE_BUILD_TYPE_UC STREQUAL "DEBUG") set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping -DTIFLASH_LLVM_COVERAGE=1") endif () +# `ENABLE_LLVM_PROFILE_INSTR` will make executable binary generate profile data automatically. Make it only work at modules dbms and libs. +if (ENABLE_LLVM_PROFILE_INSTR) + if (ENABLE_LLVM_PGO) + message (FATAL_ERROR "`ENABLE_LLVM_PROFILE_INSTR` can not be used with `ENABLE_LLVM_PGO`") + endif () + message (STATUS "Using flag `-fprofile-instr-generate`. Generate instrumented code to collect execution counts into default.profraw file(overridden by '=' form of option or `LLVM_PROFILE_FILE` env var). Follow https://clang.llvm.org/docs/UsersManual.html#profile-guided-optimization.") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-instr-generate") +endif () + if (ARCH_AMD64) include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-mvpclmulqdq -Werror -Wall -Wextra" TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT) diff --git a/dbms/src/Common/TiFlashBuildInfo.cpp b/dbms/src/Common/TiFlashBuildInfo.cpp index ff46e04384d..f2353256b9b 100644 --- a/dbms/src/Common/TiFlashBuildInfo.cpp +++ b/dbms/src/Common/TiFlashBuildInfo.cpp @@ -101,6 +101,23 @@ std::string getEnabledFeatures() #if ENABLE_THINLTO "thinlto", #endif + +// Profile instrumentation +#if ENABLE_LLVM_PROFILE_INSTR + "profile-instr", +#endif + +// PGO +#if ENABLE_LLVM_PGO_USE_SAMPLE + "pgo-sample", +#elif ENABLE_LLVM_PGO + "pgo-instr", +#endif + +// FDO +#if USE_LLVM_FDO + "fdo", +#endif }; return fmt::format("{}", fmt::join(features.begin(), features.end(), " ")); } diff --git a/format-diff.py b/format-diff.py index c8d12925fb3..cf4fe793dca 100755 --- a/format-diff.py +++ b/format-diff.py @@ -96,9 +96,6 @@ def main(): else: print("Format check passed") else: - cmd = 'clang-format -i {}'.format(' '.join(files_to_format)) - if subprocess.Popen(cmd, shell=True, cwd=tics_repo_path).wait(): - exit(-1) print("Finish code format") else: print('No file to format') diff --git a/libs/libcommon/include/common/config_common.h.in b/libs/libcommon/include/common/config_common.h.in index 46f167ea683..0ec263216f4 100644 --- a/libs/libcommon/include/common/config_common.h.in +++ b/libs/libcommon/include/common/config_common.h.in @@ -14,3 +14,7 @@ #cmakedefine01 USE_UNWIND #cmakedefine01 USE_LLVM_LIBUNWIND #cmakedefine01 ENABLE_THINLTO +#cmakedefine01 ENABLE_LLVM_PGO +#cmakedefine01 ENABLE_LLVM_PROFILE_INSTR +#cmakedefine01 ENABLE_LLVM_PGO_USE_SAMPLE +#cmakedefine01 USE_LLVM_FDO diff --git a/release-centos7-llvm/env/prepare-sysroot.sh b/release-centos7-llvm/env/prepare-sysroot.sh index e4132eae667..1134bb37ee7 100755 --- a/release-centos7-llvm/env/prepare-sysroot.sh +++ b/release-centos7-llvm/env/prepare-sysroot.sh @@ -37,6 +37,7 @@ function install_llvm() { mkdir -p llvm-project/build cd llvm-project/build + # TODO: enable `bolt` for >= 14.0.0. https://github.com/llvm/llvm-project/tree/main/bolt cmake -DCMAKE_BUILD_TYPE=Release \ -GNinja \ -DLLVM_ENABLE_PROJECTS="clang;lld;polly;clang-tools-extra" \ diff --git a/release-centos7-llvm/scripts/perf-tpch.py b/release-centos7-llvm/scripts/perf-tpch.py new file mode 100755 index 00000000000..2af518ab2f3 --- /dev/null +++ b/release-centos7-llvm/scripts/perf-tpch.py @@ -0,0 +1,211 @@ +#!/usr/bin/python3 +# Copyright 2022 PingCAP, Ltd. +# +# Licensed 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. + +import argparse +import signal +import sys +import time +import logging +import types +import subprocess + +logger = None + + +def get_tz_offset(): + import datetime + now_stamp = time.time() + local_time = datetime.datetime.fromtimestamp(now_stamp) + utc_time = datetime.datetime.utcfromtimestamp(now_stamp) + offset = local_time - utc_time + total_seconds = offset.total_seconds() + flag = '+' + if total_seconds < 0: + flag = '-' + total_seconds = -total_seconds + mm, ss = divmod(total_seconds, 60) + hh, mm = divmod(mm, 60) + tz_offset = "%s%02d:%02d" % (flag, hh, mm) + return tz_offset + + +def init_logger(): + global logger + + tz_offset = get_tz_offset() + + orig_record_factory = logging.getLogRecordFactory() + log_colors = { + logging.DEBUG: "\033[1;34m", # blue + logging.INFO: "\033[1;32m", # green + logging.WARNING: "\033[1;35m", # magenta + logging.ERROR: "\033[1;31m", # red + logging.CRITICAL: "\033[1;41m", # red reverted + } + + def get_message(ori): + msg = str(ori.msg) + if ori.args: + msg = msg % ori.args + msg = "{}{}{}".format(log_colors[ori.levelno], msg, "\033[0m") + return msg + + def record_factory(*args, **kwargs): + record = orig_record_factory(*args, **kwargs) + record.getMessage = types.MethodType(get_message, record) + return record + + logging.setLogRecordFactory(record_factory) + + root = logging.getLogger() + root.setLevel(logging.DEBUG) + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.DEBUG) + handler.setFormatter( + fmt=logging.Formatter('[%(asctime)s.%(msecs)03d {}][%(levelname)s][%(message)s]'.format(tz_offset), + datefmt='%Y/%m/%d %H:%M:%S')) + root.addHandler(handler) + logger = root + + +init_logger() + + +def wrap_run_time(func): + def wrap_func(*args, **kwargs): + bg = time.time() + r = func(*args, **kwargs) + logger.debug('Time cost {:.3f}s'.format(time.time() - bg)) + return r + + return wrap_func + + +@wrap_run_time +def run_cmd(cmd): + logger.debug("RUN CMD:\n{}\n".format(' '.join(cmd))) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = proc.communicate() + # stderr.decode('utf-8') + return stdout, stderr, proc.returncode + + +class Runner: + def __init__(self): + usage = """ +1. compile TiFlash with cmake option `-DENABLE_LLVM_PGO=ON -DENABLE_LLVM_PGO_USE_SAMPLE=ON` +2. compile https://github.com/google/autofdo and get binary `create_llvm_prof` for converting perf data to llvm profile data +3. start TiFlash process and get `` +4. prepare workload scripts file +5. run `python3 perf-tpch.py --perf --pid --workload --convert-llvm --convert-tool --binary ` +6. get llvm perf file(`tiflash.llvm.code.prof` by default) +7. compile TiFlash with env `TIFLASH_LLVM_PROFDATA=` and cmake option `-DENABLE_LLVM_PGO=ON -DENABLE_LLVM_PGO_USE_SAMPLE=ON` +8. re-run workload and compare result +""" + parser = argparse.ArgumentParser( + description="Auto FDO tools", formatter_class=argparse.ArgumentDefaultsHelpFormatter, + usage=usage) + parser.add_argument( + '--perf', help='run perf with workload', action='store_true') + parser.add_argument( + '--convert-llvm', help='convert linux perf data to llvm profile data', action='store_true') + + parser.add_argument( + '--workload', help='absolute path of workload script', required=False) + parser.add_argument( + '--pid', help='pid of TiFlash process', required=False) + parser.add_argument( + '--output', help='output file of perf data', required=False) + parser.add_argument( + '--convert-tool', help='tool to conver linux perf data to llvm profile data',) + parser.add_argument( + '--input-perf-file', help='input linux perf data file path') + parser.add_argument( + '--binary', help='binary to run workload') + parser.add_argument( + '--output-llvm-prof', help='output llvm profile data path', default='tiflash.llvm.code.prof') + self.args = parser.parse_args() + self.linux_perf_data = None + + def run(self): + if self.args.perf: + self.run_perf() + if self.args.convert_llvm: + self.convert_llvm_perf() + + def convert_llvm_perf(self): + assert self.args.convert_tool + if self.linux_perf_data is None: + assert self.args.input_perf_file + else: + self.args.input_perf_file = self.linux_perf_data + + self.args.output_llvm_prof = 'tiflash.llvm.code.prof' + + assert self.args.binary + logger.info('start to convert linux perf data `{}` to llvm profile data `{}`'.format( + self.args.input_perf_file, self.args.output_llvm_prof)) + stdout, stderr, e = run_cmd([self.args.convert_tool, '--profile', '{}'.format(self.args.input_perf_file), + '--binary', "{}".format(self.args.binary), + '--out', '{}'.format(self.args.output_llvm_prof)]) + logger.info( + 'finish convert. stdout `{}`, stderr `{}`'.format(stdout.decode('utf-8'), stderr.decode('utf-8'))) + assert e == 0 + + def run_perf(self): + assert self.args.pid + assert self.args.workload + + pid = self.args.pid + output = 'tiflash.perf.data' if self.args.output is None else self.args.output + logger.info('using output file `{}`'.format(output)) + + def workload(): + # git clone git@github.com:pingcap/go-tpc.git + # cd go-tpc + # make build + # bin/go-tpc tpch run --queries q1 --host {} -P {} --db {} --count 1 + logger.info('start to run workload `{}`'.format( + self.args.workload)) + stdout, stderr, err = run_cmd([self.args.workload]) + logger.info('finish workload `{}`. stdout `{}`, stderr `{}`'.format( + self.args.workload, stdout.decode('utf-8'), stderr.decode('utf-8'))) + assert err == 0 + perf_cmd = ["perf", "record", "-p", "{}".format( + pid), "-e", "cycles:up", "-j", "any,u", "-a", "-o", "{}".format(output)] + logger.info("start perf with cmd `{}`".format(' '.join(perf_cmd))) + perf_proc = subprocess.Popen( + perf_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # + workload() + # + perf_proc.send_signal(signal.SIGTERM) + stdout, stderr = perf_proc.communicate() + logger.info( + "stop perf. stdout `{}`, stderr `{}`".format(stdout.decode('utf-8'), stderr.decode('utf-8'))) + _ = perf_proc.wait() + # check file exits + with open(output, 'r') as f: + f.close() + self.linux_perf_data = output + + +def main(): + Runner().run() + + +if __name__ == '__main__': + main() From 4e906f244fa7598d71e664fdb301aad67ce48e8e Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Fri, 1 Jul 2022 17:48:17 +0800 Subject: [PATCH 02/14] Fix name in script --- format-diff.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/format-diff.py b/format-diff.py index cf4fe793dca..f40863928d1 100755 --- a/format-diff.py +++ b/format-diff.py @@ -49,15 +49,15 @@ def main(): default_suffix = args.suffix.strip().split(' ') if args.suffix else [] ignore_suffix = args.ignore_suffix.strip().split( ' ') if args.ignore_suffix else [] - tics_repo_path = args.repo_path - if not os.path.isabs(tics_repo_path): + tiflash_repo_path = args.repo_path + if not os.path.isabs(tiflash_repo_path): raise Exception("path of repo should be absolute") - assert tics_repo_path[-1] != '/' + assert tiflash_repo_path[-1] != '/' - os.chdir(tics_repo_path) + os.chdir(tiflash_repo_path) files_to_check = run_cmd('git diff HEAD --name-only') if args.diff_from == 'HEAD' else run_cmd( 'git diff {} --name-only'.format(args.diff_from)) - files_to_check = [os.path.join(tics_repo_path, s.strip()) + files_to_check = [os.path.join(tiflash_repo_path, s.strip()) for s in files_to_check] files_to_format = [] for f in files_to_check: @@ -74,8 +74,8 @@ def main(): files_to_format.append(file_path) if args.dump_diff_files_to: - da = [e[len(tics_repo_path):] for e in files_to_format] - json.dump({'files': da, 'repo': tics_repo_path}, + da = [e[len(tiflash_repo_path):] for e in files_to_format] + json.dump({'files': da, 'repo': tiflash_repo_path}, open(args.dump_diff_files_to, 'w')) print('dump {} modified files info to {}'.format( len(da), args.dump_diff_files_to)) @@ -84,7 +84,7 @@ def main(): print('Files to format:\n {}'.format('\n '.join(files_to_format))) for file in files_to_format: cmd = 'clang-format -i {}'.format(file) - if subprocess.Popen(cmd, shell=True, cwd=tics_repo_path).wait(): + if subprocess.Popen(cmd, shell=True, cwd=tiflash_repo_path).wait(): exit(-1) if args.check_formatted: From 0c83b5a9fcff38c40220b68d48d3793377ba2fde Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 6 Jul 2022 15:02:06 +0800 Subject: [PATCH 03/14] Optimize comparision for UTF8_BIN and UTF8MB4_BIN --- dbms/src/Columns/ColumnConst.h | 3 +- .../Functions/CollationOperatorOptimized.h | 254 ++++++++++++++++++ dbms/src/Functions/FunctionsComparison.h | 63 ++++- dbms/src/Storages/Transaction/Collator.cpp | 12 +- 4 files changed, 317 insertions(+), 15 deletions(-) create mode 100644 dbms/src/Functions/CollationOperatorOptimized.h diff --git a/dbms/src/Columns/ColumnConst.h b/dbms/src/Columns/ColumnConst.h index 27283c0f24a..da071507a72 100644 --- a/dbms/src/Columns/ColumnConst.h +++ b/dbms/src/Columns/ColumnConst.h @@ -233,7 +233,8 @@ class ColumnConst final : public COWPtrHelper template T getValue() const { - return getField().safeGet::Type>(); + auto && tmp = getField(); + return std::move(tmp.safeGet::Type>()); } }; diff --git a/dbms/src/Functions/CollationOperatorOptimized.h b/dbms/src/Functions/CollationOperatorOptimized.h new file mode 100644 index 00000000000..f8dfe853e83 --- /dev/null +++ b/dbms/src/Functions/CollationOperatorOptimized.h @@ -0,0 +1,254 @@ +// Copyright 2022 PingCAP, Ltd. +// +// Licensed 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 +#include + +#include +#include + + +namespace DB +{ + +template +ALWAYS_INLINE inline int signum(T val) +{ + return (0 < val) - (val < 0); +} + +// Check equality is much faster than other comparison. +// - check size first +// - return 0 if equal else 1 +__attribute__((flatten, always_inline, pure)) inline uint8_t RawStrEqualCompare(const std::string_view & lhs, const std::string_view & rhs) +{ + return StringRef(lhs) == StringRef(rhs) ? 0 : 1; +} + +// Compare str view by memcmp +__attribute__((flatten, always_inline, pure)) inline int RawStrCompare(const std::string_view & v1, const std::string_view & v2) +{ + return signum(v1.compare(v2)); +} + +// Remove tail space +__attribute__((flatten, always_inline, pure)) inline std::string_view RightTrim(const std::string_view & v) +{ + size_t end = v.find_last_not_of(' '); + return end == std::string_view::npos ? "" : v.substr(0, end + 1); +} + +// If true, only need to check equal or not. +template +struct IsEqualRelated +{ + static constexpr const bool value = false; +}; + +// For `EqualsOp` and `NotEqualsOp`, value is true. +template +struct IsEqualRelated> +{ + static constexpr const bool value = true; +}; +template +struct IsEqualRelated> +{ + static constexpr const bool value = true; +}; + +// Return true if any str has tail space +__attribute__((always_inline, pure)) inline bool HasTailSpace( + const ColumnString::Chars_t & a_data, + const ColumnString::Offsets & a_offsets, + size_t size) +{ + bool has_tail_space = false; + + // #pragma clang loop vectorize(enable) + for (size_t i = 0; i < size; ++i) + { + const auto * ptr = reinterpret_cast(&a_data[StringUtil::offsetAt(a_offsets, i)]); + auto size = StringUtil::sizeAt(a_offsets, i) - 1; + auto pos = size > 0 ? size - 1 : 0; // if size is 0, use the last pos which aways contains a '\0' + has_tail_space |= ptr[pos] == ' '; + } + return has_tail_space; +} + + +// Loop columns and invoke callback for each pair. +template +__attribute__((flatten, always_inline)) inline void LoopTwoColumns( + const ColumnString::Chars_t & a_data, + const ColumnString::Offsets & a_offsets, + const ColumnString::Chars_t & b_data, + const ColumnString::Offsets & b_offsets, + size_t size, + F && func) +{ + for (size_t i = 0; i < size; ++i) + { + size_t a_size = StringUtil::sizeAt(a_offsets, i) - 1; + size_t b_size = StringUtil::sizeAt(b_offsets, i) - 1; + const auto * a_ptr = reinterpret_cast(&a_data[StringUtil::offsetAt(a_offsets, i)]); + const auto * b_ptr = reinterpret_cast(&b_data[StringUtil::offsetAt(b_offsets, i)]); + + func({a_ptr, a_size}, {b_ptr, b_size}, i); + } +} + +// Loop one column and invoke callback for each pair. +template +__attribute__((flatten, always_inline)) inline void LoopOneColumn( + const ColumnString::Chars_t & a_data, + const ColumnString::Offsets & a_offsets, + size_t size, + F && func) +{ + for (size_t i = 0; i < size; ++i) + { + size_t a_size = StringUtil::sizeAt(a_offsets, i) - 1; + const auto * a_ptr = reinterpret_cast(&a_data[StringUtil::offsetAt(a_offsets, i)]); + + func({a_ptr, a_size}, i); + } +} + +// Handle str-column compare str-column. +// - Optimize UTF8_BIN and UTF8MB4_BIN +// - Check if columns don NOT contains tail space +// - If Op is `EqualsOp` or `NotEqualsOp`, optimize comparison by faster way +template +ALWAYS_INLINE inline bool StringVectorStringVector( + const ColumnString::Chars_t & a_data, + const ColumnString::Offsets & a_offsets, + const ColumnString::Chars_t & b_data, + const ColumnString::Offsets & b_offsets, + const TiDB::TiDBCollatorPtr & collator, + Result & c) +{ + bool use_optimized_path = false; + + switch (collator->getCollatorId()) + { + case TiDB::ITiDBCollator::UTF8MB4_BIN: + case TiDB::ITiDBCollator::UTF8_BIN: + { + size_t size = a_offsets.size(); + + if (unlikely(HasTailSpace(a_data, a_offsets, size) || HasTailSpace(b_data, b_offsets, size))) + { + // if any col has any str with tail space, trim it ans compare + LoopTwoColumns(a_data, a_offsets, b_data, b_offsets, size, [&c](const std::string_view & va, const std::string_view & vb, size_t i) { + if constexpr (IsEqualRelated::value) + { + c[i] = Op::apply(RawStrEqualCompare(RightTrim(va), RightTrim(vb)), 0); + } + else + { + c[i] = Op::apply(RawStrCompare(RightTrim(va), RightTrim(vb)), 0); + } + }); + } + else + { + // in most case, string will not contain tail space + LoopTwoColumns(a_data, a_offsets, b_data, b_offsets, size, [&c](const std::string_view & va, const std::string_view & vb, size_t i) { + if constexpr (IsEqualRelated::value) + { + c[i] = Op::apply(RawStrEqualCompare(va, vb), 0); + } + else + { + c[i] = Op::apply(RawStrCompare(va, vb), 0); + } + }); + } + + use_optimized_path = true; + + break; + } + default: + break; + } + return use_optimized_path; +} + +// Handle str-column compare const-str. +// - Optimize UTF8_BIN and UTF8MB4_BIN +// - Right trim const-str first +// - Check if column don NOT contains tail space +// - If Op is `EqualsOp` or `NotEqualsOp`, optimize comparison by faster way +template +ALWAYS_INLINE inline bool StringVectorConstant( + const ColumnString::Chars_t & a_data, + const ColumnString::Offsets & a_offsets, + const std::string_view & b, + const TiDB::TiDBCollatorPtr & collator, + Result & c) +{ + bool use_optimized_path = false; + + switch (collator->getCollatorId()) + { + case TiDB::ITiDBCollator::UTF8MB4_BIN: + case TiDB::ITiDBCollator::UTF8_BIN: + { + size_t size = a_offsets.size(); + + std::string_view tar_str_view = RightTrim(b); // right trim const-str first + + if (likely(!HasTailSpace(a_data, a_offsets, size))) + { + LoopOneColumn(a_data, a_offsets, size, [&c, &tar_str_view](const std::string_view & view, size_t i) { + if constexpr (IsEqualRelated::value) + { + c[i] = Op::apply(RawStrEqualCompare(view, tar_str_view), 0); + } + else + { + c[i] = Op::apply(RawStrCompare(view, tar_str_view), 0); + } + }); + } + else + { + LoopOneColumn(a_data, a_offsets, size, [&c, &tar_str_view](const std::string_view & view, size_t i) { + if constexpr (IsEqualRelated::value) + { + c[i] = Op::apply(RawStrEqualCompare(RightTrim(view), tar_str_view), 0); + } + else + { + c[i] = Op::apply(RawStrCompare(RightTrim(view), tar_str_view), 0); + } + }); + } + use_optimized_path = true; + break; + } + default: + break; + } + return use_optimized_path; +} + +} // namespace DB diff --git a/dbms/src/Functions/FunctionsComparison.h b/dbms/src/Functions/FunctionsComparison.h index 1c63a286452..e93613cd077 100644 --- a/dbms/src/Functions/FunctionsComparison.h +++ b/dbms/src/Functions/FunctionsComparison.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -301,6 +302,12 @@ struct StringComparisonWithCollatorImpl const TiDB::TiDBCollatorPtr & collator, PaddedPODArray & c) { + bool optimized_path = StringVectorStringVector(a_data, a_offsets, b_data, b_offsets, collator, c); + if (optimized_path) + { + return; + } + size_t size = a_offsets.size(); for (size_t i = 0; i < size; ++i) @@ -317,10 +324,17 @@ struct StringComparisonWithCollatorImpl static void NO_INLINE stringVectorConstant( const ColumnString::Chars_t & a_data, const ColumnString::Offsets & a_offsets, - const std::string & b, + const std::string_view & b, const TiDB::TiDBCollatorPtr & collator, PaddedPODArray & c) { + bool optimized_path = StringVectorConstant(a_data, a_offsets, b, collator, c); + + if (optimized_path) + { + return; + } + size_t size = a_offsets.size(); ColumnString::Offset b_size = b.size(); const char * b_data = reinterpret_cast(b.data()); @@ -332,7 +346,7 @@ struct StringComparisonWithCollatorImpl } static void constantStringVector( - const std::string & a, + const std::string_view & a, const ColumnString::Chars_t & b_data, const ColumnString::Offsets & b_offsets, const TiDB::TiDBCollatorPtr & collator, @@ -342,8 +356,8 @@ struct StringComparisonWithCollatorImpl } static void constantConstant( - const std::string & a, - const std::string & b, + std::string_view a, + std::string_view b, const TiDB::TiDBCollatorPtr & collator, ResultType & c) { @@ -720,10 +734,41 @@ class FunctionComparison : public IFunction using ResultType = typename ResultColumnType::value_type; using StringImpl = StringComparisonWithCollatorImpl, ResultType>; + std::string_view c0_const_str_ref{}; + std::string_view c1_const_str_ref{}; + + if (c0_const) + { + if (const auto * c0_const_string = checkAndGetColumn(&c0_const->getDataColumn()); c0_const_string) + { + c0_const_str_ref = std::string_view(c0_const_string->getDataAt(0)); + } + else if (const auto * c0_const_fixed_string = checkAndGetColumn(&c0_const->getDataColumn()); c0_const_fixed_string) + { + c0_const_str_ref = std::string_view(c0_const_fixed_string->getDataAt(0)); + } + else + throw Exception("Logical error: ColumnConst contains not String nor FixedString column", ErrorCodes::ILLEGAL_COLUMN); + } + + if (c1_const) + { + if (const auto * c1_const_string = checkAndGetColumn(&c1_const->getDataColumn()); c1_const_string) + { + c1_const_str_ref = std::string_view(c1_const_string->getDataAt(0)); + } + else if (const auto * c1_const_fixed_string = checkAndGetColumn(&c0_const->getDataColumn()); c1_const_fixed_string) + { + c1_const_str_ref = std::string_view(c1_const_fixed_string->getDataAt(0)); + } + else + throw Exception("Logical error: ColumnConst contains not String nor FixedString column", ErrorCodes::ILLEGAL_COLUMN); + } + if (c0_const && c1_const) { ResultType res = 0; - StringImpl::constantConstant(c0_const->getValue(), c1_const->getValue(), collator, res); + StringImpl::constantConstant(c0_const_str_ref, c1_const_str_ref, collator, res); block.getByPosition(result).column = block.getByPosition(result).type->createColumnConst(c0_const->size(), toField(res)); return true; } @@ -745,12 +790,12 @@ class FunctionComparison : public IFunction StringImpl::stringVectorConstant( c0_string->getChars(), c0_string->getOffsets(), - c1_const->getValue(), + c1_const_str_ref, collator, c_res->getData()); else if (c0_const && c1_string) StringImpl::constantStringVector( - c0_const->getValue(), + c0_const_str_ref, c1_string->getChars(), c1_string->getOffsets(), collator, @@ -770,8 +815,8 @@ class FunctionComparison : public IFunction template bool executeString(Block & block, size_t result, const IColumn * c0, const IColumn * c1) const { - const ColumnString * c0_string = checkAndGetColumn(c0); - const ColumnString * c1_string = checkAndGetColumn(c1); + const auto * c0_string = checkAndGetColumn(c0); + const auto * c1_string = checkAndGetColumn(c1); const ColumnConst * c0_const = checkAndGetColumnConstStringOrFixedString(c0); const ColumnConst * c1_const = checkAndGetColumnConstStringOrFixedString(c1); diff --git a/dbms/src/Storages/Transaction/Collator.cpp b/dbms/src/Storages/Transaction/Collator.cpp index a9b4d0784be..1e271f41999 100644 --- a/dbms/src/Storages/Transaction/Collator.cpp +++ b/dbms/src/Storages/Transaction/Collator.cpp @@ -183,7 +183,7 @@ class Pattern : public ITiDBCollator::IPattern }; template -class BinCollator : public ITiDBCollator +class BinCollator final : public ITiDBCollator { public: explicit BinCollator(int32_t id) @@ -249,7 +249,7 @@ using WeightType = uint16_t; extern const std::array weight_lut; } // namespace GeneralCI -class GeneralCICollator : public ITiDBCollator +class GeneralCICollator final : public ITiDBCollator { public: explicit GeneralCICollator(int32_t id) @@ -365,7 +365,7 @@ const std::array weight_lut_long = { } // namespace UnicodeCI -class UnicodeCICollator : public ITiDBCollator +class UnicodeCICollator final : public ITiDBCollator { public: explicit UnicodeCICollator(int32_t id) @@ -593,6 +593,8 @@ class UnicodeCICollator : public ITiDBCollator friend class Pattern; }; +using UTF8MB4_BIN_TYPE = BinCollator; + TiDBCollatorPtr ITiDBCollator::getCollator(int32_t id) { switch (id) @@ -607,10 +609,10 @@ TiDBCollatorPtr ITiDBCollator::getCollator(int32_t id) static const auto latin1_collator = BinCollator(LATIN1_BIN); return &latin1_collator; case ITiDBCollator::UTF8MB4_BIN: - static const auto utf8mb4_collator = BinCollator(UTF8MB4_BIN); + static const auto utf8mb4_collator = UTF8MB4_BIN_TYPE(UTF8MB4_BIN); return &utf8mb4_collator; case ITiDBCollator::UTF8_BIN: - static const auto utf8_collator = BinCollator(UTF8_BIN); + static const auto utf8_collator = UTF8MB4_BIN_TYPE(UTF8_BIN); return &utf8_collator; case ITiDBCollator::UTF8_GENERAL_CI: static const auto utf8_general_ci_collator = GeneralCICollator(UTF8_GENERAL_CI); From a270deed3b798d867ffd734ac767fde7fe76351d Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 6 Jul 2022 15:05:49 +0800 Subject: [PATCH 04/14] Revert "Fix name in script" This reverts commit 4e906f244fa7598d71e664fdb301aad67ce48e8e. --- format-diff.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/format-diff.py b/format-diff.py index f40863928d1..cf4fe793dca 100755 --- a/format-diff.py +++ b/format-diff.py @@ -49,15 +49,15 @@ def main(): default_suffix = args.suffix.strip().split(' ') if args.suffix else [] ignore_suffix = args.ignore_suffix.strip().split( ' ') if args.ignore_suffix else [] - tiflash_repo_path = args.repo_path - if not os.path.isabs(tiflash_repo_path): + tics_repo_path = args.repo_path + if not os.path.isabs(tics_repo_path): raise Exception("path of repo should be absolute") - assert tiflash_repo_path[-1] != '/' + assert tics_repo_path[-1] != '/' - os.chdir(tiflash_repo_path) + os.chdir(tics_repo_path) files_to_check = run_cmd('git diff HEAD --name-only') if args.diff_from == 'HEAD' else run_cmd( 'git diff {} --name-only'.format(args.diff_from)) - files_to_check = [os.path.join(tiflash_repo_path, s.strip()) + files_to_check = [os.path.join(tics_repo_path, s.strip()) for s in files_to_check] files_to_format = [] for f in files_to_check: @@ -74,8 +74,8 @@ def main(): files_to_format.append(file_path) if args.dump_diff_files_to: - da = [e[len(tiflash_repo_path):] for e in files_to_format] - json.dump({'files': da, 'repo': tiflash_repo_path}, + da = [e[len(tics_repo_path):] for e in files_to_format] + json.dump({'files': da, 'repo': tics_repo_path}, open(args.dump_diff_files_to, 'w')) print('dump {} modified files info to {}'.format( len(da), args.dump_diff_files_to)) @@ -84,7 +84,7 @@ def main(): print('Files to format:\n {}'.format('\n '.join(files_to_format))) for file in files_to_format: cmd = 'clang-format -i {}'.format(file) - if subprocess.Popen(cmd, shell=True, cwd=tiflash_repo_path).wait(): + if subprocess.Popen(cmd, shell=True, cwd=tics_repo_path).wait(): exit(-1) if args.check_formatted: From bf761ed4382f3669f3f04daf5d837a220cc2da13 Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 6 Jul 2022 15:05:58 +0800 Subject: [PATCH 05/14] Revert "Add options to support `Profile Guided Optimization`" This reverts commit b35b586f1053af248b1a8ce8293403924b08f8cc. --- CMakeLists.txt | 52 ----- dbms/src/Common/TiFlashBuildInfo.cpp | 17 -- format-diff.py | 3 + .../include/common/config_common.h.in | 4 - release-centos7-llvm/env/prepare-sysroot.sh | 1 - release-centos7-llvm/scripts/perf-tpch.py | 211 ------------------ 6 files changed, 3 insertions(+), 285 deletions(-) delete mode 100755 release-centos7-llvm/scripts/perf-tpch.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 98eca3e3ef4..4e14c205f18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,48 +134,6 @@ if (COMPILER_CLANG) endif () endif () -option (ENABLE_LLVM_PROFILE_INSTR "Generate instrumented code to collect execution counts" OFF) -option (ENABLE_LLVM_PGO "Enables flags for Profile Guided Optimization (PGO)" OFF) -option (ENABLE_LLVM_PGO_USE_SAMPLE "Enables flags for Profile Guided Optimization (PGO) and use sampling profilers" OFF) -set (USE_LLVM_FDO OFF CACHE BOOL "" FORCE) - -if (ENABLE_LLVM_PGO) - if (ENABLE_LLVM_PROFILE_INSTR) - message (FATAL_ERROR "`ENABLE_LLVM_PROFILE_INSTR` can not be used with `ENABLE_LLVM_PGO`") - endif () - if (ENABLE_LLVM_PGO_USE_SAMPLE) - - # Follow https://clang.llvm.org/docs/UsersManual.html#using-sampling-profilers - # Use https://github.com/google/autofdo - - set (_LLVM_PGO_USE_SAMPLE_FLAGS "-gline-tables-only -fdebug-info-for-profiling -funique-internal-linkage-names") - - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${_LLVM_PGO_USE_SAMPLE_FLAGS}") - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_LLVM_PGO_USE_SAMPLE_FLAGS}") - set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-rosegment") - message (STATUS "Add flags `${_LLVM_PGO_USE_SAMPLE_FLAGS}` for profiling") - - if (NOT "$ENV{TIFLASH_LLVM_PROFDATA}" STREQUAL "") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-sample-use=$ENV{TIFLASH_LLVM_PROFDATA}") - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-sample-use=$ENV{TIFLASH_LLVM_PROFDATA}") - message (STATUS "Use sample profile data `$ENV{TIFLASH_LLVM_PROFDATA}` for profile-guided optimization") - set (USE_LLVM_FDO ON CACHE BOOL "" FORCE) - else () - message (STATUS "NOT use sample profile data") - endif () - - unset (_LLVM_PGO_USE_SAMPLE_FLAGS) - else () - if ("$ENV{TIFLASH_LLVM_PROFDATA}" STREQUAL "") - message (FATAL_ERROR "Please set env var `TIFLASH_LLVM_PROFDATA`") - endif () - - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-use=$ENV{TIFLASH_LLVM_PROFDATA} -Wno-profile-instr-unprofiled") - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-instr-use=$ENV{TIFLASH_LLVM_PROFDATA} -Wno-profile-instr-unprofiled") - message (STATUS "Use instrumentation data `$ENV{TIFLASH_LLVM_PROFDATA}` for profile-guided optimization") - endif () -endif () - if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # clang: warning: argument unused during compilation: '-stdlib=libc++' # clang: warning: argument unused during compilation: '-specs=/usr/share/dpkg/no-pie-compile.specs' [-Wunused-command-line-argument] @@ -490,16 +448,6 @@ if (TEST_LLVM_COVERAGE AND CMAKE_BUILD_TYPE_UC STREQUAL "DEBUG") set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-instr-generate -fcoverage-mapping -DTIFLASH_LLVM_COVERAGE=1") endif () -# `ENABLE_LLVM_PROFILE_INSTR` will make executable binary generate profile data automatically. Make it only work at modules dbms and libs. -if (ENABLE_LLVM_PROFILE_INSTR) - if (ENABLE_LLVM_PGO) - message (FATAL_ERROR "`ENABLE_LLVM_PROFILE_INSTR` can not be used with `ENABLE_LLVM_PGO`") - endif () - message (STATUS "Using flag `-fprofile-instr-generate`. Generate instrumented code to collect execution counts into default.profraw file(overridden by '=' form of option or `LLVM_PROFILE_FILE` env var). Follow https://clang.llvm.org/docs/UsersManual.html#profile-guided-optimization.") - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate") - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-instr-generate") -endif () - if (ARCH_AMD64) include(CheckCXXCompilerFlag) check_cxx_compiler_flag("-mvpclmulqdq -Werror -Wall -Wextra" TIFLASH_COMPILER_VPCLMULQDQ_SUPPORT) diff --git a/dbms/src/Common/TiFlashBuildInfo.cpp b/dbms/src/Common/TiFlashBuildInfo.cpp index f2353256b9b..ff46e04384d 100644 --- a/dbms/src/Common/TiFlashBuildInfo.cpp +++ b/dbms/src/Common/TiFlashBuildInfo.cpp @@ -101,23 +101,6 @@ std::string getEnabledFeatures() #if ENABLE_THINLTO "thinlto", #endif - -// Profile instrumentation -#if ENABLE_LLVM_PROFILE_INSTR - "profile-instr", -#endif - -// PGO -#if ENABLE_LLVM_PGO_USE_SAMPLE - "pgo-sample", -#elif ENABLE_LLVM_PGO - "pgo-instr", -#endif - -// FDO -#if USE_LLVM_FDO - "fdo", -#endif }; return fmt::format("{}", fmt::join(features.begin(), features.end(), " ")); } diff --git a/format-diff.py b/format-diff.py index cf4fe793dca..c8d12925fb3 100755 --- a/format-diff.py +++ b/format-diff.py @@ -96,6 +96,9 @@ def main(): else: print("Format check passed") else: + cmd = 'clang-format -i {}'.format(' '.join(files_to_format)) + if subprocess.Popen(cmd, shell=True, cwd=tics_repo_path).wait(): + exit(-1) print("Finish code format") else: print('No file to format') diff --git a/libs/libcommon/include/common/config_common.h.in b/libs/libcommon/include/common/config_common.h.in index 0ec263216f4..46f167ea683 100644 --- a/libs/libcommon/include/common/config_common.h.in +++ b/libs/libcommon/include/common/config_common.h.in @@ -14,7 +14,3 @@ #cmakedefine01 USE_UNWIND #cmakedefine01 USE_LLVM_LIBUNWIND #cmakedefine01 ENABLE_THINLTO -#cmakedefine01 ENABLE_LLVM_PGO -#cmakedefine01 ENABLE_LLVM_PROFILE_INSTR -#cmakedefine01 ENABLE_LLVM_PGO_USE_SAMPLE -#cmakedefine01 USE_LLVM_FDO diff --git a/release-centos7-llvm/env/prepare-sysroot.sh b/release-centos7-llvm/env/prepare-sysroot.sh index 1134bb37ee7..e4132eae667 100755 --- a/release-centos7-llvm/env/prepare-sysroot.sh +++ b/release-centos7-llvm/env/prepare-sysroot.sh @@ -37,7 +37,6 @@ function install_llvm() { mkdir -p llvm-project/build cd llvm-project/build - # TODO: enable `bolt` for >= 14.0.0. https://github.com/llvm/llvm-project/tree/main/bolt cmake -DCMAKE_BUILD_TYPE=Release \ -GNinja \ -DLLVM_ENABLE_PROJECTS="clang;lld;polly;clang-tools-extra" \ diff --git a/release-centos7-llvm/scripts/perf-tpch.py b/release-centos7-llvm/scripts/perf-tpch.py deleted file mode 100755 index 2af518ab2f3..00000000000 --- a/release-centos7-llvm/scripts/perf-tpch.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/python3 -# Copyright 2022 PingCAP, Ltd. -# -# Licensed 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. - -import argparse -import signal -import sys -import time -import logging -import types -import subprocess - -logger = None - - -def get_tz_offset(): - import datetime - now_stamp = time.time() - local_time = datetime.datetime.fromtimestamp(now_stamp) - utc_time = datetime.datetime.utcfromtimestamp(now_stamp) - offset = local_time - utc_time - total_seconds = offset.total_seconds() - flag = '+' - if total_seconds < 0: - flag = '-' - total_seconds = -total_seconds - mm, ss = divmod(total_seconds, 60) - hh, mm = divmod(mm, 60) - tz_offset = "%s%02d:%02d" % (flag, hh, mm) - return tz_offset - - -def init_logger(): - global logger - - tz_offset = get_tz_offset() - - orig_record_factory = logging.getLogRecordFactory() - log_colors = { - logging.DEBUG: "\033[1;34m", # blue - logging.INFO: "\033[1;32m", # green - logging.WARNING: "\033[1;35m", # magenta - logging.ERROR: "\033[1;31m", # red - logging.CRITICAL: "\033[1;41m", # red reverted - } - - def get_message(ori): - msg = str(ori.msg) - if ori.args: - msg = msg % ori.args - msg = "{}{}{}".format(log_colors[ori.levelno], msg, "\033[0m") - return msg - - def record_factory(*args, **kwargs): - record = orig_record_factory(*args, **kwargs) - record.getMessage = types.MethodType(get_message, record) - return record - - logging.setLogRecordFactory(record_factory) - - root = logging.getLogger() - root.setLevel(logging.DEBUG) - handler = logging.StreamHandler(sys.stdout) - handler.setLevel(logging.DEBUG) - handler.setFormatter( - fmt=logging.Formatter('[%(asctime)s.%(msecs)03d {}][%(levelname)s][%(message)s]'.format(tz_offset), - datefmt='%Y/%m/%d %H:%M:%S')) - root.addHandler(handler) - logger = root - - -init_logger() - - -def wrap_run_time(func): - def wrap_func(*args, **kwargs): - bg = time.time() - r = func(*args, **kwargs) - logger.debug('Time cost {:.3f}s'.format(time.time() - bg)) - return r - - return wrap_func - - -@wrap_run_time -def run_cmd(cmd): - logger.debug("RUN CMD:\n{}\n".format(' '.join(cmd))) - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - # stderr.decode('utf-8') - return stdout, stderr, proc.returncode - - -class Runner: - def __init__(self): - usage = """ -1. compile TiFlash with cmake option `-DENABLE_LLVM_PGO=ON -DENABLE_LLVM_PGO_USE_SAMPLE=ON` -2. compile https://github.com/google/autofdo and get binary `create_llvm_prof` for converting perf data to llvm profile data -3. start TiFlash process and get `` -4. prepare workload scripts file -5. run `python3 perf-tpch.py --perf --pid --workload --convert-llvm --convert-tool --binary ` -6. get llvm perf file(`tiflash.llvm.code.prof` by default) -7. compile TiFlash with env `TIFLASH_LLVM_PROFDATA=` and cmake option `-DENABLE_LLVM_PGO=ON -DENABLE_LLVM_PGO_USE_SAMPLE=ON` -8. re-run workload and compare result -""" - parser = argparse.ArgumentParser( - description="Auto FDO tools", formatter_class=argparse.ArgumentDefaultsHelpFormatter, - usage=usage) - parser.add_argument( - '--perf', help='run perf with workload', action='store_true') - parser.add_argument( - '--convert-llvm', help='convert linux perf data to llvm profile data', action='store_true') - - parser.add_argument( - '--workload', help='absolute path of workload script', required=False) - parser.add_argument( - '--pid', help='pid of TiFlash process', required=False) - parser.add_argument( - '--output', help='output file of perf data', required=False) - parser.add_argument( - '--convert-tool', help='tool to conver linux perf data to llvm profile data',) - parser.add_argument( - '--input-perf-file', help='input linux perf data file path') - parser.add_argument( - '--binary', help='binary to run workload') - parser.add_argument( - '--output-llvm-prof', help='output llvm profile data path', default='tiflash.llvm.code.prof') - self.args = parser.parse_args() - self.linux_perf_data = None - - def run(self): - if self.args.perf: - self.run_perf() - if self.args.convert_llvm: - self.convert_llvm_perf() - - def convert_llvm_perf(self): - assert self.args.convert_tool - if self.linux_perf_data is None: - assert self.args.input_perf_file - else: - self.args.input_perf_file = self.linux_perf_data - - self.args.output_llvm_prof = 'tiflash.llvm.code.prof' - - assert self.args.binary - logger.info('start to convert linux perf data `{}` to llvm profile data `{}`'.format( - self.args.input_perf_file, self.args.output_llvm_prof)) - stdout, stderr, e = run_cmd([self.args.convert_tool, '--profile', '{}'.format(self.args.input_perf_file), - '--binary', "{}".format(self.args.binary), - '--out', '{}'.format(self.args.output_llvm_prof)]) - logger.info( - 'finish convert. stdout `{}`, stderr `{}`'.format(stdout.decode('utf-8'), stderr.decode('utf-8'))) - assert e == 0 - - def run_perf(self): - assert self.args.pid - assert self.args.workload - - pid = self.args.pid - output = 'tiflash.perf.data' if self.args.output is None else self.args.output - logger.info('using output file `{}`'.format(output)) - - def workload(): - # git clone git@github.com:pingcap/go-tpc.git - # cd go-tpc - # make build - # bin/go-tpc tpch run --queries q1 --host {} -P {} --db {} --count 1 - logger.info('start to run workload `{}`'.format( - self.args.workload)) - stdout, stderr, err = run_cmd([self.args.workload]) - logger.info('finish workload `{}`. stdout `{}`, stderr `{}`'.format( - self.args.workload, stdout.decode('utf-8'), stderr.decode('utf-8'))) - assert err == 0 - perf_cmd = ["perf", "record", "-p", "{}".format( - pid), "-e", "cycles:up", "-j", "any,u", "-a", "-o", "{}".format(output)] - logger.info("start perf with cmd `{}`".format(' '.join(perf_cmd))) - perf_proc = subprocess.Popen( - perf_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - # - workload() - # - perf_proc.send_signal(signal.SIGTERM) - stdout, stderr = perf_proc.communicate() - logger.info( - "stop perf. stdout `{}`, stderr `{}`".format(stdout.decode('utf-8'), stderr.decode('utf-8'))) - _ = perf_proc.wait() - # check file exits - with open(output, 'r') as f: - f.close() - self.linux_perf_data = output - - -def main(): - Runner().run() - - -if __name__ == '__main__': - main() From 9e405daa9652ea2c46f6a4f27f3b3f3e3faea1bc Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 6 Jul 2022 15:37:33 +0800 Subject: [PATCH 06/14] Format code --- .../Functions/CollationOperatorOptimized.h | 7 ++++++- dbms/src/Storages/Transaction/Collator.cpp | 20 +++++++------------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/dbms/src/Functions/CollationOperatorOptimized.h b/dbms/src/Functions/CollationOperatorOptimized.h index f8dfe853e83..27a89890c51 100644 --- a/dbms/src/Functions/CollationOperatorOptimized.h +++ b/dbms/src/Functions/CollationOperatorOptimized.h @@ -54,6 +54,11 @@ __attribute__((flatten, always_inline, pure)) inline std::string_view RightTrim( return end == std::string_view::npos ? "" : v.substr(0, end + 1); } +__attribute__((flatten, always_inline, pure)) inline int RtrimeStrCompare(const std::string_view & va, const std::string_view & vb) +{ + return RawStrCompare(RightTrim(va), RightTrim(vb)); +} + // If true, only need to check equal or not. template struct IsEqualRelated @@ -163,7 +168,7 @@ ALWAYS_INLINE inline bool StringVectorStringVector( } else { - c[i] = Op::apply(RawStrCompare(RightTrim(va), RightTrim(vb)), 0); + c[i] = Op::apply(RtrimeStrCompare(va, vb), 0); } }); } diff --git a/dbms/src/Storages/Transaction/Collator.cpp b/dbms/src/Storages/Transaction/Collator.cpp index 1e271f41999..eac0a144ad7 100644 --- a/dbms/src/Storages/Transaction/Collator.cpp +++ b/dbms/src/Storages/Transaction/Collator.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include @@ -29,17 +30,10 @@ TiDBCollators dummy_collators; std::vector dummy_sort_key_contaners; std::string dummy_sort_key_contaner; -std::string_view rtrim(const char * s, size_t length) +ALWAYS_INLINE std::string_view rtrim(const char * s, size_t length) { auto v = std::string_view(s, length); - size_t end = v.find_last_not_of(' '); - return end == std::string_view::npos ? "" : v.substr(0, end + 1); -} - -template -int signum(T val) -{ - return (0 < val) - (val < 0); + return DB::RightTrim(v); } using Rune = int32_t; @@ -192,9 +186,9 @@ class BinCollator final : public ITiDBCollator int compare(const char * s1, size_t length1, const char * s2, size_t length2) const override { if constexpr (padding) - return signum(rtrim(s1, length1).compare(rtrim(s2, length2))); + return DB::RtrimeStrCompare({s1, length1}, {s2, length2}); else - return signum(std::string_view(s1, length1).compare(std::string_view(s2, length2))); + return DB::RawStrCompare({s1, length1}, {s2, length2}); } StringRef sortKey(const char * s, size_t length, std::string &) const override @@ -270,7 +264,7 @@ class GeneralCICollator final : public ITiDBCollator auto sk2 = weight(c2); auto cmp = sk1 - sk2; if (cmp != 0) - return signum(cmp); + return DB::signum(cmp); } return (offset1 < v1.length()) - (offset2 < v2.length()); @@ -420,7 +414,7 @@ class UnicodeCICollator final : public ITiDBCollator } else { - return signum(static_cast(s1_first & 0xFFFF) - static_cast(s2_first & 0xFFFF)); + return DB::signum(static_cast(s1_first & 0xFFFF) - static_cast(s2_first & 0xFFFF)); } } } From a2103ac9a994ca0c4fcc2dd576793735805bcb88 Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 6 Jul 2022 16:13:47 +0800 Subject: [PATCH 07/14] Add more test cases --- tests/tidb-ci/new_collation_fullstack/expr.test | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/tidb-ci/new_collation_fullstack/expr.test b/tests/tidb-ci/new_collation_fullstack/expr.test index 15ada0f335c..1e2135c4f2d 100644 --- a/tests/tidb-ci/new_collation_fullstack/expr.test +++ b/tests/tidb-ci/new_collation_fullstack/expr.test @@ -35,6 +35,13 @@ mysql> set session tidb_isolation_read_engines='tiflash'; select /*+ read_from_s | 2 | abc | +------+-------+ +mysql> set session tidb_isolation_read_engines='tiflash'; select /*+ read_from_storage(tiflash[t]) */ id, value1 from test.t where value1 = 'abc '; ++------+-------+ +| id | value1| ++------+-------+ +| 1 | abc | +| 2 | abc | ++------+-------+ mysql> set session tidb_isolation_read_engines='tiflash'; select /*+ read_from_storage(tiflash[t]) */ id, value from test.t where value like 'aB%'; +------+-------+ @@ -62,6 +69,13 @@ mysql> set session tidb_isolation_read_engines='tiflash'; select /*+ read_from_s | 3 | def | +------+-------+ +mysql> set session tidb_isolation_read_engines='tiflash'; select /*+ read_from_storage(tiflash[t]) */ id, value1 from test.t where value1 = 'def '; ++------+-------+ +| id | value1| ++------+-------+ +| 3 | def | ++------+-------+ + mysql> set session tidb_isolation_read_engines='tiflash'; select /*+ read_from_storage(tiflash[t]) */ id, value1 from test.t where value1 in ('Abc','def'); +------+-------+ | id | value1| From d5dc1b0eced92f188de09a62f2334c51db225b6f Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Wed, 6 Jul 2022 21:34:23 +0800 Subject: [PATCH 08/14] optimize code --- .../Functions/CollationOperatorOptimized.h | 103 +++++------------- 1 file changed, 27 insertions(+), 76 deletions(-) diff --git a/dbms/src/Functions/CollationOperatorOptimized.h b/dbms/src/Functions/CollationOperatorOptimized.h index 27a89890c51..4306c5dfa1d 100644 --- a/dbms/src/Functions/CollationOperatorOptimized.h +++ b/dbms/src/Functions/CollationOperatorOptimized.h @@ -47,11 +47,15 @@ __attribute__((flatten, always_inline, pure)) inline int RawStrCompare(const std return signum(v1.compare(v2)); } +constexpr char SPACE = ' '; + // Remove tail space __attribute__((flatten, always_inline, pure)) inline std::string_view RightTrim(const std::string_view & v) { - size_t end = v.find_last_not_of(' '); - return end == std::string_view::npos ? "" : v.substr(0, end + 1); + if (likely(v.empty() || v.back() != SPACE)) + return v; + size_t end = v.find_last_not_of(SPACE); + return end == std::string_view::npos ? std::string_view{} : std::string_view(v.data(), end + 1); } __attribute__((flatten, always_inline, pure)) inline int RtrimeStrCompare(const std::string_view & va, const std::string_view & vb) @@ -78,26 +82,6 @@ struct IsEqualRelated> static constexpr const bool value = true; }; -// Return true if any str has tail space -__attribute__((always_inline, pure)) inline bool HasTailSpace( - const ColumnString::Chars_t & a_data, - const ColumnString::Offsets & a_offsets, - size_t size) -{ - bool has_tail_space = false; - - // #pragma clang loop vectorize(enable) - for (size_t i = 0; i < size; ++i) - { - const auto * ptr = reinterpret_cast(&a_data[StringUtil::offsetAt(a_offsets, i)]); - auto size = StringUtil::sizeAt(a_offsets, i) - 1; - auto pos = size > 0 ? size - 1 : 0; // if size is 0, use the last pos which aways contains a '\0' - has_tail_space |= ptr[pos] == ' '; - } - return has_tail_space; -} - - // Loop columns and invoke callback for each pair. template __attribute__((flatten, always_inline)) inline void LoopTwoColumns( @@ -158,34 +142,16 @@ ALWAYS_INLINE inline bool StringVectorStringVector( { size_t size = a_offsets.size(); - if (unlikely(HasTailSpace(a_data, a_offsets, size) || HasTailSpace(b_data, b_offsets, size))) - { - // if any col has any str with tail space, trim it ans compare - LoopTwoColumns(a_data, a_offsets, b_data, b_offsets, size, [&c](const std::string_view & va, const std::string_view & vb, size_t i) { - if constexpr (IsEqualRelated::value) - { - c[i] = Op::apply(RawStrEqualCompare(RightTrim(va), RightTrim(vb)), 0); - } - else - { - c[i] = Op::apply(RtrimeStrCompare(va, vb), 0); - } - }); - } - else - { - // in most case, string will not contain tail space - LoopTwoColumns(a_data, a_offsets, b_data, b_offsets, size, [&c](const std::string_view & va, const std::string_view & vb, size_t i) { - if constexpr (IsEqualRelated::value) - { - c[i] = Op::apply(RawStrEqualCompare(va, vb), 0); - } - else - { - c[i] = Op::apply(RawStrCompare(va, vb), 0); - } - }); - } + LoopTwoColumns(a_data, a_offsets, b_data, b_offsets, size, [&c](const std::string_view & va, const std::string_view & vb, size_t i) { + if constexpr (IsEqualRelated::value) + { + c[i] = Op::apply(RawStrEqualCompare(RightTrim(va), RightTrim(vb)), 0); + } + else + { + c[i] = Op::apply(RtrimeStrCompare(va, vb), 0); + } + }); use_optimized_path = true; @@ -221,32 +187,17 @@ ALWAYS_INLINE inline bool StringVectorConstant( std::string_view tar_str_view = RightTrim(b); // right trim const-str first - if (likely(!HasTailSpace(a_data, a_offsets, size))) - { - LoopOneColumn(a_data, a_offsets, size, [&c, &tar_str_view](const std::string_view & view, size_t i) { - if constexpr (IsEqualRelated::value) - { - c[i] = Op::apply(RawStrEqualCompare(view, tar_str_view), 0); - } - else - { - c[i] = Op::apply(RawStrCompare(view, tar_str_view), 0); - } - }); - } - else - { - LoopOneColumn(a_data, a_offsets, size, [&c, &tar_str_view](const std::string_view & view, size_t i) { - if constexpr (IsEqualRelated::value) - { - c[i] = Op::apply(RawStrEqualCompare(RightTrim(view), tar_str_view), 0); - } - else - { - c[i] = Op::apply(RawStrCompare(RightTrim(view), tar_str_view), 0); - } - }); - } + LoopOneColumn(a_data, a_offsets, size, [&c, &tar_str_view](const std::string_view & view, size_t i) { + if constexpr (IsEqualRelated::value) + { + c[i] = Op::apply(RawStrEqualCompare(RightTrim(view), tar_str_view), 0); + } + else + { + c[i] = Op::apply(RawStrCompare(RightTrim(view), tar_str_view), 0); + } + }); + use_optimized_path = true; break; } From bd4e49a5872aa20d644352c8f00753bec0c84acc Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 7 Jul 2022 12:30:57 +0800 Subject: [PATCH 09/14] Fix typo --- dbms/src/Functions/CollationOperatorOptimized.h | 4 ++-- dbms/src/Storages/Transaction/Collator.cpp | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dbms/src/Functions/CollationOperatorOptimized.h b/dbms/src/Functions/CollationOperatorOptimized.h index 4306c5dfa1d..6303b703709 100644 --- a/dbms/src/Functions/CollationOperatorOptimized.h +++ b/dbms/src/Functions/CollationOperatorOptimized.h @@ -58,7 +58,7 @@ __attribute__((flatten, always_inline, pure)) inline std::string_view RightTrim( return end == std::string_view::npos ? std::string_view{} : std::string_view(v.data(), end + 1); } -__attribute__((flatten, always_inline, pure)) inline int RtrimeStrCompare(const std::string_view & va, const std::string_view & vb) +__attribute__((flatten, always_inline, pure)) inline int RtrimStrCompare(const std::string_view & va, const std::string_view & vb) { return RawStrCompare(RightTrim(va), RightTrim(vb)); } @@ -149,7 +149,7 @@ ALWAYS_INLINE inline bool StringVectorStringVector( } else { - c[i] = Op::apply(RtrimeStrCompare(va, vb), 0); + c[i] = Op::apply(RtrimStrCompare(va, vb), 0); } }); diff --git a/dbms/src/Storages/Transaction/Collator.cpp b/dbms/src/Storages/Transaction/Collator.cpp index eac0a144ad7..1b0221a6829 100644 --- a/dbms/src/Storages/Transaction/Collator.cpp +++ b/dbms/src/Storages/Transaction/Collator.cpp @@ -183,10 +183,11 @@ class BinCollator final : public ITiDBCollator explicit BinCollator(int32_t id) : ITiDBCollator(id) {} + int compare(const char * s1, size_t length1, const char * s2, size_t length2) const override { if constexpr (padding) - return DB::RtrimeStrCompare({s1, length1}, {s2, length2}); + return DB::RtrimStrCompare({s1, length1}, {s2, length2}); else return DB::RawStrCompare({s1, length1}, {s2, length2}); } @@ -195,8 +196,7 @@ class BinCollator final : public ITiDBCollator { if constexpr (padding) { - auto v = rtrim(s, length); - return StringRef(v.data(), v.length()); + return StringRef(rtrim(s, length)); } else { From a8e0ed24e195decea06312b606a40d6b6e5e090b Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 7 Jul 2022 16:40:57 +0800 Subject: [PATCH 10/14] Fix bug --- dbms/src/Functions/FunctionsComparison.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/Functions/FunctionsComparison.h b/dbms/src/Functions/FunctionsComparison.h index e93613cd077..6719cc5d5b8 100644 --- a/dbms/src/Functions/FunctionsComparison.h +++ b/dbms/src/Functions/FunctionsComparison.h @@ -757,7 +757,7 @@ class FunctionComparison : public IFunction { c1_const_str_ref = std::string_view(c1_const_string->getDataAt(0)); } - else if (const auto * c1_const_fixed_string = checkAndGetColumn(&c0_const->getDataColumn()); c1_const_fixed_string) + else if (const auto * c1_const_fixed_string = checkAndGetColumn(&c1_const->getDataColumn()); c1_const_fixed_string) { c1_const_str_ref = std::string_view(c1_const_fixed_string->getDataAt(0)); } From b85741c14767999349f20ee1a27c78aa77ab6d9a Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 7 Jul 2022 17:05:03 +0800 Subject: [PATCH 11/14] Address comment - extract method --- dbms/src/Functions/FunctionsComparison.h | 47 ++++++++++-------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/dbms/src/Functions/FunctionsComparison.h b/dbms/src/Functions/FunctionsComparison.h index 6719cc5d5b8..69f50f8482a 100644 --- a/dbms/src/Functions/FunctionsComparison.h +++ b/dbms/src/Functions/FunctionsComparison.h @@ -720,23 +720,9 @@ class FunctionComparison : public IFunction } } - template - bool executeStringWithCollator( - Block & block, - size_t result, - const IColumn * c0, - const IColumn * c1, - const ColumnString * c0_string, - const ColumnString * c1_string, - const ColumnConst * c0_const, - const ColumnConst * c1_const) const + static inline std::string_view genConstStrRef(const ColumnConst * c0_const) { - using ResultType = typename ResultColumnType::value_type; - using StringImpl = StringComparisonWithCollatorImpl, ResultType>; - std::string_view c0_const_str_ref{}; - std::string_view c1_const_str_ref{}; - if (c0_const) { if (const auto * c0_const_string = checkAndGetColumn(&c0_const->getDataColumn()); c0_const_string) @@ -750,20 +736,25 @@ class FunctionComparison : public IFunction else throw Exception("Logical error: ColumnConst contains not String nor FixedString column", ErrorCodes::ILLEGAL_COLUMN); } + return c0_const_str_ref; + } - if (c1_const) - { - if (const auto * c1_const_string = checkAndGetColumn(&c1_const->getDataColumn()); c1_const_string) - { - c1_const_str_ref = std::string_view(c1_const_string->getDataAt(0)); - } - else if (const auto * c1_const_fixed_string = checkAndGetColumn(&c1_const->getDataColumn()); c1_const_fixed_string) - { - c1_const_str_ref = std::string_view(c1_const_fixed_string->getDataAt(0)); - } - else - throw Exception("Logical error: ColumnConst contains not String nor FixedString column", ErrorCodes::ILLEGAL_COLUMN); - } + template + bool executeStringWithCollator( + Block & block, + size_t result, + const IColumn * c0, + const IColumn * c1, + const ColumnString * c0_string, + const ColumnString * c1_string, + const ColumnConst * c0_const, + const ColumnConst * c1_const) const + { + using ResultType = typename ResultColumnType::value_type; + using StringImpl = StringComparisonWithCollatorImpl, ResultType>; + + std::string_view c0_const_str_ref = genConstStrRef(c0_const); + std::string_view c1_const_str_ref = genConstStrRef(c1_const); if (c0_const && c1_const) { From 1608bab28ddc0421dde8aac2e0d0da75e7573eea Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 7 Jul 2022 17:37:06 +0800 Subject: [PATCH 12/14] Update dbms/src/Functions/CollationOperatorOptimized.h Co-authored-by: ruoxi --- dbms/src/Functions/CollationOperatorOptimized.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/Functions/CollationOperatorOptimized.h b/dbms/src/Functions/CollationOperatorOptimized.h index 6303b703709..141594ea47d 100644 --- a/dbms/src/Functions/CollationOperatorOptimized.h +++ b/dbms/src/Functions/CollationOperatorOptimized.h @@ -166,7 +166,7 @@ ALWAYS_INLINE inline bool StringVectorStringVector( // Handle str-column compare const-str. // - Optimize UTF8_BIN and UTF8MB4_BIN // - Right trim const-str first -// - Check if column don NOT contains tail space +// - Check if column does NOT contain tail space // - If Op is `EqualsOp` or `NotEqualsOp`, optimize comparison by faster way template ALWAYS_INLINE inline bool StringVectorConstant( From 9ad2e69fa32e96468cff4409666d19d4bab1784f Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 7 Jul 2022 17:37:17 +0800 Subject: [PATCH 13/14] Update dbms/src/Functions/CollationOperatorOptimized.h Co-authored-by: ruoxi --- dbms/src/Functions/CollationOperatorOptimized.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbms/src/Functions/CollationOperatorOptimized.h b/dbms/src/Functions/CollationOperatorOptimized.h index 141594ea47d..395ecc5b9eb 100644 --- a/dbms/src/Functions/CollationOperatorOptimized.h +++ b/dbms/src/Functions/CollationOperatorOptimized.h @@ -122,7 +122,7 @@ __attribute__((flatten, always_inline)) inline void LoopOneColumn( // Handle str-column compare str-column. // - Optimize UTF8_BIN and UTF8MB4_BIN -// - Check if columns don NOT contains tail space +// - Check if columns do NOT contain tail space // - If Op is `EqualsOp` or `NotEqualsOp`, optimize comparison by faster way template ALWAYS_INLINE inline bool StringVectorStringVector( From 5b3d8e0f7ffe24a3641022c01a595b50d9922d24 Mon Sep 17 00:00:00 2001 From: Zhigao Tong Date: Thu, 7 Jul 2022 17:51:06 +0800 Subject: [PATCH 14/14] Address comment --- dbms/src/Functions/FunctionsComparison.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dbms/src/Functions/FunctionsComparison.h b/dbms/src/Functions/FunctionsComparison.h index 69f50f8482a..8f7502fba85 100644 --- a/dbms/src/Functions/FunctionsComparison.h +++ b/dbms/src/Functions/FunctionsComparison.h @@ -356,8 +356,8 @@ struct StringComparisonWithCollatorImpl } static void constantConstant( - std::string_view a, - std::string_view b, + const std::string_view & a, + const std::string_view & b, const TiDB::TiDBCollatorPtr & collator, ResultType & c) {