aboutsummaryrefslogtreecommitdiffstats
path: root/util
diff options
context:
space:
mode:
authorosidorkin <osidorkin@yandex-team.com>2023-03-23 12:15:57 +0300
committerosidorkin <osidorkin@yandex-team.com>2023-03-23 12:15:57 +0300
commit8cfa97b5487686f556b3dae62132c8f099b1a0b3 (patch)
treebdf7d7ab18e12b5a22db8341f0231eaea484ded4 /util
parent8d5942b8f813c0e704a166c3c83902ccceefca07 (diff)
downloadydb-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.cpp71
-rw-r--r--util/string/cast.h67
-rw-r--r--util/string/cast_ut.cpp12
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)));
+ }
};