Skip to content

Commit 7e780e5

Browse files
authored
More ctl::string optimization (#1232)
Moves some isbig checks into string.h, enabling smarter optimizations to be made on small strings. Also we no longer zero out our string prior to calling the various constructors, buying back the performance we lost on big strings when we made the small-string optimization. We further add a little optimization to the big_string copy constructor: if the string is using half or more of its capacity, then we don’t recompute capacity and just take the old string’s. As well, the copy constructor always makes a small string when it will fit, even if copied from a big string that got truncated. This also reworks the test to follow the idiom adopted elsewhere re stl, and adds a helper function to tell if a string is small based on data().
1 parent 9a5a138 commit 7e780e5

File tree

3 files changed

+162
-71
lines changed

3 files changed

+162
-71
lines changed

ctl/string.cc

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,45 +23,66 @@
2323

2424
namespace ctl {
2525

26-
string::~string() noexcept
26+
void
27+
string::destroy_big() noexcept
2728
{
28-
if (isbig()) {
29-
auto* b = big();
30-
if (b->n) {
31-
if (b->n >= b->c)
32-
__builtin_trap();
33-
if (b->p[b->n])
34-
__builtin_trap();
35-
}
36-
if (b->c && !b->p)
29+
auto* b = big();
30+
if (b->n) {
31+
if (b->n >= b->c)
32+
__builtin_trap();
33+
if (b->p[b->n])
3734
__builtin_trap();
38-
free(b->p);
3935
}
36+
if (b->c && !b->p)
37+
__builtin_trap();
38+
free(b->p);
4039
}
4140

42-
string::string(const char* s) noexcept : string()
43-
{
44-
append(s, strlen(s));
45-
}
46-
47-
string::string(const string& s) noexcept : string()
48-
{
49-
append(s.data(), s.size());
50-
}
51-
52-
string::string(const string_view s) noexcept : string()
41+
void
42+
string::init_big(const string& s) noexcept
5343
{
54-
append(s.p, s.n);
44+
char* p2;
45+
#ifndef NDEBUG
46+
if (!s.isbig())
47+
__builtin_trap();
48+
#endif
49+
if (s.size() >= s.capacity() >> 1) {
50+
if (!(p2 = (char*)malloc(s.capacity())))
51+
__builtin_trap();
52+
set_big_string(p2, s.size(), s.capacity());
53+
} else {
54+
init_big(string_view(s));
55+
}
5556
}
5657

57-
string::string(const size_t size, const char ch) noexcept : string()
58+
void
59+
string::init_big(const string_view s) noexcept
5860
{
59-
resize(size, ch);
61+
size_t need;
62+
char* p2;
63+
if (ckd_add(&need, s.n, 1 /* nul */ + 15))
64+
__builtin_trap();
65+
need &= -16;
66+
if (!(p2 = (char*)malloc(need)))
67+
__builtin_trap();
68+
memcpy(p2, s.p, s.n);
69+
p2[s.n] = 0;
70+
set_big_string(p2, s.n, need);
6071
}
6172

62-
string::string(const char* s, const size_t size) noexcept : string()
73+
void
74+
string::init_big(const size_t n, const char ch) noexcept
6375
{
64-
append(s, size);
76+
size_t need;
77+
char* p2;
78+
if (ckd_add(&need, n, 1 /* nul */ + 15))
79+
__builtin_trap();
80+
need &= -16;
81+
if (!(p2 = (char*)malloc(need)))
82+
__builtin_trap();
83+
memset(p2, ch, n);
84+
p2[n] = 0;
85+
set_big_string(p2, n, need);
6586
}
6687

6788
const char*

ctl/string.h

Lines changed: 85 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,60 @@ class string
4848
using const_iterator = const char*;
4949
static constexpr size_t npos = -1;
5050

51-
~string() /* noexcept */;
52-
string(string_view) noexcept;
53-
string(const char*) noexcept;
54-
string(const string&) noexcept;
55-
string(const char*, size_t) noexcept;
56-
explicit string(size_t, char = 0) noexcept;
51+
string() noexcept
52+
{
53+
__builtin_memset(blob, 0, sizeof(size_t) * 2);
54+
// equivalent to set_small_size(0) but also zeroes memory
55+
*(((size_t*)blob) + 2) = __::sso_max << (sizeof(size_t) - 1) * 8;
56+
}
57+
58+
string(const string_view s) noexcept
59+
{
60+
if (s.n <= __::sso_max) {
61+
__builtin_memcpy(blob, s.p, s.n);
62+
__builtin_memset(blob + s.n, 0, __::sso_max - s.n);
63+
set_small_size(s.n);
64+
} else {
65+
init_big(s);
66+
}
67+
}
68+
69+
explicit string(const size_t n, const char ch = 0) noexcept
70+
{
71+
if (n <= __::sso_max) {
72+
__builtin_memset(blob, ch, n);
73+
__builtin_memset(blob + n, 0, __::sso_max - n);
74+
set_small_size(n);
75+
} else {
76+
init_big(n, ch);
77+
}
78+
}
79+
80+
string(const char* const p) noexcept
81+
: string(string_view(p, __builtin_strlen(p)))
82+
{
83+
}
84+
85+
string(const string& r) noexcept
86+
{
87+
if (r.size() <= __::sso_max) {
88+
__builtin_memcpy(blob, r.data(), __::string_size);
89+
set_small_size(r.size());
90+
} else {
91+
init_big(r);
92+
}
93+
}
94+
95+
string(const char* const p, const size_t n) noexcept
96+
: string(string_view(p, n))
97+
{
98+
}
99+
100+
~string() /* noexcept */
101+
{
102+
if (isbig())
103+
destroy_big();
104+
}
57105

58106
string& operator=(string) noexcept;
59107
const char* c_str() const noexcept;
@@ -78,13 +126,6 @@ class string
78126
size_t find(char, size_t = 0) const noexcept;
79127
size_t find(string_view, size_t = 0) const noexcept;
80128

81-
string() noexcept
82-
{
83-
__builtin_memset(blob, 0, sizeof(size_t) * 2);
84-
// equivalent to set_small_size(0) but also zeroes memory
85-
*(((size_t*)blob) + 2) = __::sso_max << (sizeof(size_t) - 1) * 8;
86-
}
87-
88129
void swap(string& s) noexcept
89130
{
90131
ctl::swap(blob, s.blob);
@@ -110,17 +151,17 @@ class string
110151
return isbig() ? !big()->n : small()->rem >= __::sso_max;
111152
}
112153

113-
inline char* data() noexcept
154+
char* data() noexcept
114155
{
115156
return isbig() ? big()->p : small()->buf;
116157
}
117158

118-
inline const char* data() const noexcept
159+
const char* data() const noexcept
119160
{
120161
return isbig() ? big()->p : small()->buf;
121162
}
122163

123-
inline size_t size() const noexcept
164+
size_t size() const noexcept
124165
{
125166
#if 0
126167
if (!isbig() && small()->rem > __::sso_max)
@@ -215,7 +256,7 @@ class string
215256
append(s.p, s.n);
216257
}
217258

218-
inline operator string_view() const noexcept
259+
operator string_view() const noexcept
219260
{
220261
return string_view(data(), size());
221262
}
@@ -277,63 +318,68 @@ class string
277318
}
278319

279320
private:
280-
inline bool isbig() const noexcept
321+
void destroy_big() noexcept;
322+
void init_big(const string&) noexcept;
323+
void init_big(string_view) noexcept;
324+
void init_big(size_t, char) noexcept;
325+
326+
bool isbig() const noexcept
281327
{
282328
return *(blob + __::sso_max) & 0x80;
283329
}
284330

285-
inline void set_small_size(const size_t size) noexcept
331+
void set_small_size(const size_t size) noexcept
286332
{
287333
if (size > __::sso_max)
288334
__builtin_trap();
289-
*(blob + __::sso_max) = (__::sso_max - size);
335+
__s.rem = __::sso_max - size;
290336
}
291337

292-
inline void set_big_string(char* const p,
293-
const size_t n,
294-
const size_t c2) noexcept
338+
void set_big_string(char* const p, const size_t n, const size_t c2) noexcept
295339
{
296340
if (c2 > __::big_mask)
297341
__builtin_trap();
298-
*(char**)blob = p;
299-
*(((size_t*)blob) + 1) = n;
300-
*(((size_t*)blob) + 2) = c2 | ~__::big_mask;
342+
__b.p = p;
343+
__b.n = n;
344+
__b.c = c2 | ~__::big_mask;
301345
}
302346

303-
inline __::small_string* small() noexcept
347+
__::small_string* small() noexcept
304348
{
305349
if (isbig())
306350
__builtin_trap();
307-
return reinterpret_cast<__::small_string*>(blob);
351+
return &__s;
308352
}
309353

310-
inline const __::small_string* small() const noexcept
354+
const __::small_string* small() const noexcept
311355
{
312356
if (isbig())
313357
__builtin_trap();
314-
return reinterpret_cast<const __::small_string*>(blob);
358+
return &__s;
315359
}
316360

317-
inline __::big_string* big() noexcept
361+
__::big_string* big() noexcept
318362
{
319363
if (!isbig())
320364
__builtin_trap();
321-
return reinterpret_cast<__::big_string*>(blob);
365+
return &__b;
322366
}
323367

324-
inline const __::big_string* big() const noexcept
368+
const __::big_string* big() const noexcept
325369
{
326370
if (!isbig())
327371
__builtin_trap();
328-
return reinterpret_cast<const __::big_string*>(blob);
372+
return &__b;
329373
}
330374

331-
friend string strcat(string_view, string_view);
375+
friend string strcat(string_view, string_view) noexcept;
332376

333-
alignas(union {
334-
__::big_string a;
335-
__::small_string b;
336-
}) char blob[__::string_size];
377+
union
378+
{
379+
__::big_string __b;
380+
__::small_string __s;
381+
char blob[__::string_size];
382+
};
337383
};
338384

339385
static_assert(sizeof(string) == __::string_size);

test/ctl/string_test.cc

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,19 @@
2323
#include "libc/runtime/runtime.h"
2424
#include "libc/str/str.h"
2525

26-
using String = ctl::string;
2726
// #include <string>
28-
// using String = std::string;
27+
// #define ctl std
28+
29+
using String = ctl::string;
30+
31+
#undef ctl
32+
33+
inline bool
34+
issmall(const String& s)
35+
{
36+
return s.capacity() == sizeof(s) &&
37+
s.data() == reinterpret_cast<const char*>(&s);
38+
}
2939

3040
int
3141
main()
@@ -358,15 +368,14 @@ main()
358368
String s;
359369
if constexpr (std::is_same_v<ctl::string, decltype(s)>) {
360370
// tests the small-string optimization on ctl::string
361-
char* d = s.data();
362371
for (int i = 0; i < 23; ++i) {
363372
s.append("a");
364-
if (s.data() != d) {
373+
if (!issmall(s)) {
365374
return 79 + i;
366375
}
367376
}
368377
s.append("a");
369-
if (s.data() == d) {
378+
if (issmall(s)) {
370379
return 103;
371380
}
372381
} else {
@@ -380,6 +389,21 @@ main()
380389
}
381390
}
382391

392+
{
393+
String s("arst", 4);
394+
for (int i = 0; i < 30; ++i) {
395+
s.append("a");
396+
}
397+
s.resize(4);
398+
if (s != "arst")
399+
return 105;
400+
if constexpr (std::is_same_v<ctl::string, decltype(s)>) {
401+
String r(s);
402+
if (issmall(s) || !issmall(r))
403+
return 106;
404+
}
405+
}
406+
383407
CheckForMemoryLeaks();
384408
return 0;
385409
}

0 commit comments

Comments
 (0)