Skip to content

Commit 5f4e8c7

Browse files
committed
Merged rg_bucket from master
rg_bucket is meant to avoid filling internal memory with tiny allocations (thousands of strdup -> a few 4096 blocks). Of course making a strdup that always allocates in external memory might have been a better/simpler idea...
1 parent cdc3ebf commit 5f4e8c7

File tree

5 files changed

+85
-10
lines changed

5 files changed

+85
-10
lines changed

components/retro-go/rg_gui.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,7 @@ typedef struct
10431043
{
10441044
rg_gui_option_t *options;
10451045
size_t count;
1046+
rg_bucket_t *filenames;
10461047
bool (*validator)(const char *path);
10471048
} file_picker_opts_t;
10481049

@@ -1054,7 +1055,7 @@ static int file_picker_cb(const rg_scandir_t *entry, void *arg)
10541055
rg_gui_option_t *options = realloc(f->options, (f->count + 2) * sizeof(rg_gui_option_t));
10551056
if (!options)
10561057
return RG_SCANDIR_STOP;
1057-
char *name = strdup(entry->basename);
1058+
char *name = rg_bucket_insert(f->filenames, entry->basename, strlen(entry->basename) + 1);
10581059
f->options = options;
10591060
f->options[f->count++] = (rg_gui_option_t){(intptr_t)name, name, NULL, RG_DIALOG_FLAG_NORMAL, NULL};
10601061
return RG_SCANDIR_CONTINUE;
@@ -1065,6 +1066,7 @@ char *rg_gui_file_picker(const char *title, const char *path, bool (*validator)(
10651066
file_picker_opts_t options = {
10661067
.options = calloc(8, sizeof(rg_gui_option_t)),
10671068
.count = 0,
1069+
.filenames = rg_bucket_create(4096),
10681070
.validator = validator,
10691071
};
10701072
char *filepath = NULL;
@@ -1098,8 +1100,7 @@ char *rg_gui_file_picker(const char *title, const char *path, bool (*validator)(
10981100
}
10991101

11001102
cleanup:
1101-
for (size_t i = 0; i < options.count; ++i)
1102-
free((void *)(options.options[i].arg));
1103+
rg_bucket_free(options.filenames);
11031104
free(options.options);
11041105
return filepath;
11051106
}

components/retro-go/rg_utils.c

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,63 @@ const char *rg_unique_string(const char *str)
351351
return obj->data;
352352
}
353353

354-
// Note: You should use calloc/malloc everywhere possible. This function is used to ensure
355-
// that some memory is put in specific regions for performance or hardware reasons.
356-
// Memory from this function should be freed with free()
354+
typedef struct rg_bucket_s
355+
{
356+
size_t capacity;
357+
size_t cursor;
358+
rg_bucket_t *prev;
359+
rg_bucket_t *next;
360+
uint8_t data[];
361+
} rg_bucket_t;
362+
363+
rg_bucket_t *rg_bucket_create(size_t capacity_bytes)
364+
{
365+
rg_bucket_t *bucket = calloc(1, sizeof(rg_bucket_t) + capacity_bytes);
366+
if (!bucket)
367+
return NULL;
368+
bucket->capacity = capacity_bytes;
369+
return bucket;
370+
}
371+
372+
void *rg_bucket_insert(rg_bucket_t *bucket, const void *item, size_t item_bytes)
373+
{
374+
if (!bucket || bucket->capacity < item_bytes)
375+
{
376+
RG_LOGW("Item size exceeds bucket capacity!");
377+
return NULL;
378+
}
379+
while (bucket->cursor + item_bytes > bucket->capacity)
380+
{
381+
if (!bucket->next) // End of the list, must allocate
382+
{
383+
rg_bucket_t *new_bucket = rg_bucket_create(bucket->capacity);
384+
if (!new_bucket)
385+
return NULL;
386+
new_bucket->prev = bucket;
387+
bucket->next = new_bucket;
388+
bucket = new_bucket;
389+
break;
390+
}
391+
bucket = bucket->next;
392+
}
393+
void *ptr = bucket->data + bucket->cursor;
394+
if (item)
395+
memcpy(ptr, item, item_bytes);
396+
bucket->cursor += ((item_bytes + (sizeof(int) - 1)) / sizeof(int)) * sizeof(int);
397+
// bucket->cursor += item_bytes;
398+
return ptr;
399+
}
400+
401+
void rg_bucket_free(rg_bucket_t *bucket)
402+
{
403+
while (bucket)
404+
{
405+
rg_bucket_t *next = bucket->next;
406+
free(bucket);
407+
bucket = next;
408+
}
409+
}
410+
357411
void *rg_alloc(size_t size, uint32_t caps)
358412
{
359413
char caps_list[36] = "";

components/retro-go/rg_utils.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
* they cannot be freed. unique avoids keeping multiple copies of an identical string (eg a path)
4848
* Things like unique_string("abc") == unique_string("abc") are guaranteed to be true
4949
*/
50-
const char *rg_const_string(const char *str);
5150
const char *rg_unique_string(const char *str);
5251
char *rg_strtolower(char *str);
5352
char *rg_strtoupper(char *str);
@@ -73,7 +72,19 @@ const char *rg_relpath(const char *path);
7372
uint32_t rg_crc32(uint32_t crc, const uint8_t *buf, size_t len);
7473
uint32_t rg_hash(const char *buf, size_t len);
7574

75+
/* Bucket allocator */
76+
// The bucket allocator is basically a linked-list of fixed-size buckets that can contain variable-sized items.
77+
// It does not keep track of individual items and items cannot be removed or resized.
78+
// Its purpose is to replace the thousands of small allocations that fill internal memory (eg strdup)
79+
typedef struct rg_bucket_s rg_bucket_t;
80+
rg_bucket_t *rg_bucket_create(size_t capacity_bytes);
81+
void *rg_bucket_insert(rg_bucket_t *bucket, const void *item, size_t item_bytes);
82+
void rg_bucket_free(rg_bucket_t *bucket);
83+
7684
/* Misc */
85+
// Note: You should use calloc/malloc everywhere possible. This function is used to ensure
86+
// that some memory is put in specific regions for performance or hardware reasons.
87+
// Memory from this function should be freed with free()
7788
void *rg_alloc(size_t size, uint32_t caps);
7889
// rg_usleep behaves like usleep in libc: it will sleep for *at least* `us` microseconds, but possibly more
7990
// due to scheduling. You should use rg_task_delay() if you don't need more than 10-15ms granularity.

launcher/main/applications.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,15 @@ static int scan_folder_cb(const rg_scandir_t *entry, void *arg)
6161
app->files_capacity = new_capacity;
6262
}
6363

64+
char *name = rg_bucket_insert(app->filenames, entry->basename, strlen(entry->basename) + 1);
65+
if (!name)
66+
{
67+
RG_LOGW("Ran out of memory for names, file scanning stopped at %d entries ...", app->files_count);
68+
return RG_SCANDIR_STOP;
69+
}
70+
6471
app->files[app->files_count++] = (retro_file_t) {
65-
.name = strdup(entry->basename),
72+
.name = name,
6673
.folder = rg_unique_string(entry->dirname),
6774
.checksum = 0,
6875
.missing_cover = 0,
@@ -417,8 +424,8 @@ static void event_handler(gui_event_t event, tab_t *tab)
417424
{
418425
if (app && app->initialized)
419426
{
420-
for (size_t i = 0; i < app->files_count; ++i)
421-
free((char *)app->files[i].name);
427+
rg_bucket_free(app->filenames);
428+
app->filenames = rg_bucket_create(4096);
422429
app->files_count = 0;
423430
app->initialized = false;
424431
}
@@ -669,6 +676,7 @@ static void application(const char *desc, const char *name, const char *exts, co
669676
app->available = rg_system_have_app(app->partition);
670677
app->files = calloc(100, sizeof(retro_file_t));
671678
app->files_capacity = 100;
679+
app->filenames = rg_bucket_create(4096);
672680
app->crc_offset = crc_offset;
673681

674682
gui_add_tab(app->short_name, app->description, app, event_handler);

launcher/main/applications.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ typedef struct retro_app_s
4040
retro_file_t *files;
4141
size_t files_capacity;
4242
size_t files_count;
43+
rg_bucket_t *filenames;
4344
bool use_crc_covers;
4445
bool initialized;
4546
bool available;

0 commit comments

Comments
 (0)