Use thread-local hashes instead of per-function hashes to cache the results of invoking generic functions

This commit is contained in:
jgarcia 2008-01-06 15:44:04 +00:00
parent 26d819ad80
commit 4356e84222
12 changed files with 274 additions and 206 deletions

View file

@ -12,6 +12,11 @@ ECL 0.9k:
- Improved hashing on 64-bit machines.
* CLOS:
- When caching generic function calls, ECL now uses a thread-local hash table
instead of one hash table per generic function.
;;; Local Variables: ***
;;; mode:text ***
;;; fill-column:79 ***

View file

@ -15,6 +15,54 @@
#include <string.h>
#include <ecl/ecl.h>
#include <ecl/internal.h>
#include "newhash.h"
static cl_object
do_clear_gfun_hash(cl_object target)
{
cl_object table = cl_env.gfun_hash;
if (target == Ct) {
cl_clrhash(table);
} else {
cl_index hsize = table->hash.size;
struct ecl_hashtable_entry *htable = table->hash.data;
for (; hsize; htable++, hsize--) {
if (htable->key != OBJNULL) {
cl_object gfun = CAR(htable->key);
if (gfun == target) {
htable->key = OBJNULL;
htable->value = OBJNULL;
table->hash.entries--;
}
}
}
}
}
cl_object
si_clear_gfun_hash(cl_object what)
{
/*
* This function clears the generic function call hashes selectively.
* what = Ct means clear the hash completely
* what = generic function, means cleans only these entries
* If we work on a multithreaded environment, we simply enqueue these
* operations and wait for the destination thread to update its own hash.
*/
#ifdef ECL_THREADS
cl_object list;
THREAD_OP_LOCK();
list = cl_core.processes;
for (; list != Cnil; list = CDR(list)) {
cl_object process = CAR(list);
struct cl_env_struct *env = process->process.env;
env->gfun_hash_clear_list = CONS(what, env->gfun_hash_clear_list);
}
THREAD_OP_UNLOCK();
#else
do_clear_gfun_hash(what);
#endif
}
static void
reshape_instance(cl_object x, int delta)
@ -98,20 +146,22 @@ si_generic_function_p(cl_object x)
static struct ecl_hashtable_entry *
get_meth_hash(cl_object *keys, int argno, cl_object hashtable)
{
int hsize;
cl_index hsize;
struct ecl_hashtable_entry *e, *htable;
cl_object hkey, tlist;
register cl_index i = 0;
int k, n; /* k added by chou */
register cl_index i;
int k, n;
for (i = 0, n = 0; n < argno; n++) {
register cl_index a = (cl_index)keys[n];
register cl_index b = GOLDEN_RATIO;
mix(a, b, i);
}
hsize = hashtable->hash.size;
htable = hashtable->hash.data;
for (n = 0; n < argno; n++)
i += (cl_index)keys[n] / 4; /* instead of:
i += hash_eql(keys[n]);
i += hash_eql(Cnil);
*/
for (i %= hsize, k = 0; k < hsize; i = (i + 1) % hsize, k++) {
i = i % hsize;
for (k = 0; k < hsize; k++) {
bool b = 1;
e = &htable[i];
hkey = e->key;
@ -122,6 +172,7 @@ get_meth_hash(cl_object *keys, int argno, cl_object hashtable)
b &= (keys[n] == CAR(tlist));
if (b)
return(&htable[i]);
if (++i >= hsize) i = 0;
}
ecl_internal_error("get_meth_hash");
}
@ -134,12 +185,14 @@ set_meth_hash(cl_object *keys, int argno, cl_object hashtable, cl_object value)
cl_index i;
i = hashtable->hash.entries + 1;
if (i > 512) {
/* It does not make sense to let these hashes grow large */
cl_clrhash(hashtable);
} else if (i >= hashtable->hash.size ||
i >= (hashtable->hash.size * hashtable->hash.factor)) {
ecl_extend_hashtable(hashtable);
if (i >= hashtable->hash.size ||
i >= (hashtable->hash.size * hashtable->hash.factor)) {
if (hashtable->hash.size > 4092) {
/* It does not make sense to let these hashes grow large */
cl_clrhash(hashtable);
} else {
ecl_extend_hashtable(hashtable);
}
}
keylist = Cnil;
for (p = keys + argno; p > keys; p--) keylist = CONS(p[-1], keylist);
@ -157,10 +210,24 @@ standard_dispatch(cl_narg narg, cl_object gf, cl_object *args)
int i, spec_no;
struct ecl_hashtable_entry *e;
cl_object spec_how_list = GFUN_SPEC(gf);
cl_object table = GFUN_HASH(gf);
cl_object argtype[LAMBDA_PARAMETERS_LIMIT];
cl_object table = cl_env.gfun_hash;
cl_object argtype[1+LAMBDA_PARAMETERS_LIMIT];
for (spec_no = 0; spec_how_list != Cnil;) {
#ifdef ECL_THREADS
/* See whether we have to clear the hash from some generic functions right now. */
if (cl_env.gfun_hash_clear_list != Cnil) {
cl_object clear_list;
THREAD_OP_LOCK();
clear_list = cl_env.gfun_hash_clear_list;
for ( ; clear_list != Cnil ; clear_list = CDR(clear_list)) {
do_clear_gfun_hash(CAR(clear_list));
}
cl_env.gfun_hash_clear_list = Cnil;
THREAD_OP_UNLOCK();
}
#endif
argtype[0] = gf;
for (spec_no = 1; spec_how_list != Cnil;) {
cl_object spec_how = CAR(spec_how_list);
cl_object spec_type = CAR(spec_how);
int spec_position = fix(CDR(spec_how));

View file

@ -19,172 +19,7 @@
#include <string.h>
#include <ctype.h>
#include <ecl/internal.h>
/********************
* HASHING ROUTINES *
********************/
/*
* SBCL'S old mashing function. Leads to many collisions.
*/
#if 0
#define mash(h,n) ((((h) << 5) | ((h) >> (FIXNUM_BITS - 5))) ^ (n))
#define hash_word(h,x) mash(h,(cl_index)x)
static cl_hashkey
hash_string(cl_hashkey h, const unsigned char *buf, cl_index len)
{
for (; len; len--) {
h = mash(h, (*buf++));
}
return h;
}
#endif
/*
* SBCL's newest algorithm. Leads to few collisions, and it is faster.
*/
#if FIXNUM_BITS > 32
/*
* 64 bit version
*/
#define GOLDEN_RATIO 0x9e3779b97f4a7c13L
#define mix(a,b,c) \
{ \
a=a-b; a=a-c; a=a^(c>>43); \
b=b-c; b=b-a; b=b^(a<<9); \
c=c-a; c=c-b; c=c^(b>>8); \
a=a-b; a=a-c; a=a^(c>>38); \
b=b-c; b=b-a; b=b^(a<<23); \
c=c-a; c=c-b; c=c^(b>>5); \
a=a-b; a=a-c; a=a^(c>>35); \
b=b-c; b=b-a; b=b^(a<<49); \
c=c-a; c=c-b; c=c^(b>>11); \
a=a-b; a=a-c; a=a^(c>>12); \
b=b-c; b=b-a; b=b^(a<<18); \
c=c-a; c=c-b; c=c^(b>>22); \
}
#define extract_word(k) \
(k[0]+((cl_index)k[1]<<8)+((cl_index)k[2]<<16)+((cl_index)k[3]<<24)+ \
((cl_index)k[4]<<32)+((cl_index)k[5]<<40)+((cl_index)k[6]<<48)+ \
((cl_index)k[7]<<52))
static cl_index
hash_string(cl_index initval, const unsigned char *k, cl_index len)
{
register cl_index a = GOLDEN_RATIO, b = GOLDEN_RATIO, c = initval;
for (; len > 24; ) {
a += extract_word(k); k+=8;
b += extract_word(k); k+=8;
c += extract_word(k); k+=8;
mix(a,b,c);
len -= 24;
}
/*------------------------------------- handle the last 11 bytes */
c += len;
switch(len) {
/* all the case statements fall through */
case 23: c+=((cl_index)k[22]<<52);
case 22: c+=((cl_index)k[21]<<48);
case 21: c+=((cl_index)k[20]<<40);
case 20: c+=((cl_index)k[19]<<32);
case 19: c+=((cl_index)k[18]<<24);
case 18: c+=((cl_index)k[17]<<16);
case 17: c+=((cl_index)k[16]<<8);
/* the first byte of c is reserved for the length */
case 16: b+=((cl_index)k[15]<<52);
case 15: b+=((cl_index)k[14]<<48);
case 14: b+=((cl_index)k[13]<<40);
case 13: b+=((cl_index)k[12]<<32);
case 12: b+=((cl_index)k[11]<<24);
case 11: b+=((cl_index)k[10]<<16);
case 10: b+=((cl_index)k[9]<<8);
case 9 : b+=k[8];
case 8 : a+=((cl_index)k[7]<<52);
case 7 : a+=((cl_index)k[6]<<48);
case 6 : a+=((cl_index)k[5]<<40);
case 5 : a+=((cl_index)k[4]<<32);
case 4 : a+=((cl_index)k[3]<<24);
case 3 : a+=((cl_index)k[2]<<16);
case 2 : a+=((cl_index)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
mix(a,b,c);
/*-------------------------------------------- report the result */
return c;
}
#else
/*
* 32 bit version
*/
#define GOLDEN_RATIO 0x9e3779b9L
#define mix(a,b,c) \
{ \
a -= b; a -= c; a ^= (c>>13); \
b -= c; b -= a; b ^= (a<<8); \
c -= a; c -= b; c ^= (b>>13); \
a -= b; a -= c; a ^= (c>>12); \
b -= c; b -= a; b ^= (a<<16); \
c -= a; c -= b; c ^= (b>>5); \
a -= b; a -= c; a ^= (c>>3); \
b -= c; b -= a; b ^= (a<<10); \
c -= a; c -= b; c ^= (b>>15); \
}
#define extract_word(k) \
(k[0]+((cl_index)k[1]<<8)+((cl_index)k[2]<<16)+((cl_index)k[3]<<24))
static cl_index
hash_string(cl_index initval, const unsigned char *k, cl_index len)
{
register cl_index a = GOLDEN_RATIO, b = GOLDEN_RATIO, c = initval;
for (; len > 12; ) {
a += extract_word(k); k += 4;
b += extract_word(k); k += 4;
c += extract_word(k); k += 4;
mix(a,b,c);
len -= 12;
}
/*------------------------------------- handle the last 11 bytes */
c += len;
switch(len) {
/* all the case statements fall through */
case 11: c+=((cl_index)k[10]<<24);
case 10: c+=((cl_index)k[9]<<16);
case 9 : c+=((cl_index)k[8]<<8);
/* the first byte of c is reserved for the length */
case 8 : b+=((cl_index)k[7]<<24);
case 7 : b+=((cl_index)k[6]<<16);
case 6 : b+=((cl_index)k[5]<<8);
case 5 : b+=k[4];
case 4 : a+=((cl_index)k[3]<<24);
case 3 : a+=((cl_index)k[2]<<16);
case 2 : a+=((cl_index)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
mix(a,b,c);
/*-------------------------------------------- report the result */
return c;
}
#endif
static cl_index hash_word(cl_index c, cl_index a)
{
cl_index b = GOLDEN_RATIO;
mix(a, b, c);
return c;
}
#include "newhash.h"
static void corrupted_hash(cl_object hashtable) /*__attribute__((noreturn))*/;

View file

@ -93,6 +93,17 @@ ecl_init_env(struct cl_env_struct *env)
((struct ecl_fficall*)env->fficall)->registers = 0;
#endif
#ifdef CLOS
#ifdef ECL_THREADS
env->gfun_hash_clear_list = Cnil;
#endif
env->gfun_hash =
cl__make_hash_table(@'equalp', MAKE_FIXNUM(1024), /* size */
ecl_make_singlefloat(1.5f), /* rehash-size */
ecl_make_singlefloat(0.5f), /* rehash-threshold */
Cnil); /* thread-safe */
#endif
init_stacks(&i);
}

146
src/c/newhash.h Normal file
View file

@ -0,0 +1,146 @@
/********************
* HASHING ROUTINES *
********************/
/*
* SBCL's newest algorithm. Leads to few collisions, and it is faster.
*/
#if FIXNUM_BITS > 32
/*
* 64 bit version
*/
#define GOLDEN_RATIO 0x9e3779b97f4a7c13L
#define mix(a,b,c) \
{ \
a=a-b; a=a-c; a=a^(c>>43); \
b=b-c; b=b-a; b=b^(a<<9); \
c=c-a; c=c-b; c=c^(b>>8); \
a=a-b; a=a-c; a=a^(c>>38); \
b=b-c; b=b-a; b=b^(a<<23); \
c=c-a; c=c-b; c=c^(b>>5); \
a=a-b; a=a-c; a=a^(c>>35); \
b=b-c; b=b-a; b=b^(a<<49); \
c=c-a; c=c-b; c=c^(b>>11); \
a=a-b; a=a-c; a=a^(c>>12); \
b=b-c; b=b-a; b=b^(a<<18); \
c=c-a; c=c-b; c=c^(b>>22); \
}
#define extract_word(k) \
(k[0]+((cl_index)k[1]<<8)+((cl_index)k[2]<<16)+((cl_index)k[3]<<24)+ \
((cl_index)k[4]<<32)+((cl_index)k[5]<<40)+((cl_index)k[6]<<48)+ \
((cl_index)k[7]<<52))
static cl_index
hash_string(cl_index initval, const unsigned char *k, cl_index len)
{
register cl_index a = GOLDEN_RATIO, b = GOLDEN_RATIO, c = initval;
for (; len > 24; ) {
a += extract_word(k); k+=8;
b += extract_word(k); k+=8;
c += extract_word(k); k+=8;
mix(a,b,c);
len -= 24;
}
/*------------------------------------- handle the last 11 bytes */
c += len;
switch(len) {
/* all the case statements fall through */
case 23: c+=((cl_index)k[22]<<52);
case 22: c+=((cl_index)k[21]<<48);
case 21: c+=((cl_index)k[20]<<40);
case 20: c+=((cl_index)k[19]<<32);
case 19: c+=((cl_index)k[18]<<24);
case 18: c+=((cl_index)k[17]<<16);
case 17: c+=((cl_index)k[16]<<8);
/* the first byte of c is reserved for the length */
case 16: b+=((cl_index)k[15]<<52);
case 15: b+=((cl_index)k[14]<<48);
case 14: b+=((cl_index)k[13]<<40);
case 13: b+=((cl_index)k[12]<<32);
case 12: b+=((cl_index)k[11]<<24);
case 11: b+=((cl_index)k[10]<<16);
case 10: b+=((cl_index)k[9]<<8);
case 9 : b+=k[8];
case 8 : a+=((cl_index)k[7]<<52);
case 7 : a+=((cl_index)k[6]<<48);
case 6 : a+=((cl_index)k[5]<<40);
case 5 : a+=((cl_index)k[4]<<32);
case 4 : a+=((cl_index)k[3]<<24);
case 3 : a+=((cl_index)k[2]<<16);
case 2 : a+=((cl_index)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
mix(a,b,c);
/*-------------------------------------------- report the result */
return c;
}
#else
/*
* 32 bit version
*/
#define GOLDEN_RATIO 0x9e3779b9L
#define mix(a,b,c) \
{ \
a -= b; a -= c; a ^= (c>>13); \
b -= c; b -= a; b ^= (a<<8); \
c -= a; c -= b; c ^= (b>>13); \
a -= b; a -= c; a ^= (c>>12); \
b -= c; b -= a; b ^= (a<<16); \
c -= a; c -= b; c ^= (b>>5); \
a -= b; a -= c; a ^= (c>>3); \
b -= c; b -= a; b ^= (a<<10); \
c -= a; c -= b; c ^= (b>>15); \
}
#define extract_word(k) \
(k[0]+((cl_index)k[1]<<8)+((cl_index)k[2]<<16)+((cl_index)k[3]<<24))
static cl_index
hash_string(cl_index initval, const unsigned char *k, cl_index len)
{
register cl_index a = GOLDEN_RATIO, b = GOLDEN_RATIO, c = initval;
for (; len > 12; ) {
a += extract_word(k); k += 4;
b += extract_word(k); k += 4;
c += extract_word(k); k += 4;
mix(a,b,c);
len -= 12;
}
/*------------------------------------- handle the last 11 bytes */
c += len;
switch(len) {
/* all the case statements fall through */
case 11: c+=((cl_index)k[10]<<24);
case 10: c+=((cl_index)k[9]<<16);
case 9 : c+=((cl_index)k[8]<<8);
/* the first byte of c is reserved for the length */
case 8 : b+=((cl_index)k[7]<<24);
case 7 : b+=((cl_index)k[6]<<16);
case 6 : b+=((cl_index)k[5]<<8);
case 5 : b+=k[4];
case 4 : a+=((cl_index)k[3]<<24);
case 3 : a+=((cl_index)k[2]<<16);
case 2 : a+=((cl_index)k[1]<<8);
case 1 : a+=k[0];
/* case 0: nothing left to add */
}
mix(a,b,c);
/*-------------------------------------------- report the result */
return c;
}
#endif
static cl_index hash_word(cl_index c, cl_index a)
{
cl_index b = GOLDEN_RATIO;
mix(a, b, c);
return c;
}

View file

@ -1673,6 +1673,10 @@ cl_symbols[] = {
{SYS_ "CODE-BLOCK", SI_ORDINARY, NULL, -1, OBJNULL},
#ifdef CLOS
{SYS_ "CLEAR-GFUN-HASH", SI_ORDINARY, si_clear_gfun_hash, 1, OBJNULL},
#endif
/* Tag for end of list */
{NULL, CL_ORDINARY, NULL, -1, OBJNULL}};

View file

@ -1673,6 +1673,10 @@ cl_symbols[] = {
{SYS_ "CODE-BLOCK",NULL},
#ifdef CLOS
{SYS_ "CLEAR-GFUN-HASH","si_clear_gfun_hash"},
#endif
/* Tag for end of list */
{NULL,NULL}};

View file

@ -189,7 +189,7 @@ their lambda lists ~A and ~A are not congruent."
(setf (generic-function-methods gf)
(delete method (generic-function-methods gf))
(method-generic-function method) nil)
(clrhash (generic-function-method-hash gf))
(si:clear-gfun-hash gf)
gf)
;;; ----------------------------------------------------------------------

View file

@ -86,13 +86,6 @@
(defparameter +standard-generic-function-slots+
'((name :initarg :name :initform nil
:accessor generic-function-name)
(method-hash :accessor generic-function-method-hash
:initform (make-hash-table
:test #'eql
;; use fixnums as limits for efficiency:
:size *default-method-cache-size*
:rehash-size #.(/ *default-method-cache-size* 2)
:rehash-threshold 0.5f0))
(spec-list :initform nil :accessor generic-function-spec-list)
(method-combination
:initarg :method-combination :initform '(standard)
@ -202,13 +195,7 @@
(fdefinition name)
;; create a fake standard-generic-function object:
(let ((gfun (si:allocate-raw-instance nil (find-class 't)
#.(length +standard-generic-function-slots+)))
(hash (make-hash-table
:test #'eql
;; use fixnums as limits for efficiency:
:size *default-method-cache-size*
:rehash-size #.(/ *default-method-cache-size* 2)
:rehash-threshold 0.5f0)))
#.(length +standard-generic-function-slots+))))
(declare (type standard-object gfun))
;; create a new gfun
(si::instance-sig-set gfun)
@ -216,8 +203,7 @@
(generic-function-lambda-list gfun) lambda-list
(generic-function-method-combination gfun) '(standard)
(generic-function-methods gfun) nil
(generic-function-spec-list gfun) nil
(generic-function-method-hash gfun) hash)
(generic-function-spec-list gfun) nil)
(when l-l-p
(setf (generic-function-argument-precedence-order gfun)
(rest (si::process-lambda-list lambda-list t))))
@ -367,7 +353,7 @@
(list ,@a-p-o)))
'function))))))
(setf (generic-function-a-p-o-function gf) function)
(clrhash (generic-function-method-hash gf)))))
(si:clear-gfun-hash gf))))
(defun print-object (object stream)
(print-unreadable-object (object stream)))

View file

@ -97,6 +97,16 @@ struct cl_env_struct {
#endif
int interrupt_pending;
/* The following is a hash table for caching invocations of
generic functions. In a multithreaded environment we must
queue operations in which the hash is cleared from updated
generic functions. */
#ifdef CLOS
cl_object gfun_hash;
#ifdef ECL_THREADS
cl_object gfun_hash_clear_list;
#endif
#endif
/* foreign function interface */
void *fficall;
};
@ -630,6 +640,7 @@ extern void ecl_register_root(cl_object *p);
/* gfun.c */
#ifdef CLOS
extern cl_object si_clear_gfun_hash(cl_object what);
extern cl_object clos_set_funcallable_instance_function(cl_object x, cl_object function_or_t);
extern cl_object si_generic_function_p(cl_object instance);

View file

@ -165,9 +165,8 @@ extern void ecl_extend_hashtable(cl_object hashtable);
/* gfun.d, kernel.lsp */
#define GFUN_NAME(x) ((x)->instance.slots[0])
#define GFUN_HASH(x) ((x)->instance.slots[1])
#define GFUN_SPEC(x) ((x)->instance.slots[2])
#define GFUN_COMB(x) ((x)->instance.slots[3])
#define GFUN_SPEC(x) ((x)->instance.slots[1])
#define GFUN_COMB(x) ((x)->instance.slots[2])
/* package.d */

View file

@ -24,7 +24,7 @@ Returns, as a string, the location of the machine on which ECL runs."
(defun lisp-implementation-version ()
"Args:()
Returns the version of your ECL as a string."
"@PACKAGE_VERSION@ (CVS 2008-01-05 22:02)")
"@PACKAGE_VERSION@ (CVS 2008-01-06 16:18)")
(defun machine-type ()
"Args: ()