Speedup: new OldNewMap implementation for file loading

In production files that use a lot of linking I measured loading speedups between 5% and 18%. In files that use less linking the speedup might not be noticeable at all, but it should not be slower.

Reviewer: brecht

Differential Revision: https://developer.blender.org/D4038
This commit is contained in:
Jacques Lucke 2018-12-13 15:29:54 +01:00
parent fdab9a8ed1
commit 33993c056a
Notes: blender-bot 2023-02-14 05:32:45 +01:00
Referenced by issue #59572, Particle Index Number not working
Referenced by issue #59374, The UI overlays are flickering
Referenced by issue #59334, Blender crashes if an object has a subdivision surface modifier and a displacement modifier linked to vertex group when switching to weight paint mode
Referenced by issue #56033, Display mirror modifier in Edit Mode doesn't work (2.80 Alpha)
1 changed files with 129 additions and 187 deletions

View File

@ -117,6 +117,7 @@
#include "BLI_math.h"
#include "BLI_threads.h"
#include "BLI_mempool.h"
#include "BLI_ghash.h"
#include "BLT_translation.h"
@ -178,7 +179,6 @@
#include "readfile.h"
#include <errno.h>
/**
@ -246,21 +246,6 @@
# define DEBUG_PRINTF(...)
#endif
/***/
typedef struct OldNew {
const void *old;
void *newp;
int nr;
} OldNew;
typedef struct OldNewMap {
OldNew *entries;
int nentries, entriessize;
bool sorted;
int lasthit;
} OldNewMap;
/* local prototypes */
static void *read_struct(FileData *fd, BHead *bh, const char *blockname);
@ -306,49 +291,126 @@ static const char *library_parent_filepath(Library *lib)
return lib->parent ? lib->parent->filepath : "<direct>";
}
/* ************** OldNewMap ******************* */
typedef struct OldNew {
const void *oldp;
void *newp;
/* `nr` is "user count" for data, and ID code for libdata. */
int nr;
} OldNew;
typedef struct OldNewMap {
/* Array that stores the actual entries. */
OldNew *entries;
int nentries;
/* Hashmap that stores indices into the `entries` array. */
int32_t *map;
int capacity_exp;
} OldNewMap;
#define ENTRIES_CAPACITY(onm) (1 << (onm)->capacity_exp)
#define MAP_CAPACITY(onm) (1 << ((onm)->capacity_exp + 1))
#define SLOT_MASK(onm) (MAP_CAPACITY(onm) - 1)
#define DEFAULT_SIZE_EXP 6
#define PERTURB_SHIFT 5
/* based on the probing algorithm used in Python dicts. */
#define ITER_SLOTS(onm, KEY, SLOT_NAME, INDEX_NAME) \
uint32_t hash = BLI_ghashutil_ptrhash(KEY); \
uint32_t mask = SLOT_MASK(onm); \
uint perturb = hash; \
int SLOT_NAME = mask & hash; \
int INDEX_NAME = onm->map[SLOT_NAME]; \
for (;;SLOT_NAME = mask & ((5 * SLOT_NAME) + 1 + perturb), perturb >>= PERTURB_SHIFT, INDEX_NAME = onm->map[SLOT_NAME])
static void oldnewmap_insert_index_in_map(OldNewMap *onm, const void *ptr, int index)
{
ITER_SLOTS(onm, ptr, slot, stored_index) {
if (stored_index == -1) {
onm->map[slot] = index;
break;
}
}
}
static void oldnewmap_insert_or_replace(OldNewMap *onm, OldNew entry)
{
ITER_SLOTS(onm, entry.oldp, slot, index) {
if (index == -1) {
onm->entries[onm->nentries] = entry;
onm->map[slot] = onm->nentries;
onm->nentries++;
break;
}
else if (onm->entries[index].oldp == entry.oldp) {
onm->entries[index] = entry;
break;
}
}
}
static OldNew *oldnewmap_lookup_entry(const OldNewMap *onm, const void *addr)
{
ITER_SLOTS(onm, addr, slot, index) {
if (index >= 0) {
OldNew *entry = &onm->entries[index];
if (entry->oldp == addr) {
return entry;
}
}
else {
return NULL;
}
}
}
static void oldnewmap_clear_map(OldNewMap *onm)
{
memset(onm->map, 0xFF, MAP_CAPACITY(onm) * sizeof(*onm->map));
}
static void oldnewmap_increase_size(OldNewMap *onm)
{
onm->capacity_exp++;
onm->entries = MEM_reallocN(onm->entries, sizeof(*onm->entries) * ENTRIES_CAPACITY(onm));
onm->map = MEM_reallocN(onm->map, sizeof(*onm->map) * MAP_CAPACITY(onm));
oldnewmap_clear_map(onm);
for (int i = 0; i < onm->nentries; i++) {
oldnewmap_insert_index_in_map(onm, onm->entries[i].oldp, i);
}
}
/* Public OldNewMap API */
static OldNewMap *oldnewmap_new(void)
{
OldNewMap *onm = MEM_callocN(sizeof(*onm), "OldNewMap");
onm->entriessize = 1024;
onm->entries = MEM_malloc_arrayN(onm->entriessize, sizeof(*onm->entries), "OldNewMap.entries");
onm->capacity_exp = DEFAULT_SIZE_EXP;
onm->entries = MEM_malloc_arrayN(ENTRIES_CAPACITY(onm), sizeof(*onm->entries), "OldNewMap.entries");
onm->map = MEM_malloc_arrayN(MAP_CAPACITY(onm), sizeof(*onm->map), "OldNewMap.map");
oldnewmap_clear_map(onm);
return onm;
}
static int verg_oldnewmap(const void *v1, const void *v2)
{
const struct OldNew *x1 = v1, *x2 = v2;
if (x1->old > x2->old) return 1;
else if (x1->old < x2->old) return -1;
return 0;
}
static void oldnewmap_sort(FileData *fd)
{
BLI_assert(fd->libmap->sorted == false);
qsort(fd->libmap->entries, fd->libmap->nentries, sizeof(OldNew), verg_oldnewmap);
fd->libmap->sorted = 1;
}
/* nr is zero for data, and ID code for libdata */
static void oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr, int nr)
{
OldNew *entry;
if (oldaddr == NULL || newaddr == NULL) return;
if (UNLIKELY(onm->nentries == onm->entriessize)) {
onm->entriessize *= 2;
onm->entries = MEM_reallocN(onm->entries, sizeof(*onm->entries) * onm->entriessize);
if (UNLIKELY(onm->nentries == ENTRIES_CAPACITY(onm))) {
oldnewmap_increase_size(onm);
}
entry = &onm->entries[onm->nentries++];
entry->old = oldaddr;
entry->newp = newaddr;
entry->nr = nr;
OldNew entry;
entry.oldp = oldaddr;
entry.newp = newaddr;
entry.nr = nr;
oldnewmap_insert_or_replace(onm, entry);
}
void blo_do_versions_oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void *newaddr, int nr)
@ -356,125 +418,28 @@ void blo_do_versions_oldnewmap_insert(OldNewMap *onm, const void *oldaddr, void
oldnewmap_insert(onm, oldaddr, newaddr, nr);
}
/**
* Do a full search (no state).
*
* \param lasthit: Use as a reference position to avoid a full search
* from either end of the array, giving more efficient lookups.
*
* \note This would seem an ideal case for hash or btree lookups.
* However the data is written in-order, using the \a lasthit will normally avoid calling this function.
* Creating a btree/hash structure adds overhead for the common-case to optimize the corner-case
* (since most entries will never be retrieved).
* So just keep full lookups as a fall-back.
*/
static int oldnewmap_lookup_entry_full(const OldNewMap *onm, const void *addr, int lasthit)
{
const int nentries = onm->nentries;
const OldNew *entries = onm->entries;
int i;
/* search relative to lasthit where possible */
if (lasthit >= 0 && lasthit < nentries) {
/* search forwards */
i = lasthit;
while (++i != nentries) {
if (entries[i].old == addr) {
return i;
}
}
/* search backwards */
i = lasthit + 1;
while (i--) {
if (entries[i].old == addr) {
return i;
}
}
}
else {
/* search backwards (full) */
i = nentries;
while (i--) {
if (entries[i].old == addr) {
return i;
}
}
}
return -1;
}
static void *oldnewmap_lookup_and_inc(OldNewMap *onm, const void *addr, bool increase_users)
{
int i;
if (addr == NULL) return NULL;
if (onm->lasthit < onm->nentries - 1) {
OldNew *entry = &onm->entries[++onm->lasthit];
if (entry->old == addr) {
if (increase_users)
entry->nr++;
return entry->newp;
}
}
i = oldnewmap_lookup_entry_full(onm, addr, onm->lasthit);
if (i != -1) {
OldNew *entry = &onm->entries[i];
BLI_assert(entry->old == addr);
onm->lasthit = i;
if (increase_users)
entry->nr++;
return entry->newp;
}
return NULL;
OldNew *entry = oldnewmap_lookup_entry(onm, addr);
if (entry == NULL) return NULL;
if (increase_users) entry->nr++;
return entry->newp;
}
/* for libdata, nr has ID code, no increment */
/* for libdata, OldNew.nr has ID code, no increment */
static void *oldnewmap_liblookup(OldNewMap *onm, const void *addr, const void *lib)
{
if (addr == NULL) {
return NULL;
}
/* lasthit works fine for non-libdata, linking there is done in same sequence as writing */
if (onm->sorted) {
const OldNew entry_s = {.old = addr};
OldNew *entry = bsearch(&entry_s, onm->entries, onm->nentries, sizeof(OldNew), verg_oldnewmap);
if (entry) {
ID *id = entry->newp;
if (id && (!lib || id->lib)) {
return id;
}
}
}
else {
/* note, this can be a bottle neck when loading some files */
const int i = oldnewmap_lookup_entry_full(onm, addr, -1);
if (i != -1) {
OldNew *entry = &onm->entries[i];
ID *id = entry->newp;
BLI_assert(entry->old == addr);
if (id && (!lib || id->lib)) {
return id;
}
}
}
if (addr == NULL) return NULL;
ID *id = oldnewmap_lookup_and_inc(onm, addr, false);
if (id == NULL) return NULL;
if (!lib || id->lib) return id;
return NULL;
}
static void oldnewmap_free_unused(OldNewMap *onm)
{
int i;
for (i = 0; i < onm->nentries; i++) {
for (int i = 0; i < onm->nentries; i++) {
OldNew *entry = &onm->entries[i];
if (entry->nr == 0) {
MEM_freeN(entry->newp);
@ -485,16 +450,25 @@ static void oldnewmap_free_unused(OldNewMap *onm)
static void oldnewmap_clear(OldNewMap *onm)
{
onm->capacity_exp = DEFAULT_SIZE_EXP;
oldnewmap_clear_map(onm);
onm->nentries = 0;
onm->lasthit = 0;
}
static void oldnewmap_free(OldNewMap *onm)
{
MEM_freeN(onm->entries);
MEM_freeN(onm->map);
MEM_freeN(onm);
}
#undef ENTRIES_CAPACITY
#undef MAP_CAPACITY
#undef SLOT_MASK
#undef DEFAULT_SIZE_EXP
#undef PERTURB_SHIFT
#undef ITER_SLOTS
/***/
static void read_libraries(FileData *basefd, ListBase *mainlist);
@ -1486,31 +1460,6 @@ static void *newdataadr(FileData *fd, const void *adr) /* only direct datab
return oldnewmap_lookup_and_inc(fd->datamap, adr, true);
}
/* This is a special version of newdataadr() which allows us to keep lasthit of
* map unchanged. In certain cases this makes file loading time significantly
* faster.
*
* Use this function in cases like restoring pointer from one list element to
* another list element, but keep lasthit value so we can continue restoring
* pointers efficiently.
*
* Example of this could be found in direct_link_fcurves() which restores the
* fcurve group pointer and keeps lasthit optimal for linking all further
* fcurves.
*/
static void *newdataadr_ex(FileData *fd, const void *adr, bool increase_lasthit) /* only direct databocks */
{
if (increase_lasthit) {
return newdataadr(fd, adr);
}
else {
int lasthit = fd->datamap->lasthit;
void *newadr = newdataadr(fd, adr);
fd->datamap->lasthit = lasthit;
return newadr;
}
}
static void *newdataadr_no_us(FileData *fd, const void *adr) /* only direct databocks */
{
return oldnewmap_lookup_and_inc(fd->datamap, adr, false);
@ -1593,12 +1542,7 @@ static void *newlibadr_real_us(FileData *fd, const void *lib, const void *adr)
static void change_idid_adr_fd(FileData *fd, const void *old, void *new)
{
int i;
/* use a binary search if we have a sorted libmap, for now it's not needed. */
BLI_assert(fd->libmap->sorted == false);
for (i = 0; i < fd->libmap->nentries; i++) {
for (int i = 0; i < fd->libmap->nentries; i++) {
OldNew *entry = &fd->libmap->entries[i];
if (old == entry->newp && entry->nr == ID_ID) {
@ -2669,7 +2613,7 @@ static void direct_link_fcurves(FileData *fd, ListBase *list)
fcu->rna_path = newdataadr(fd, fcu->rna_path);
/* group */
fcu->grp = newdataadr_ex(fd, fcu->grp, false);
fcu->grp = newdataadr(fd, fcu->grp);
/* clear disabled flag - allows disabled drivers to be tried again ([#32155]),
* but also means that another method for "reviving disabled F-Curves" exists
@ -8865,8 +8809,6 @@ static void do_versions_after_linking(Main *main)
static void lib_link_all(FileData *fd, Main *main)
{
oldnewmap_sort(fd);
lib_link_id(fd, main);
/* No load UI for undo memfiles */