Skip to content

Commit ef7a81e

Browse files
committed
Amendment: refactor table destruction, make sure everything is freed
Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent 778e003 commit ef7a81e

1 file changed

Lines changed: 64 additions & 45 deletions

File tree

src/libutil/ustring.cpp

Lines changed: 64 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -40,41 +40,33 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
4040
"BASE_CAPACITY must be a power of 2");
4141

4242
TableRepMap()
43-
: entries(static_cast<ustring::TableRep**>(
44-
calloc(BASE_CAPACITY, sizeof(ustring::TableRep*))))
45-
, memory_usage(sizeof(*this)
46-
+ sizeof(ustring::TableRep*) * BASE_CAPACITY)
43+
: memory_usage(sizeof(*this))
4744
{
45+
resize_entries(BASE_CAPACITY);
4846
allocate_pool_block();
4947
}
5048

5149
~TableRepMap()
5250
{ /* just let memory leak */
5351
}
5452

55-
void clear()
53+
// Free all allocated resources. Note that this leaves the TableRepMap
54+
// in an unusable state.
55+
void free_resources()
5656
{
5757
ustring_write_lock_t lock(mutex);
58-
// Destroy all TableRep objects. The destructor safely handles the
59-
// case where the internal std::string aliases the pool chars.
60-
for (size_t i = 0; i <= mask; ++i) {
61-
if (entries[i])
62-
entries[i]->~TableRep();
63-
}
64-
// Free all pool allocations and large individual allocations.
58+
// Destroy TableRep objects FIRST, while the pool/large-alloc memory
59+
// they live in is still valid. Their destructors may read/write the
60+
// surrounding allocation and (on some platforms) the std::string
61+
// subobject owns a separate heap buffer that only gets freed here.
62+
destroy_entries();
63+
entries.clear();
64+
entries.shrink_to_fit();
65+
// Now safe to free the backing buffers.
6566
all_pools.clear();
6667
all_pools.shrink_to_fit();
6768
large_allocs.clear();
6869
large_allocs.shrink_to_fit();
69-
free(entries);
70-
// Re-initialize to a fresh, usable state.
71-
mask = BASE_CAPACITY - 1;
72-
entries = static_cast<ustring::TableRep**>(
73-
calloc(BASE_CAPACITY, sizeof(ustring::TableRep*)));
74-
num_entries = 0;
75-
memory_usage = sizeof(*this)
76-
+ sizeof(ustring::TableRep*) * BASE_CAPACITY;
77-
allocate_pool_block();
7870
}
7971

8072
size_t get_memory_usage()
@@ -174,31 +166,35 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
174166
private:
175167
void grow()
176168
{
177-
size_t new_mask = mask * 2 + 1;
169+
// Temporarily hold the old entries while we are copying them
170+
std::vector<ustring::TableRep*> old_entries;
171+
old_entries.swap(entries);
172+
size_t old_num_entries = num_entries;
173+
size_t to_copy = old_num_entries;
178174

179-
// NOTE: only increment by half because we are doubling the entries and freeing the old
180-
memory_usage += (mask + 1) * sizeof(ustring::TableRep*);
175+
// Make bigger space for new entries table and new mask
176+
resize_entries(2 * old_entries.size());
181177

182-
ustring::TableRep** new_entries = static_cast<ustring::TableRep**>(
183-
calloc(new_mask + 1, sizeof(ustring::TableRep*)));
184-
size_t to_copy = num_entries;
178+
// Copy each entry from old into the new, recomputing the hash because
179+
// the mask has changd.
185180
for (size_t i = 0; to_copy != 0; i++) {
186-
if (entries[i] == 0)
181+
if (old_entries[i] == nullptr)
187182
continue;
188-
size_t pos = entries[i]->hashed & new_mask, dist = 0;
183+
// i is old position, pos will be new position
184+
size_t pos = old_entries[i]->hashed & mask, dist = 0;
189185
for (;;) {
190-
if (new_entries[pos] == 0)
186+
if (entries[pos] == 0)
191187
break;
192188
++dist;
193-
pos = (pos + dist) & new_mask; // quadratic probing
189+
pos = (pos + dist) & mask; // quadratic probing
194190
}
195-
new_entries[pos] = entries[i];
191+
entries[pos] = old_entries[i];
192+
old_entries[i] = nullptr;
196193
to_copy--;
197194
}
198195

199-
free(entries);
200-
entries = new_entries;
201-
mask = new_mask;
196+
// old_entries will free when we exit this function
197+
memory_usage -= sizeof(ustring::TableRep*) * old_entries.size();
202198
}
203199

204200
ustring::TableRep* make_rep(string_view str, uint64_t hash)
@@ -239,18 +235,41 @@ template<unsigned BASE_CAPACITY, unsigned POOL_SIZE> struct TableRepMap {
239235
all_pools.emplace_back(pool);
240236
}
241237

242-
OIIO_CACHE_ALIGN mutable ustring_mutex_t mutex;
243-
size_t mask = BASE_CAPACITY - 1;
244-
ustring::TableRep** entries = nullptr;
245-
size_t num_entries = 0;
246-
char* pool = nullptr; // Current pool block we're using
247-
size_t pool_offset = 0; // Next offset within current block
248-
size_t memory_usage = 0; // Total memory usage
238+
void destroy_entries()
239+
{
240+
// Destroy all TableRep objects. The destructor safely handles the
241+
// case where the internal std::string aliases the pool chars.
242+
for (auto& e : entries) {
243+
if (e) {
244+
e->~TableRep();
245+
e = nullptr;
246+
}
247+
}
248+
}
249+
250+
void resize_entries(size_t newsize)
251+
{
252+
OIIO_CONTRACT_ASSERT(entries.empty());
253+
OIIO_CONTRACT_ASSERT_MSG((newsize & (newsize - 1)) == 0,
254+
"New entries size must be power of 2");
255+
entries.resize(newsize, nullptr);
256+
memory_usage += sizeof(ustring::TableRep*) * entries.size();
257+
num_entries = 0;
258+
mask = newsize - 1;
259+
}
260+
261+
std::vector<ustring::TableRep*> entries;
262+
size_t mask = BASE_CAPACITY - 1;
263+
size_t num_entries = 0;
264+
char* pool = nullptr; // Current pool block we're using
265+
size_t pool_offset = 0; // Next offset within current block
266+
size_t memory_usage = 0; // Total memory usage
249267
std::vector<std::unique_ptr<char[]>> all_pools;
250268
std::vector<std::unique_ptr<char[]>> large_allocs;
251269
#ifdef USTRING_TRACK_NUM_LOOKUPS
252270
size_t num_lookups = 0;
253271
#endif
272+
OIIO_CACHE_ALIGN mutable ustring_mutex_t mutex;
254273
};
255274

256275
#if 0
@@ -290,10 +309,10 @@ struct UstringTable {
290309
return num;
291310
}
292311

293-
void clear()
312+
void free_resources()
294313
{
295314
for (auto& bin : bins)
296-
bin.clear();
315+
bin.free_resources();
297316
}
298317

299318
# ifdef USTRING_TRACK_NUM_LOOKUPS
@@ -749,7 +768,7 @@ static int ustring_cleanup_atexit_registered = []() {
749768
OIIO::print("ustring: freeing table resources ({} bytes)\n",
750769
v3_1::ustring_table().get_memory_usage());
751770
#endif
752-
v3_1::ustring_table().clear();
771+
v3_1::ustring_table().free_resources();
753772
v3_1::reverse_map().clear();
754773
}
755774
});

0 commit comments

Comments
 (0)