Yet another representation for arrays

This "linear" representation (see ltable.h for details) has worse
locality than cells, but the simpler access code seems to compensate
that.
This commit is contained in:
Roberto Ierusalimschy
2024-04-05 15:35:11 -03:00
parent 3507c3380f
commit 5edacafcfa
3 changed files with 65 additions and 44 deletions

View File

@@ -773,15 +773,12 @@ typedef union Node {
#define setnorealasize(t) ((t)->flags |= BITRAS) #define setnorealasize(t) ((t)->flags |= BITRAS)
typedef struct ArrayCell ArrayCell;
typedef struct Table { typedef struct Table {
CommonHeader; CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */ lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of 'node' array */ lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int alimit; /* "limit" of 'array' array */ unsigned int alimit; /* "limit" of 'array' array */
ArrayCell *array; /* array part */ Value *array; /* array part */
Node *node; Node *node;
struct Table *metatable; struct Table *metatable;
GCObject *gclist; GCObject *gclist;

View File

@@ -25,6 +25,7 @@
#include <math.h> #include <math.h>
#include <limits.h> #include <limits.h>
#include <string.h>
#include "lua.h" #include "lua.h"
@@ -73,7 +74,7 @@ typedef union {
** MAXASIZEB is the maximum number of elements in the array part such ** MAXASIZEB is the maximum number of elements in the array part such
** that the size of the array fits in 'size_t'. ** that the size of the array fits in 'size_t'.
*/ */
#define MAXASIZEB ((MAX_SIZET/sizeof(ArrayCell)) * NM) #define MAXASIZEB (MAX_SIZET/(sizeof(Value) + 1))
/* /*
@@ -553,26 +554,52 @@ static int numusehash (const Table *t, unsigned *nums, unsigned *pna) {
/* /*
** Convert an "abstract size" (number of slots in an array) to ** Convert an "abstract size" (number of slots in an array) to
** "concrete size" (number of bytes in the array). ** "concrete size" (number of bytes in the array).
** If the abstract size is not a multiple of NM, the last cell is
** incomplete, so we don't need to allocate memory for the whole cell.
** 'extra' computes how many values are not needed in that last cell.
** It will be zero when 'size' is a multiple of NM, and from there it
** increases as 'size' decreases, up to (NM - 1).
*/ */
static size_t concretesize (unsigned int size) { static size_t concretesize (unsigned int size) {
unsigned int numcells = (size + NM - 1) / NM; /* (size / NM) rounded up */ return size * sizeof(Value) + size; /* space for the two arrays */
unsigned int extra = NM - 1 - ((size + NM - 1) % NM);
return numcells * sizeof(ArrayCell) - extra * sizeof(Value);
} }
static ArrayCell *resizearray (lua_State *L , Table *t, /*
** Resize the array part of a table. If new size is equal to the old,
** do nothing. Else, if new size is zero, free the old array. (It must
** be present, as the sizes are different.) Otherwise, allocate a new
** array, move the common elements to new proper position, and then
** frees old array.
** When array grows, we could reallocate it, but we still would need
** to move the elements to their new position, so the copy implicit
** in realloc is a waste. When array shrinks, it always erases some
** elements that should still be in the array, so we must reallocate in
** two steps anyway. It is simpler to always reallocate in two steps.
*/
static Value *resizearray (lua_State *L , Table *t,
unsigned oldasize, unsigned oldasize,
unsigned newasize) { unsigned newasize) {
size_t oldasizeb = concretesize(oldasize); if (oldasize == newasize)
size_t newasizeb = concretesize(newasize); return t->array; /* nothing to be done */
void *a = luaM_reallocvector(L, t->array, oldasizeb, newasizeb, lu_byte); else if (newasize == 0) { /* erasing array? */
return cast(ArrayCell*, a); Value *op = t->array - oldasize; /* original array's real address */
luaM_freemem(L, op, concretesize(oldasize)); /* free it */
return NULL;
}
else {
size_t newasizeb = concretesize(newasize);
Value *np = cast(Value *,
luaM_reallocvector(L, NULL, 0, newasizeb, lu_byte));
if (np == NULL) /* allocation error? */
return NULL;
if (oldasize > 0) {
Value *op = t->array - oldasize; /* real original array */
unsigned tomove = (oldasize < newasize) ? oldasize : newasize;
lua_assert(tomove > 0);
/* move common elements to new position */
memcpy(np + newasize - tomove,
op + oldasize - tomove,
concretesize(tomove));
luaM_freemem(L, op, concretesize(oldasize));
}
return np + newasize; /* shift pointer to the end of value segment */
}
} }
@@ -699,7 +726,7 @@ void luaH_resize (lua_State *L, Table *t, unsigned newasize,
unsigned nhsize) { unsigned nhsize) {
Table newt; /* to keep the new hash part */ Table newt; /* to keep the new hash part */
unsigned int oldasize = setlimittosize(t); unsigned int oldasize = setlimittosize(t);
ArrayCell *newarray; Value *newarray;
if (newasize > MAXASIZE) if (newasize > MAXASIZE)
luaG_runerror(L, "table overflow"); luaG_runerror(L, "table overflow");
/* create new hash part with appropriate size into 'newt' */ /* create new hash part with appropriate size into 'newt' */
@@ -777,18 +804,12 @@ Table *luaH_new (lua_State *L) {
/* /*
** Frees a table. The assert ensures the correctness of 'concretesize', ** Frees a table.
** checking its result against the address of the last element in the
** array part of the table, computed abstractly.
*/ */
void luaH_free (lua_State *L, Table *t) { void luaH_free (lua_State *L, Table *t) {
unsigned int realsize = luaH_realasize(t); unsigned int realsize = luaH_realasize(t);
size_t sizeb = concretesize(realsize);
lua_assert((sizeb == 0 && realsize == 0) ||
cast_charp(t->array) + sizeb - sizeof(Value) ==
cast_charp(getArrVal(t, realsize - 1)));
freehash(L, t); freehash(L, t);
luaM_freemem(L, t->array, sizeb); resizearray(L, t, realsize, 0);
luaM_free(L, t); luaM_free(L, t);
} }

View File

@@ -71,7 +71,7 @@
/* /*
** 'luaH_get*' operations set 'res', unless the value is absent, and ** 'luaH_get*' operations set 'res', unless the value is absent, and
** return the tag of the result, ** return the tag of the result.
** The 'luaH_pset*' (pre-set) operations set the given value and return ** The 'luaH_pset*' (pre-set) operations set the given value and return
** HOK, unless the original value is absent. In that case, if the key ** HOK, unless the original value is absent. In that case, if the key
** is really absent, they return HNOTFOUND. Otherwise, if there is a ** is really absent, they return HNOTFOUND. Otherwise, if there is a
@@ -86,24 +86,27 @@
/* /*
** The array part of a table is represented by an array of cells. ** The array part of a table is represented by an inverted array of
** Each cell is composed of NM tags followed by NM values, so that ** values followed by an array of tags, to avoid wasting space with
** no space is wasted in padding. The last cell may be incomplete, ** padding. The 'array' pointer points to the junction of the two
** that is, it may have fewer than NM values. ** arrays, so that values are indexed with negative indices and tags
** with non-negative indices.
Values Tags
--------------------------------------------------------
... | Value 1 | Value 0 |0|1|...
--------------------------------------------------------
^ t->array
** All accesses to 't->array' should be through the macros 'getArrTag'
** and 'getArrVal'.
*/ */
#define NM cast_uint(sizeof(Value))
struct ArrayCell {
lu_byte tag[NM];
Value value[NM];
};
/* Computes the address of the tag for the abstract index 'k' */ /* Computes the address of the tag for the abstract index 'k' */
#define getArrTag(t,k) (&(t)->array[(k)/NM].tag[(k)%NM]) #define getArrTag(t,k) (cast(lu_byte*, (t)->array) + (k))
/* Computes the address of the value for the abstract index 'k' */ /* Computes the address of the value for the abstract index 'k' */
#define getArrVal(t,k) (&(t)->array[(k)/NM].value[(k)%NM]) #define getArrVal(t,k) ((t)->array - 1 - (k))
/* /*
@@ -117,9 +120,9 @@ struct ArrayCell {
/* /*
** Often, we need to check the tag of a value before moving it. These ** Often, we need to check the tag of a value before moving it. The
** macros also move TValues to/from arrays, but receive the precomputed ** following macros also move TValues to/from arrays, but receive the
** tag value or address as an extra argument. ** precomputed tag value or address as an extra argument.
*/ */
#define farr2val(h,k,tag,res) \ #define farr2val(h,k,tag,res) \
((res)->tt_ = tag, (res)->value_ = *getArrVal(h,(k)-1u)) ((res)->tt_ = tag, (res)->value_ = *getArrVal(h,(k)-1u))