diff options
author | osidorkin <osidorkin@yandex-team.com> | 2023-03-23 12:15:57 +0300 |
---|---|---|
committer | osidorkin <osidorkin@yandex-team.com> | 2023-03-23 12:15:57 +0300 |
commit | 8cfa97b5487686f556b3dae62132c8f099b1a0b3 (patch) | |
tree | bdf7d7ab18e12b5a22db8341f0231eaea484ded4 /util | |
parent | 8d5942b8f813c0e704a166c3c83902ccceefca07 (diff) | |
download | ydb-8cfa97b5487686f556b3dae62132c8f099b1a0b3.tar.gz |
util: Add constexpr int to string conversion class. This will allow us not to have heap allocations when joining ints to string
Diffstat (limited to 'util')
-rw-r--r-- | util/string/cast.cpp | 71 | ||||
-rw-r--r-- | util/string/cast.h | 67 | ||||
-rw-r--r-- | util/string/cast_ut.cpp | 12 |
3 files changed, 80 insertions, 70 deletions
diff --git a/util/string/cast.cpp b/util/string/cast.cpp index 4cd940f57b..360551f068 100644 --- a/util/string/cast.cpp +++ b/util/string/cast.cpp @@ -32,9 +32,6 @@ using double_conversion::StringToDoubleConverter; */ namespace { - constexpr char IntToChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - - static_assert(Y_ARRAY_SIZE(IntToChar) == 16, "expect Y_ARRAY_SIZE(IntToChar) == 16"); // clang-format off constexpr int LetterToIntMap[] = { @@ -53,82 +50,16 @@ namespace { // clang-format on template <class T> - std::enable_if_t<std::is_signed<T>::value, std::make_unsigned_t<T>> NegateNegativeSigned(T value) noexcept { - return std::make_unsigned_t<T>(-(value + 1)) + std::make_unsigned_t<T>(1); - } - - template <class T> - std::enable_if_t<std::is_unsigned<T>::value, std::make_unsigned_t<T>> NegateNegativeSigned(T) noexcept { - Y_UNREACHABLE(); - } - - template <class T> std::make_signed_t<T> NegatePositiveSigned(T value) noexcept { return value > 0 ? (-std::make_signed_t<T>(value - 1) - 1) : 0; } - template <class T, unsigned base, class TChar> - struct TBasicIntFormatter { - static_assert(1 < base && base < 17, "expect 1 < base && base < 17"); - static_assert(std::is_unsigned<T>::value, "TBasicIntFormatter can only handle unsigned integers."); - - static inline size_t Format(T value, TChar* buf, size_t len) { - Y_ENSURE(len, TStringBuf("zero length")); - - TChar* tmp = buf; - - do { - // divide only once, do not use mod - const T nextVal = static_cast<T>(value / base); - *tmp++ = IntToChar[base == 2 || base == 4 || base == 8 || base == 16 ? value & (base - 1) : value - base * nextVal]; - value = nextVal; - } while (value && --len); - - Y_ENSURE(!value, TStringBuf("not enough room in buffer")); - - const size_t result = tmp - buf; - - --tmp; - - while (buf < tmp) { - TChar c = *buf; - - *buf = *tmp; - *tmp = c; - ++buf; - --tmp; - } - - return result; - } - }; - - template <class T, unsigned base, class TChar> - struct TIntFormatter { - static_assert(1 < base && base < 17, "expect 1 < base && base < 17"); - static_assert(std::is_integral<T>::value, "T must be an integral type."); - - static inline size_t Format(T value, TChar* buf, size_t len) { - using TUFmt = TBasicIntFormatter<std::make_unsigned_t<T>, base, TChar>; - - if (std::is_signed<T>::value && value < 0) { - Y_ENSURE(len >= 2, TStringBuf("not enough room in buffer")); - - *buf = '-'; - - return 1 + TUFmt::Format(NegateNegativeSigned(value), buf + 1, len - 1); - } - - return TUFmt::Format(value, buf, len); - } - }; - template <class T> struct TFltModifiers; template <class T, int base, class TChar> Y_NO_INLINE size_t FormatInt(T value, TChar* buf, size_t len) { - return TIntFormatter<T, base, TChar>::Format(value, buf, len); + return TIntStringBuf<T, base, TChar>::Convert(value, buf, len); } template <class T> diff --git a/util/string/cast.h b/util/string/cast.h index 3d94ecb0de..d09fe0401c 100644 --- a/util/string/cast.h +++ b/util/string/cast.h @@ -386,3 +386,70 @@ static inline TUtf16String ToWtring(const TWtringBuf wtr) { static inline TUtf32String ToUtf32String(const TUtf32StringBuf wtr) { return TUtf32String(wtr); } + +template <typename T, unsigned base = 10, class TChar = char> +class TIntStringBuf { +private: + // inline constexprs are not supported by CUDA yet + static constexpr char IntToChar[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + static_assert(Y_ARRAY_SIZE(IntToChar) == 16, "expect Y_ARRAY_SIZE(IntToChar) == 16"); + static_assert(1 < base && base < 17, "expect 1 < base && base < 17"); + +public: + template <std::enable_if_t<std::is_integral<T>::value, bool> = true> + explicit constexpr TIntStringBuf(T t) { + Size_ = Convert(t, Buf_, sizeof(Buf_) - 1); + // Init the rest of the array, + // otherwise constexpr copy and move constructors don't work due to uninitialized data access + std::fill(Buf_ + Size_, Buf_ + sizeof(Buf_), '\0'); + } + + constexpr operator TStringBuf() const noexcept { + return TStringBuf(Buf_, Size_); + } + + constexpr static ui32 Convert(T t, TChar* buf, ui32 bufLen) { + if (std::is_signed<T>::value && t < 0) { + Y_ENSURE(bufLen >= 2, TStringBuf("not enough room in buffer")); + buf[0] = '-'; + const auto mt = std::make_unsigned_t<T>(-(t + 1)) + std::make_unsigned_t<T>(1); + return ConvertUnsigned(mt, &buf[1], bufLen - 1) + 1; + } else { + return ConvertUnsigned(t, buf, bufLen); + } + } + +private: + constexpr static ui32 ConvertUnsigned(typename std::make_unsigned<T>::type t, TChar* buf, ui32 bufLen) { + Y_ENSURE(bufLen, TStringBuf("zero length")); + + if (t == 0) { + *buf = '0'; + return 1; + } + auto* be = buf + bufLen; + ui32 l = 0; + while (t > 0 && be > buf) { + const auto v = t / base; + const auto r = (base == 2 || base == 4 || base == 8 || base == 16) ? t & (base - 1) : t - base * v; + --be; + if /*constexpr*/ (base <= 10) { // if constexpr is not supported by CUDA yet + *be = r + '0'; + } else { + *be = IntToChar[r]; + } + ++l; + t = v; + } + Y_ENSURE(!t, TStringBuf("not enough room in buffer")); + for (ui32 i = 0; i < l; ++i) { + *buf = *be; + ++buf; + ++be; + } + return l; + } + ui32 Size_; + TChar Buf_[sizeof(T) * 8]; // worst case base = 2 +}; diff --git a/util/string/cast_ut.cpp b/util/string/cast_ut.cpp index 8690fa78dc..0ff92e379b 100644 --- a/util/string/cast_ut.cpp +++ b/util/string/cast_ut.cpp @@ -587,4 +587,16 @@ Y_UNIT_TEST_SUITE(TCastTest) { UNIT_ASSERT_VALUES_EQUAL(ToString(U'я'), "1103"); UNIT_ASSERT_VALUES_EQUAL(ToString(U'\U0001F600'), "128512"); // 'GRINNING FACE' (U+1F600) } + + Y_UNIT_TEST(TestTIntStringBuf) { + static_assert(TStringBuf(TIntStringBuf(111)) == TStringBuf("111")); + static_assert(TStringBuf(TIntStringBuf(-111)) == TStringBuf("-111")); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(TIntStringBuf(0)), "0"sv); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(TIntStringBuf(1111)), "1111"sv); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(TIntStringBuf(-1)), "-1"sv); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(TIntStringBuf(-1111)), "-1111"sv); + + constexpr auto v = TIntStringBuf(-1111); + UNIT_ASSERT_VALUES_EQUAL(TStringBuf(v), TStringBuf(ToString(-1111))); + } }; |