Improve macro table performance
In the existing implementation, when a new macro is added, the whole table has to be sorted again. Hence the cost of adding n macros is worse than O(n^2), due to arithmetic progression. This change drops all qsort(3) stuff altogether, by carefully preserving table in sorted order. In findEntry routine, bsearch(3) is replaced with customized binary search which tracks position for insertion. In the addMacro routine, if a matching entry is not found, this position is used for direct insertion, after the rest of the elements are "shifted to the right" with memmove(3). Likewise, in delMacro routine, the elements are shifted back to the left when the last macro definition is popped. Technically, shifting half of the array with memmove(3) is still O(n^2); however, modern CPUs process contiguous memory in a very efficient manner, and glibc provides a fine-tuned memmove(3) implementation. Also, macro table entries are now allocated in a single chunk. This change reduces rpm startup costs by factor of 6. Also, this change improves specfile parser performance by a factor of 2 (e.g. the parse time of texlive.spec is reduced from 67s to 35s). Signed-off-by: Panu Matilainen <pmatilai@redhat.com>
This commit is contained in:
parent
fec91c539c
commit
301d5450a1
359
rpmio/macro.c
359
rpmio/macro.c
|
@ -39,16 +39,15 @@ struct rpmMacroEntry_s {
|
|||
struct rpmMacroEntry_s *prev;/*!< Macro entry stack. */
|
||||
char *name; /*!< Macro name. */
|
||||
char *opts; /*!< Macro parameters (a la getopt) */
|
||||
char *body; /*!< Macro body. */
|
||||
int used; /*!< No. of expansions. */
|
||||
int level; /*!< Scoping level. */
|
||||
char body[]; /*!< Macro body. */
|
||||
};
|
||||
|
||||
/*! The structure used to store the set of macros in a context. */
|
||||
struct rpmMacroContext_s {
|
||||
rpmMacroEntry *macroTable; /*!< Macro entry table for context. */
|
||||
int macrosAllocated;/*!< No. of allocated macros. */
|
||||
int firstFree; /*!< No. of macros. */
|
||||
rpmMacroEntry *tab; /*!< Macro entry table (array of pointers). */
|
||||
int n; /*!< No. of macros. */
|
||||
};
|
||||
|
||||
|
||||
|
@ -80,98 +79,21 @@ static int print_macro_trace = _PRINT_MACRO_TRACE;
|
|||
#define _PRINT_EXPAND_TRACE 0
|
||||
static int print_expand_trace = _PRINT_EXPAND_TRACE;
|
||||
|
||||
#define MACRO_CHUNK_SIZE 16
|
||||
|
||||
/* forward ref */
|
||||
static int expandMacro(MacroBuf mb, const char *src, size_t slen);
|
||||
|
||||
/* =============================================================== */
|
||||
|
||||
/**
|
||||
* Compare macro entries by name (qsort/bsearch).
|
||||
* @param ap 1st macro entry
|
||||
* @param bp 2nd macro entry
|
||||
* @return result of comparison
|
||||
*/
|
||||
static int
|
||||
compareMacroName(const void * ap, const void * bp)
|
||||
{
|
||||
rpmMacroEntry ame = *((const rpmMacroEntry *)ap);
|
||||
rpmMacroEntry bme = *((const rpmMacroEntry *)bp);
|
||||
|
||||
if (ame == NULL && bme == NULL)
|
||||
return 0;
|
||||
if (ame == NULL)
|
||||
return 1;
|
||||
if (bme == NULL)
|
||||
return -1;
|
||||
return strcmp(ame->name, bme->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enlarge macro table.
|
||||
* @param mc macro context
|
||||
*/
|
||||
static void
|
||||
expandMacroTable(rpmMacroContext mc)
|
||||
{
|
||||
if (mc->macroTable == NULL) {
|
||||
mc->macrosAllocated = MACRO_CHUNK_SIZE;
|
||||
mc->macroTable = (rpmMacroEntry *)
|
||||
xmalloc(sizeof(*(mc->macroTable)) * mc->macrosAllocated);
|
||||
mc->firstFree = 0;
|
||||
} else {
|
||||
mc->macrosAllocated += MACRO_CHUNK_SIZE;
|
||||
mc->macroTable = (rpmMacroEntry *)
|
||||
xrealloc(mc->macroTable, sizeof(*(mc->macroTable)) *
|
||||
mc->macrosAllocated);
|
||||
}
|
||||
memset(&mc->macroTable[mc->firstFree], 0, MACRO_CHUNK_SIZE * sizeof(*(mc->macroTable)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort entries in macro table.
|
||||
* @param mc macro context
|
||||
*/
|
||||
static void
|
||||
sortMacroTable(rpmMacroContext mc)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (mc == NULL || mc->macroTable == NULL)
|
||||
return;
|
||||
|
||||
qsort(mc->macroTable, mc->firstFree, sizeof(*(mc->macroTable)),
|
||||
compareMacroName);
|
||||
|
||||
/* Empty pointers are now at end of table. Reset first free index. */
|
||||
for (i = 0; i < mc->firstFree; i++) {
|
||||
if (mc->macroTable[i] != NULL)
|
||||
continue;
|
||||
mc->firstFree = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rpmDumpMacroTable(rpmMacroContext mc, FILE * fp)
|
||||
{
|
||||
int nempty = 0;
|
||||
int nactive = 0;
|
||||
|
||||
if (mc == NULL) mc = rpmGlobalMacroContext;
|
||||
if (fp == NULL) fp = stderr;
|
||||
|
||||
fprintf(fp, "========================\n");
|
||||
if (mc->macroTable != NULL) {
|
||||
int i;
|
||||
for (i = 0; i < mc->firstFree; i++) {
|
||||
rpmMacroEntry me;
|
||||
if ((me = mc->macroTable[i]) == NULL) {
|
||||
/* XXX this should never happen */
|
||||
nempty++;
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < mc->n; i++) {
|
||||
rpmMacroEntry me = mc->tab[i];
|
||||
assert(me);
|
||||
fprintf(fp, "%3d%c %s", me->level,
|
||||
(me->used > 0 ? '=' : ':'), me->name);
|
||||
if (me->opts && *me->opts)
|
||||
|
@ -179,11 +101,9 @@ rpmDumpMacroTable(rpmMacroContext mc, FILE * fp)
|
|||
if (me->body && *me->body)
|
||||
fprintf(fp, "\t%s", me->body);
|
||||
fprintf(fp, "\n");
|
||||
nactive++;
|
||||
}
|
||||
}
|
||||
fprintf(fp, _("======================== active %d empty %d\n"),
|
||||
nactive, nempty);
|
||||
mc->n, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,33 +111,41 @@ rpmDumpMacroTable(rpmMacroContext mc, FILE * fp)
|
|||
* @param mc macro context
|
||||
* @param name macro name
|
||||
* @param namelen no. of bytes
|
||||
* @param pos found/insert position
|
||||
* @return address of slot in macro table with name (or NULL)
|
||||
*/
|
||||
static rpmMacroEntry *
|
||||
findEntry(rpmMacroContext mc, const char * name, size_t namelen)
|
||||
findEntry(rpmMacroContext mc, const char *name, size_t namelen, size_t *pos)
|
||||
{
|
||||
rpmMacroEntry key, *ret;
|
||||
struct rpmMacroEntry_s keybuf;
|
||||
char namebuf[namelen+1];
|
||||
const char *mname = name;
|
||||
|
||||
if (mc == NULL) mc = rpmGlobalMacroContext;
|
||||
if (mc->macroTable == NULL || mc->firstFree == 0)
|
||||
return NULL;
|
||||
|
||||
if (namelen > 0) {
|
||||
strncpy(namebuf, name, namelen);
|
||||
namebuf[namelen] = '\0';
|
||||
mname = namebuf;
|
||||
/* bsearch */
|
||||
int cmp = 1;
|
||||
size_t l = 0;
|
||||
size_t u = mc->n;
|
||||
size_t i = 0;
|
||||
while (l < u) {
|
||||
i = (l + u) / 2;
|
||||
rpmMacroEntry me = mc->tab[i];
|
||||
if (namelen == 0)
|
||||
cmp = strcmp(me->name, name);
|
||||
else {
|
||||
cmp = strncmp(me->name, name, namelen);
|
||||
/* longer me->name compares greater */
|
||||
if (cmp == 0)
|
||||
cmp = (me->name[namelen] != '\0');
|
||||
}
|
||||
if (cmp < 0)
|
||||
l = i + 1;
|
||||
else if (cmp > 0)
|
||||
u = i;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
key = &keybuf;
|
||||
memset(key, 0, sizeof(*key));
|
||||
key->name = (char *)mname;
|
||||
ret = (rpmMacroEntry *) bsearch(&key, mc->macroTable, mc->firstFree,
|
||||
sizeof(*(mc->macroTable)), compareMacroName);
|
||||
/* XXX TODO: find 1st empty slot and return that */
|
||||
return ret;
|
||||
if (pos)
|
||||
*pos = (cmp < 0) ? i + 1 : i;
|
||||
if (cmp == 0)
|
||||
return &mc->tab[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* =============================================================== */
|
||||
|
@ -664,58 +592,6 @@ exit:
|
|||
return se;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push new macro definition onto macro entry stack.
|
||||
* @param mep address of macro entry slot
|
||||
* @param n macro name
|
||||
* @param o macro parameters (NULL if none)
|
||||
* @param b macro body (NULL becomes "")
|
||||
* @param level macro recursion level
|
||||
*/
|
||||
static void
|
||||
pushMacro(rpmMacroEntry * mep,
|
||||
const char * n, const char * o,
|
||||
const char * b, int level)
|
||||
{
|
||||
rpmMacroEntry prev = (mep && *mep ? *mep : NULL);
|
||||
rpmMacroEntry me = (rpmMacroEntry) xmalloc(sizeof(*me));
|
||||
|
||||
me->prev = prev;
|
||||
me->name = (prev ? prev->name : xstrdup(n));
|
||||
me->opts = (o ? xstrdup(o) : NULL);
|
||||
me->body = xstrdup(b ? b : "");
|
||||
me->used = 0;
|
||||
me->level = level;
|
||||
if (mep)
|
||||
*mep = me;
|
||||
else
|
||||
me = _free(me);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop macro definition from macro entry stack.
|
||||
* @param mep address of macro entry slot
|
||||
*/
|
||||
static void
|
||||
popMacro(rpmMacroEntry * mep)
|
||||
{
|
||||
if (mep && *mep) {
|
||||
rpmMacroEntry me = *mep;
|
||||
|
||||
/* restore previous definition of the macro */
|
||||
*mep = me->prev;
|
||||
|
||||
/* name is shared between entries, only free if last of its kind */
|
||||
if (me->prev == NULL)
|
||||
free(me->name);
|
||||
free(me->opts);
|
||||
free(me->body);
|
||||
|
||||
memset(me, 0, sizeof(*me)); /* trash and burn */
|
||||
free(me);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Free parsed arguments for parameterized macro.
|
||||
* @param mb macro expansion state
|
||||
|
@ -724,21 +600,12 @@ static void
|
|||
freeArgs(MacroBuf mb)
|
||||
{
|
||||
rpmMacroContext mc = mb->mc;
|
||||
int ndeleted = 0;
|
||||
int i;
|
||||
|
||||
if (mc == NULL || mc->macroTable == NULL)
|
||||
return;
|
||||
|
||||
/* Delete dynamic macro definitions */
|
||||
for (i = 0; i < mc->firstFree; i++) {
|
||||
rpmMacroEntry *mep, me;
|
||||
for (int i = 0; i < mc->n; i++) {
|
||||
int skiptest = 0;
|
||||
mep = &mc->macroTable[i];
|
||||
me = *mep;
|
||||
|
||||
if (me == NULL) /* XXX this should never happen */
|
||||
continue;
|
||||
rpmMacroEntry me = mc->tab[i];
|
||||
assert(me);
|
||||
if (me->level < mb->depth)
|
||||
continue;
|
||||
if (strlen(me->name) == 1 && strchr("#*0", *me->name)) {
|
||||
|
@ -751,14 +618,11 @@ freeArgs(MacroBuf mb)
|
|||
me->name, me->body, me->level);
|
||||
#endif
|
||||
}
|
||||
popMacro(mep);
|
||||
if (!(mep && *mep))
|
||||
ndeleted++;
|
||||
/* compensate if the slot is to go away */
|
||||
if (me->prev == NULL)
|
||||
i--;
|
||||
delMacro(mc, me->name);
|
||||
}
|
||||
|
||||
/* If any deleted macros, sort macro table */
|
||||
if (ndeleted)
|
||||
sortMacroTable(mc);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1337,7 +1201,7 @@ expandMacro(MacroBuf mb, const char *src, size_t slen)
|
|||
}
|
||||
|
||||
/* Expand defined macros */
|
||||
mep = findEntry(mb->mc, f, fn);
|
||||
mep = findEntry(mb->mc, f, fn, NULL);
|
||||
me = (mep ? *mep : NULL);
|
||||
|
||||
/* XXX Special processing for flags */
|
||||
|
@ -1457,41 +1321,98 @@ void
|
|||
addMacro(rpmMacroContext mc,
|
||||
const char * n, const char * o, const char * b, int level)
|
||||
{
|
||||
rpmMacroEntry * mep;
|
||||
if (mc == NULL)
|
||||
mc = rpmGlobalMacroContext;
|
||||
|
||||
if (mc == NULL) mc = rpmGlobalMacroContext;
|
||||
/* new entry */
|
||||
rpmMacroEntry me;
|
||||
/* pointer into me */
|
||||
char *p;
|
||||
/* calculate sizes */
|
||||
size_t olen = o ? strlen(o) : 0;
|
||||
size_t blen = b ? strlen(b) : 0;
|
||||
size_t mesize = sizeof(*me) + blen + 1 + (olen ? olen + 1 : 0);
|
||||
|
||||
/* If new name, expand macro table */
|
||||
if ((mep = findEntry(mc, n, 0)) == NULL) {
|
||||
if (mc->firstFree == mc->macrosAllocated)
|
||||
expandMacroTable(mc);
|
||||
if (mc->macroTable != NULL)
|
||||
mep = mc->macroTable + mc->firstFree++;
|
||||
size_t pos;
|
||||
rpmMacroEntry *mep = findEntry(mc, n, 0, &pos);
|
||||
if (mep) {
|
||||
/* entry with shared name */
|
||||
me = xmalloc(mesize);
|
||||
/* copy body */
|
||||
p = me->body;
|
||||
if (blen)
|
||||
memcpy(p, b, blen + 1);
|
||||
else
|
||||
*p = '\0';
|
||||
p += blen + 1;
|
||||
/* set name */
|
||||
me->name = (*mep)->name;
|
||||
}
|
||||
else {
|
||||
/* extend macro table */
|
||||
const int delta = 256;
|
||||
if (mc->n % delta == 0)
|
||||
mc->tab = xrealloc(mc->tab, sizeof(me) * (mc->n + delta));
|
||||
/* shift pos+ entries to the right */
|
||||
memmove(mc->tab + pos + 1, mc->tab + pos, sizeof(me) * (mc->n - pos));
|
||||
mc->n++;
|
||||
/* make slot */
|
||||
mc->tab[pos] = NULL;
|
||||
mep = &mc->tab[pos];
|
||||
/* entry with new name */
|
||||
size_t nlen = strlen(n);
|
||||
me = xmalloc(mesize + nlen + 1);
|
||||
/* copy body */
|
||||
p = me->body;
|
||||
if (blen)
|
||||
memcpy(p, b, blen + 1);
|
||||
else
|
||||
*p = '\0';
|
||||
p += blen + 1;
|
||||
/* copy name */
|
||||
me->name = memcpy(p, n, nlen + 1);
|
||||
p += nlen + 1;
|
||||
}
|
||||
|
||||
if (mep != NULL) {
|
||||
/* Push macro over previous definition */
|
||||
pushMacro(mep, n, o, b, level);
|
||||
|
||||
/* If new name, sort macro table */
|
||||
if ((*mep)->prev == NULL)
|
||||
sortMacroTable(mc);
|
||||
}
|
||||
/* copy options */
|
||||
if (olen)
|
||||
me->opts = memcpy(p, o, olen + 1);
|
||||
else
|
||||
me->opts = o ? "" : NULL;
|
||||
/* initialize */
|
||||
me->used = 0;
|
||||
me->level = level;
|
||||
/* push over previous definition */
|
||||
me->prev = *mep;
|
||||
*mep = me;
|
||||
}
|
||||
|
||||
void
|
||||
delMacro(rpmMacroContext mc, const char * n)
|
||||
{
|
||||
rpmMacroEntry * mep;
|
||||
if (mc == NULL)
|
||||
mc = rpmGlobalMacroContext;
|
||||
|
||||
if (mc == NULL) mc = rpmGlobalMacroContext;
|
||||
/* If name exists, pop entry */
|
||||
if ((mep = findEntry(mc, n, 0)) != NULL) {
|
||||
popMacro(mep);
|
||||
/* If deleted name, sort macro table */
|
||||
if (!(mep && *mep))
|
||||
sortMacroTable(mc);
|
||||
size_t pos;
|
||||
rpmMacroEntry *mep = findEntry(mc, n, 0, &pos);
|
||||
if (mep == NULL)
|
||||
return;
|
||||
/* parting entry */
|
||||
rpmMacroEntry me = *mep;
|
||||
assert(me);
|
||||
/* detach/pop definition */
|
||||
mc->tab[pos] = me->prev;
|
||||
/* shrink macro table */
|
||||
if (me->prev == NULL) {
|
||||
mc->n--;
|
||||
/* move pos+ elements to the left */
|
||||
memmove(mc->tab + pos, mc->tab + pos + 1, sizeof(me) * (mc->n - pos));
|
||||
/* deallocate */
|
||||
if (mc->n == 0)
|
||||
mc->tab = _free(mc->tab);
|
||||
}
|
||||
/* comes in a single chunk */
|
||||
free(me);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -1513,18 +1434,11 @@ rpmLoadMacros(rpmMacroContext mc, int level)
|
|||
if (mc == NULL || mc == rpmGlobalMacroContext)
|
||||
return;
|
||||
|
||||
if (mc->macroTable != NULL) {
|
||||
int i;
|
||||
for (i = 0; i < mc->firstFree; i++) {
|
||||
rpmMacroEntry *mep, me;
|
||||
mep = &mc->macroTable[i];
|
||||
me = *mep;
|
||||
|
||||
if (me == NULL) /* XXX this should never happen */
|
||||
continue;
|
||||
for (int i = 0; i < mc->n; i++) {
|
||||
rpmMacroEntry me = mc->tab[i];
|
||||
assert(me);
|
||||
addMacro(NULL, me->name, me->opts, me->body, (level - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -1597,18 +1511,13 @@ rpmInitMacros(rpmMacroContext mc, const char * macrofiles)
|
|||
void
|
||||
rpmFreeMacros(rpmMacroContext mc)
|
||||
{
|
||||
|
||||
if (mc == NULL) mc = rpmGlobalMacroContext;
|
||||
|
||||
if (mc->macroTable != NULL) {
|
||||
for (int i = 0; i < mc->firstFree; i++) {
|
||||
while (mc->macroTable[i] != NULL) {
|
||||
popMacro(&mc->macroTable[i]);
|
||||
if (mc == NULL)
|
||||
mc = rpmGlobalMacroContext;
|
||||
while (mc->n > 0) {
|
||||
/* remove from the end to avoid memmove */
|
||||
rpmMacroEntry me = mc->tab[mc->n - 1];
|
||||
delMacro(mc, me->name);
|
||||
}
|
||||
}
|
||||
free(mc->macroTable);
|
||||
}
|
||||
memset(mc, 0, sizeof(*mc));
|
||||
}
|
||||
|
||||
char *
|
||||
|
|
Loading…
Reference in New Issue