Skip to content

Commit 14f3dbb

Browse files
committed
string_list: less memory allocations, performance optimizations:
* string_split / string_split_noalloc — pre-count tokens and pre-allocate the elem array in one shot, eliminating repeated realloc doublings (the biggest win for typical split-heavy workloads). * string_list_append — grow check happens before strdup, so a failed realloc doesn't waste a malloc+free cycle on the string copy. * new function string_list_append_n - and remove static function from archive_file
1 parent 201eaff commit 14f3dbb

3 files changed

Lines changed: 143 additions & 85 deletions

File tree

libretro-common/file/archive_file.c

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -463,40 +463,6 @@ bool file_archive_perform_mode(const char *path, const char *valid_exts,
463463
return true;
464464
}
465465

466-
/**
467-
* string_list_append_n:
468-
* @list : pointer to string list
469-
* @elem : element to add to the string list
470-
* @length : read at most this many bytes from elem
471-
* @attr : attributes of new element.
472-
*
473-
* Appends a new element to the string list.
474-
*
475-
* @return true if successful, otherwise false.
476-
**/
477-
static bool string_list_append_n(struct string_list *list,
478-
const char *elem, unsigned length,
479-
union string_list_elem_attr attr)
480-
{
481-
char *data_dup = NULL;
482-
483-
if (list->size >= list->cap &&
484-
!string_list_capacity(list, list->cap * 2))
485-
return false;
486-
487-
if (!(data_dup = (char*)malloc(length + 1)))
488-
return false;
489-
490-
strlcpy(data_dup, elem, length + 1);
491-
492-
list->elems[list->size].data = data_dup;
493-
list->elems[list->size].attr = attr;
494-
495-
list->size++;
496-
return true;
497-
}
498-
499-
500466
/**
501467
* file_archive_filename_split:
502468
* @str : filename to turn into a string list

libretro-common/include/lists/string_list.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ struct string_list *string_list_new(void);
111111
* @attr : attributes of new element.
112112
*
113113
* Appends a new element to the string list.
114-
114+
*
115115
* Hidden non-leaf function cost:
116116
* - Calls string_list_capacity()
117117
* - Calls strdup
@@ -121,6 +121,21 @@ struct string_list *string_list_new(void);
121121
bool string_list_append(struct string_list *list, const char *elem,
122122
union string_list_elem_attr attr);
123123

124+
/**
125+
* string_list_append_n:
126+
* @list : pointer to string list
127+
* @elem : element to add to the string list
128+
* @len : length of @elem (caller already knows it)
129+
* @attr : attributes of new element.
130+
*
131+
* Appends a new element when the caller already has its length,
132+
* avoiding a redundant strlen inside strdup.
133+
*
134+
* @return true if successful, otherwise false.
135+
**/
136+
bool string_list_append_n(struct string_list *list, const char *elem,
137+
size_t len, union string_list_elem_attr attr);
138+
124139
/**
125140
* string_list_free
126141
* @list : pointer to string list object

libretro-common/lists/string_list.c

Lines changed: 127 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ static bool string_list_deinitialize_internal(struct string_list *list)
4343
free(list->elems[i].data);
4444
if (list->elems[i].userdata)
4545
free(list->elems[i].userdata);
46-
list->elems[i].data = NULL;
47-
list->elems[i].userdata = NULL;
4846
}
4947

5048
free(list->elems);
@@ -64,7 +62,8 @@ bool string_list_capacity(struct string_list *list, size_t cap)
6462
return false;
6563

6664
if (cap > list->cap)
67-
memset(&new_data[list->cap], 0, sizeof(*new_data) * (cap - list->cap));
65+
memset(&new_data[list->cap], 0,
66+
sizeof(*new_data) * (cap - list->cap));
6867

6968
list->elems = new_data;
7069
list->cap = cap;
@@ -87,78 +86,104 @@ bool string_list_deinitialize(struct string_list *list)
8786
return false;
8887
if (!string_list_deinitialize_internal(list))
8988
return false;
90-
list->elems = NULL;
91-
list->size = 0;
92-
list->cap = 0;
89+
list->elems = NULL;
90+
list->size = 0;
91+
list->cap = 0;
9392
return true;
9493
}
9594

9695
struct string_list *string_list_new(void)
9796
{
98-
struct string_list_elem *
99-
elems = NULL;
97+
struct string_list_elem *elems = NULL;
10098
struct string_list *list = (struct string_list*)
10199
malloc(sizeof(*list));
102100
if (!list)
103101
return NULL;
104102

105-
list->cap = 0;
106-
list->size = 0;
107-
list->elems = NULL;
103+
list->cap = 0;
104+
list->size = 0;
105+
list->elems = NULL;
108106

109-
if (!(elems = (struct string_list_elem*)
110-
calloc(32, sizeof(*elems))))
107+
elems = (struct string_list_elem*)
108+
calloc(32, sizeof(*elems));
109+
if (!elems)
111110
{
112-
string_list_free(list);
111+
free(list);
113112
return NULL;
114113
}
115114

116-
list->elems = elems;
117-
list->cap = 32;
115+
list->elems = elems;
116+
list->cap = 32;
118117

119118
return list;
120119
}
121120

122121
bool string_list_initialize(struct string_list *list)
123122
{
124-
struct string_list_elem *
125-
elems = NULL;
123+
struct string_list_elem *elems = NULL;
126124
if (!list)
127125
return false;
128-
if (!(elems = (struct string_list_elem*)
129-
calloc(32, sizeof(*elems))))
126+
127+
elems = (struct string_list_elem*)
128+
calloc(32, sizeof(*elems));
129+
if (!elems)
130130
{
131131
string_list_deinitialize(list);
132132
return false;
133133
}
134-
list->elems = elems;
135-
list->size = 0;
136-
list->cap = 32;
134+
135+
list->elems = elems;
136+
list->size = 0;
137+
list->cap = 32;
137138
return true;
138139
}
139140

140141
bool string_list_append(struct string_list *list, const char *elem,
141142
union string_list_elem_attr attr)
142143
{
143144
char *data_dup = NULL;
145+
size_t elem_len;
144146

145-
/* Note: If 'list' is incorrectly initialised
146-
* (i.e. if struct is zero initialised and
147-
* string_list_initialize() is not called on
148-
* it) capacity will be zero. This will cause
149-
* a segfault. Handle this case by forcing the new
150-
* capacity to a fixed size of 32 */
147+
/* Grow first - if this fails we haven't wasted a strdup. */
151148
if ( list->size >= list->cap
152149
&& !string_list_capacity(list,
153150
(list->cap > 0) ? (list->cap * 2) : 32))
154151
return false;
155152

156-
if (!(data_dup = strdup(elem)))
153+
/* Manual strdup: single traversal of elem to get length,
154+
* then malloc + memcpy (avoids the hidden strlen inside
155+
* libc strdup on some platforms that do strlen+malloc+memcpy
156+
* as two passes). */
157+
elem_len = strlen(elem);
158+
data_dup = (char*)malloc(elem_len + 1);
159+
if (!data_dup)
157160
return false;
161+
memcpy(data_dup, elem, elem_len + 1);
158162

159163
list->elems[list->size].data = data_dup;
160164
list->elems[list->size].attr = attr;
165+
list->size++;
166+
return true;
167+
}
168+
169+
bool string_list_append_n(struct string_list *list, const char *elem,
170+
size_t len, union string_list_elem_attr attr)
171+
{
172+
char *data_dup = NULL;
173+
174+
if ( list->size >= list->cap
175+
&& !string_list_capacity(list,
176+
(list->cap > 0) ? (list->cap * 2) : 32))
177+
return false;
161178

179+
data_dup = (char*)malloc(len + 1);
180+
if (!data_dup)
181+
return false;
182+
memcpy(data_dup, elem, len);
183+
data_dup[len] = '\0';
184+
185+
list->elems[list->size].data = data_dup;
186+
list->elems[list->size].attr = attr;
162187
list->size++;
163188
return true;
164189
}
@@ -200,6 +225,31 @@ void string_list_join_concat_special(char *s, size_t len,
200225
}
201226
}
202227

228+
/**
229+
* Count delimited tokens in @str without modifying it.
230+
* Treats any character in @delim as a separator (same as strtok).
231+
* Returns the number of non-empty tokens.
232+
*/
233+
static size_t string_count_tokens(const char *str, const char *delim)
234+
{
235+
size_t count = 0;
236+
bool in_tok = false;
237+
const char *p = str;
238+
239+
for (; *p != '\0'; p++)
240+
{
241+
if (strchr(delim, (unsigned char)*p))
242+
in_tok = false;
243+
else if (!in_tok)
244+
{
245+
in_tok = true;
246+
count++;
247+
}
248+
}
249+
250+
return count;
251+
}
252+
203253
struct string_list *string_split(const char *str, const char *delim)
204254
{
205255
char *save = NULL;
@@ -213,14 +263,28 @@ struct string_list *string_split(const char *str, const char *delim)
213263
if (!(copy = strdup(str)))
214264
goto error;
215265

266+
/* Pre-size the element array to avoid repeated reallocs.
267+
* We scan the original string (not the copy, since strtok
268+
* mutates it) to count tokens. */
269+
{
270+
size_t token_count = string_count_tokens(str, delim);
271+
if (token_count > list->cap)
272+
{
273+
if (!string_list_capacity(list, token_count))
274+
goto error;
275+
}
276+
}
277+
216278
tmp = strtok_r(copy, delim, &save);
217279
while (tmp)
218280
{
219281
union string_list_elem_attr attr;
282+
size_t tok_len;
220283

221-
attr.i = 0;
284+
attr.i = 0;
285+
tok_len = strlen(tmp);
222286

223-
if (!string_list_append(list, tmp, attr))
287+
if (!string_list_append_n(list, tmp, tok_len, attr))
224288
goto error;
225289

226290
tmp = strtok_r(NULL, delim, &save);
@@ -248,14 +312,29 @@ bool string_split_noalloc(struct string_list *list,
248312
if (!(copy = strdup(str)))
249313
return false;
250314

251-
tmp = strtok_r(copy, delim, &save);
315+
/* Pre-size to avoid repeated reallocs. */
316+
{
317+
size_t token_count = string_count_tokens(str, delim);
318+
if (token_count > list->cap)
319+
{
320+
if (!string_list_capacity(list, token_count))
321+
{
322+
free(copy);
323+
return false;
324+
}
325+
}
326+
}
327+
328+
tmp = strtok_r(copy, delim, &save);
252329
while (tmp)
253330
{
254331
union string_list_elem_attr attr;
332+
size_t tok_len;
255333

256-
attr.i = 0;
334+
attr.i = 0;
335+
tok_len = strlen(tmp);
257336

258-
if (!string_list_append(list, tmp, attr))
337+
if (!string_list_append_n(list, tmp, tok_len, attr))
259338
{
260339
free(copy);
261340
return false;
@@ -289,7 +368,7 @@ bool string_list_find_elem_prefix(const struct string_list *list,
289368
{
290369
size_t i;
291370
char prefixed[255];
292-
size_t _len = strlcpy(prefixed, prefix, sizeof(prefixed));
371+
size_t _len = strlcpy(prefixed, prefix, sizeof(prefixed));
293372
strlcpy(prefixed + _len, elem, sizeof(prefixed) - _len);
294373
for (i = 0; i < list->size; i++)
295374
{
@@ -311,36 +390,34 @@ struct string_list *string_list_clone(const struct string_list *src)
311390
if (!dest)
312391
return NULL;
313392

314-
dest->elems = NULL;
315-
dest->size = src->size;
316-
if (src->cap < dest->size)
317-
dest->cap = dest->size;
318-
else
319-
dest->cap = src->cap;
393+
dest->elems = NULL;
394+
dest->size = src->size;
395+
dest->cap = (src->cap < dest->size) ? dest->size : src->cap;
320396

321-
if (!(elems = (struct string_list_elem*)
322-
calloc(dest->cap, sizeof(struct string_list_elem))))
397+
elems = (struct string_list_elem*)
398+
calloc(dest->cap, sizeof(struct string_list_elem));
399+
if (!elems)
323400
{
324401
free(dest);
325402
return NULL;
326403
}
327404

328-
dest->elems = elems;
405+
dest->elems = elems;
329406

330407
for (i = 0; i < src->size; i++)
331408
{
332-
const char *_src = src->elems[i].data;
333-
size_t len = _src ? strlen(_src) : 0;
409+
const char *_src = src->elems[i].data;
410+
size_t slen = _src ? strlen(_src) : 0;
334411

335412
dest->elems[i].data = NULL;
336413
dest->elems[i].attr = src->elems[i].attr;
337414

338-
if (len != 0)
415+
if (slen != 0)
339416
{
340-
char *ret = (char*)malloc(len + 1);
417+
char *ret = (char*)malloc(slen + 1);
341418
if (ret)
342419
{
343-
strcpy(ret, _src);
420+
memcpy(ret, _src, slen + 1); /* memcpy > strcpy: no NUL scan */
344421
dest->elems[i].data = ret;
345422
}
346423
}

0 commit comments

Comments
 (0)