Coverage Report

Created: 2026-10-19 09:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/root/doris/be/src/util/faststring.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <butil/macros.h>
21
#include <sanitizer/asan_interface.h>
22
23
#include <cstdint>
24
#include <cstring>
25
#include <string>
26
27
#include "core/allocator.h"
28
#include "core/allocator_fwd.h"
29
#include "util/memcpy_inlined.h"
30
#include "util/slice.h"
31
32
namespace doris {
33
34
// A faststring is similar to a std::string, except that it is faster for many
35
// common use cases (in particular, resize() will fill with uninitialized data
36
// instead of memsetting to \0)
37
// only build() can transfer data to the outside.
38
class faststring : private Allocator<false, false, false, DefaultMemoryAllocator> {
39
public:
40
    enum { kInitialCapacity = 32 };
41
42
11.1M
    faststring() : data_(initial_data_), len_(0), capacity_(kInitialCapacity) {}
43
44
    // Construct a string with the given capacity, in bytes.
45
    explicit faststring(size_t capacity)
46
39.6k
            : data_(initial_data_), len_(0), capacity_(kInitialCapacity) {
47
39.6k
        if (capacity > capacity_) {
48
6.56k
            data_ = reinterpret_cast<uint8_t*>(Allocator::alloc(capacity));
49
6.56k
            capacity_ = capacity;
50
6.56k
        }
51
39.6k
        ASAN_POISON_MEMORY_REGION(data_, capacity_);
52
39.6k
    }
53
54
11.2M
    ~faststring() {
55
11.2M
        ASAN_UNPOISON_MEMORY_REGION(initial_data_, arraysize(initial_data_));
56
11.2M
        if (data_ != initial_data_) {
57
87.7k
            Allocator::free(data_, capacity_);
58
87.7k
        }
59
11.2M
    }
60
61
    // Reset the valid length of the string to 0.
62
    //
63
    // This does not free up any memory. The capacity of the string remains unchanged.
64
22.5M
    void clear() {
65
22.5M
        resize(0);
66
22.5M
        ASAN_POISON_MEMORY_REGION(data_, capacity_);
67
22.5M
    }
68
69
    // Resize the string to the given length.
70
    // If the new length is larger than the old length, the capacity is expanded as necessary.
71
    //
72
    // NOTE: in contrast to std::string's implementation, Any newly "exposed" bytes of data are
73
    // not cleared.
74
148M
    void resize(size_t newsize) {
75
148M
        if (newsize > capacity_) {
76
38.6k
            reserve(newsize);
77
38.6k
        }
78
148M
        len_ = newsize;
79
148M
        ASAN_POISON_MEMORY_REGION(data_ + len_, capacity_ - len_);
80
148M
        ASAN_UNPOISON_MEMORY_REGION(data_, len_);
81
148M
    }
82
83
    // Return the buffer built so far and reset `this` to the initial status (size() == 0).
84
    // NOTE: the returned data pointer is not necessarily the pointer returned by data()
85
60.7k
    OwnedSlice build() {
86
60.7k
        uint8_t* ret = data_;
87
60.7k
        if (ret == initial_data_) {
88
1.39k
            ret = reinterpret_cast<uint8_t*>(Allocator::alloc(capacity_));
89
1.39k
            DCHECK(len_ <= capacity_);
90
1.39k
            memcpy(ret, data_, len_);
91
1.39k
        }
92
60.7k
        OwnedSlice result(ret, len_, capacity_);
93
60.7k
        len_ = 0;
94
60.7k
        capacity_ = kInitialCapacity;
95
60.7k
        data_ = initial_data_;
96
60.7k
        ASAN_POISON_MEMORY_REGION(data_, capacity_);
97
60.7k
        return result;
98
60.7k
    }
99
100
    // Reserve space for the given total amount of data. If the current capacity is already
101
    // larger than the newly requested capacity, this is a no-op (i.e. it does not ever free memory).
102
    //
103
    // NOTE: even though the new capacity is reserved, it is illegal to begin writing into that memory
104
    // directly using pointers. If ASAN is enabled, this is ensured using manual memory poisoning.
105
124M
    void reserve(size_t newcapacity) {
106
124M
        if (newcapacity <= capacity_) [[likely]] {
107
124M
            return;
108
124M
        }
109
        // Grow by at least 50% rather than to exactly `newcapacity`. Callers such
110
        // as BitWriter reserve a few bytes at a time, and reallocating on every
111
        // call makes appending n bytes cost O(n^2) in memcpy.
112
149k
        GrowToAtLeast(newcapacity);
113
149k
    }
114
115
    // Append the given data to the string, resizing capacity as necessary.
116
2.28M
    void append(const void* src_v, size_t count) {
117
2.28M
        const uint8_t* src = reinterpret_cast<const uint8_t*>(src_v);
118
2.28M
        EnsureRoomForAppend(count);
119
2.28M
        ASAN_UNPOISON_MEMORY_REGION(data_ + len_, count);
120
121
        // appending short values is common enough that this
122
        // actually helps, according to benchmarks. In theory
123
        // memcpy_inlined should already be just as good, but this
124
        // was ~20% faster for reading a large prefix-coded string file
125
        // where each string was only a few chars different
126
2.28M
        if (count <= 4) {
127
1.17M
            uint8_t* p = &data_[len_];
128
3.72M
            for (int i = 0; i < count; i++) {
129
2.55M
                *p++ = *src++;
130
2.55M
            }
131
1.17M
        } else {
132
1.11M
            memcpy_inlined(&data_[len_], src, count);
133
1.11M
        }
134
2.28M
        len_ += count;
135
2.28M
    }
136
137
    // Append the given string to this string.
138
8
    void append(const std::string& str) { append(str.data(), str.size()); }
139
140
    // Append the given character to this string.
141
99.2k
    void push_back(const char byte) {
142
99.2k
        EnsureRoomForAppend(1);
143
99.2k
        ASAN_UNPOISON_MEMORY_REGION(data_ + len_, 1);
144
99.2k
        data_[len_] = byte;
145
99.2k
        len_++;
146
99.2k
    }
147
148
    // Return the valid length of this string.
149
32.6k
    size_t length() const { return len_; }
150
151
    // Return the valid length of this string (identical to length())
152
3.77M
    size_t size() const { return len_; }
153
154
    // Return the allocated capacity of this string.
155
124M
    size_t capacity() const { return capacity_; }
156
157
    // Return a pointer to the data in this string. Note that this pointer
158
    // may be invalidated by any later non-const operation.
159
285k
    const uint8_t* data() const { return &data_[0]; }
160
161
    // Return a pointer to the data in this string. Note that this pointer
162
    // may be invalidated by any later non-const operation.
163
124M
    uint8_t* data() { return &data_[0]; }
164
165
    // Return the given element of this string. Note that this does not perform
166
    // any bounds checking.
167
0
    const uint8_t& at(size_t i) const { return data_[i]; }
168
169
    // Return the given element of this string. Note that this does not perform
170
    // any bounds checking.
171
0
    const uint8_t& operator[](size_t i) const { return data_[i]; }
172
173
    // Return the given element of this string. Note that this does not perform
174
    // any bounds checking.
175
2.40M
    uint8_t& operator[](size_t i) { return data_[i]; }
176
177
    // Reset the contents of this string by copying 'len' bytes from 'src'.
178
25.6k
    void assign_copy(const uint8_t* src, size_t len) {
179
        // Reset length so that the first resize doesn't need to copy the current
180
        // contents of the array.
181
25.6k
        len_ = 0;
182
25.6k
        resize(len);
183
25.6k
        memcpy(data(), src, len);
184
25.6k
    }
185
186
    // Reset the contents of this string by copying from the given std::string.
187
0
    void assign_copy(const std::string& str) {
188
0
        assign_copy(reinterpret_cast<const uint8_t*>(str.c_str()), str.size());
189
0
    }
190
191
    // Reallocates the internal storage to fit only the current data.
192
    //
193
    // This may revert to using internal storage if the current length is shorter than
194
    // kInitialCapacity. In that case, after this call, capacity() will go down to
195
    // kInitialCapacity.
196
    //
197
    // Any pointers within this instance may be invalidated.
198
102
    void shrink_to_fit() {
199
102
        if (data_ == initial_data_ || capacity_ == len_) return;
200
61
        ShrinkToFitInternal();
201
61
    }
202
203
    // Return a copy of this string as a std::string.
204
0
    std::string ToString() const {
205
0
        return std::string(reinterpret_cast<const char*>(data()), len_);
206
0
    }
207
208
private:
209
    DISALLOW_COPY_AND_ASSIGN(faststring);
210
211
    // If necessary, expand the buffer to fit at least 'count' more bytes.
212
    // If the array has to be grown, it is grown by at least 50%.
213
2.38M
    void EnsureRoomForAppend(size_t count) {
214
2.38M
        if (len_ + count <= capacity_) [[likely]] {
215
2.34M
            return;
216
2.34M
        }
217
218
        // Call the non-inline slow path - this reduces the number of instructions
219
        // on the hot path.
220
42.5k
        GrowToAtLeast(len_ + count);
221
42.5k
    }
222
223
    // The slow path of EnsureRoomForAppend. Grows the buffer by either
224
    // 'count' bytes, or 50%, whichever is more.
225
    void GrowToAtLeast(size_t newcapacity);
226
227
    // Grow the array to the given capacity, which must be more than
228
    // the current capacity.
229
    void GrowArray(size_t newcapacity);
230
231
    void ShrinkToFitInternal();
232
233
    uint8_t* data_ = nullptr;
234
    uint8_t initial_data_[kInitialCapacity];
235
    size_t len_;
236
    // NOTE: we will make a initial buffer as part of the object, so the smallest
237
    // possible value of capacity_ is kInitialCapacity.
238
    size_t capacity_;
239
};
240
241
} // namespace doris