Skip to content

Commit

Permalink
Merge pull request #1284 from svaarala/rework-object-hash
Browse files Browse the repository at this point in the history
Rework object hash part algorithm
  • Loading branch information
svaarala authored Jan 15, 2017
2 parents 3692379 + 51f7b3b commit 299efb0
Show file tree
Hide file tree
Showing 24 changed files with 366 additions and 311 deletions.
4 changes: 4 additions & 0 deletions RELEASES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2390,6 +2390,10 @@ Planned
algorithm based on single linked chaining of duk_hstrings, with the same
algorithm serving both default and low memory environments (GH-1277)

* Replace object property table hash algorithm with a faster algorithm
which uses a 2^N size and a bit mask instead of a prime size and a MOD;
use a hash table more eagerly than before (GH-1284)

* Add a "global" property to the global object to provide easy access to the
global object itself without needing idioms like
"new Function('return this')()"; implemented based on
Expand Down
14 changes: 14 additions & 0 deletions config/config-options/DUK_USE_HOBJECT_ARRAY_ABANDON_LIMIT.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
define: DUK_USE_HOBJECT_ARRAY_ABANDON_LIMIT
introduced: 2.1.0
default: 2
tags:
- performance
- lowmemory
description: >
Abandon array part if its density is below L. The limit L is expressed as
a .3 fixed point point, e.g. 2 means 2/8 = 25%.
The default limit is quite low: one array entry with packed duk_tval is 8
bytes whereas one normal entry is 4+1+8 = 13 bytes without a hash entry,
and 17-21 bytes with a hash entry (load factor 0.5-1.0). So the array part
shouldn't be abandoned very easily from a footprint point of view.
13 changes: 13 additions & 0 deletions config/config-options/DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
define: DUK_USE_HOBJECT_ARRAY_FAST_RESIZE_LIMIT
introduced: 2.1.0
default: 9
tags:
- performance
- lowmemory
description: >
Skip abandon check in object array part resize if new_size < L * old_size.
The limit L is expressed as a .3 fixed point value, e.g. 9 means 9/8 =
112.5% of current size.
This is rather technical and you should only change the parameter if you've
looked at the internals.
8 changes: 8 additions & 0 deletions config/config-options/DUK_USE_HOBJECT_ARRAY_MINGROW_ADD.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define: DUK_USE_HOBJECT_ARRAY_MINGROW_ADD
introduced: 2.1.0
default: 16
tags:
- performance
description: >
Technical internal parameter, see sources for details. Only adjust if
you've looked at the internals.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define: DUK_USE_HOBJECT_ARRAY_MINGROW_DIVISOR
introduced: 2.1.0
default: 8
tags:
- performance
description: >
Technical internal parameter, see sources for details. Only adjust if
you've looked at the internals.
8 changes: 8 additions & 0 deletions config/config-options/DUK_USE_HOBJECT_ENTRY_MINGROW_ADD.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define: DUK_USE_HOBJECT_ENTRY_MINGROW_DIVISOR
introduced: 2.1.0
default: 8
tags:
- performance
description: >
Technical internal parameter, see sources for details. Only adjust if
you've looked at the internals.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
define: DUK_USE_HOBJECT_ENTRY_MINGROW_ADD
introduced: 2.1.0
default: 16
tags:
- performance
description: >
Technical internal parameter, see sources for details. Only adjust if
you've looked at the internals.
2 changes: 0 additions & 2 deletions config/config-options/DUK_USE_HOBJECT_HASH_PART.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ description: >
enabled unless the target is very low on memory.
If DUK_USE_OBJSIZES16 is defined, this option must not be defined.
# FIXME: expose property limit for hash table as a DUK_USE_xxx flag?
20 changes: 20 additions & 0 deletions config/config-options/DUK_USE_HOBJECT_HASH_PROP_LIMIT.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
define: DUK_USE_HOBJECT_HASH_PROP_LIMIT
introduced: 2.1.0
default: 8
tags:
- performance
- lowmemory
description: >
Minimum number of properties needed for a hash part to be included in the
object property table. This limit is checked whenever an object is resized.
A hash part improves property lookup performance even for small objects,
starting from roughly 4 properties. However, this ignores the cost of
setting up and managing the hash part, which is offset only if property
lookups made through the hash part can offset the setup cost. A hash part
is worth it for heavily accessed small objects or large objects (even those
accessed quite infrequently). The limit doesn't take into account property
access frequency, so it is necessarily a compromise.
A lower value improves performance (a value as low a 4-8 can be useful)
while a higher value conserves memory.
4 changes: 4 additions & 0 deletions config/examples/low_memory.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ DUK_USE_STRTAB_RESIZE_CHECK_MASK: 255 # -""-

DUK_USE_HSTRING_ARRIDX: false

# Only add a hash table for quite large objects to conserve memory. Even
# lower memory targets usually drop hash part support entirely.
DUK_USE_HOBJECT_HASH_PROP_LIMIT: 64

# Consider using pointer compression, see doc/low-memory.rst.
#DUK_USE_REFCOUNT16: true
#DUK_USE_STRHASH16: true
Expand Down
51 changes: 11 additions & 40 deletions doc/hobject-design.rst
Original file line number Diff line number Diff line change
Expand Up @@ -731,12 +731,12 @@ lookups::
| 0 | = 0xffffffffU
| UNUSED |
| UNUSED | DELETED = DUK_HOBJECT_HASHIDX_DELETED
+---------+ = 0xfffffffeU
| UNUSED | = 0xfffffffeU
+---------+
DELETED entries don't terminate hash
probe sequences, UNUSED entries do.
Here, e_size = 5, e_next = 3, h_size = 7.
Here, e_size = 5, e_next = 3, h_size = 8.

.. FIXME for some unknown reason the illustration breaks with pandoc
Expand Down Expand Up @@ -815,8 +815,7 @@ Hash part details
The hash part maps a key ``K`` to an index ``I`` of the entry part or
indicates that ``K`` does not exist. The hash part uses a `closed hash
table`__, i.e. the hash table has a fixed size and a certain key has
multiple possible locations in a *probe sequence*. The current probe
sequence uses a variant of *double hashing*.
multiple possible locations in a *probe sequence*.

__ http://en.wikipedia.org/wiki/Hash_table#Open_addressing

Expand All @@ -834,46 +833,18 @@ is either an index to the entry part, or one of two markers:
Hash table size (``h_size``) is selected relative to the maximum number
of inserted elements ``N`` (equal to ``e_size`` in practice) in two steps:

#. A temporary value ``T`` is selected relative to the number of entries,
as ``c * N`` where ``c`` is currently about 1.2.

#. ``T`` is rounded upwards to the closest prime from a pre-generated
list of primes with an approximately fixed prime-to-prime ratio.

+ The list of primes generated by ``genhashsizes.py``, and is encoded
in a bit packed format, decoded on the fly. See ``genhashsizes.py``
for details.

+ The fact that the hash table size is a prime simplifies probe sequence
handling: it is easy to select probe steps which are guaranteed to
cover all entries of the hash table.
#. Find lowest N so that ``2 ** N >= e_size``.

+ The ratio between successive primes is currently about 1.15.
As a result, the hash table size is about 1.2-1.4 times larger than
the maximum number of properties in the entry part. This implies a
maximum hash table load factor of about 72-83%.

+ The current minimum prime used is 17.
#. Use ``2 ** (N + 1)`` as hash size. This guarantees load factor is
lower than 0.5 after resize.

The probe sequence for a certain key is guaranteed to walk through every
hash table entry, and is generated as follows:

#. The initial hash index is computed directly from the string hash,
modulo hash table size as: ``I = string_hash % h_size``.

#. The probe step is then selected from a pre-generated table of 32
probe steps as: ``S = probe_steps[string_hash % 32]``.

+ The probe steps are is guaranteed to be non-zero and relatively prime
to all precomputed hash table size primes. See ``genhashsizes.py``.
hash table entry. Currently the probe sequence is simply:

+ Currently the precomputed steps are small primes which are not present
in the precomputed hash size primes list. Technically they don't need
to be primes (or small), as long as they are relatively prime to all
possible hash table sizes, i.e. ``gcd(S, h_size)=1``, to guarantee that
the probe sequence walks through all entries of the hash.
* ``(X + i) % h_size`` where i=0,1,...,h_size-1.

#. The probe sequence is: ``(X + i*S) % h_size`` where i=0,1,...h_size-1.
This isn't ideal for avoiding clustering (double hashing would be better)
but is cache friendly and works well enough with low load factors.

When looking up an element from the hash table, we walk through the probe
sequence looking at the hash table entries. If a UNUSED entry is found, the
Expand Down
31 changes: 3 additions & 28 deletions src-input/duk_hobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
#if !defined(DUK_HOBJECT_H_INCLUDED)
#define DUK_HOBJECT_H_INCLUDED

/* Object flag. There are currently 25 flag bits available. Make sure
* this stays in sync with debugger object inspection code.
/* Object flags. Make sure this stays in sync with debugger object
* inspection code.
*/

/* XXX: some flags are object subtype specific (e.g. common to all function
Expand Down Expand Up @@ -651,22 +651,9 @@
#if defined(DUK_USE_OBJSIZES16)
#define DUK_HOBJECT_MAX_PROPERTIES 0x0000ffffUL
#else
#define DUK_HOBJECT_MAX_PROPERTIES 0x7fffffffUL /* 2**31-1 ~= 2G properties */
#define DUK_HOBJECT_MAX_PROPERTIES 0x3fffffffUL /* 2**30-1 ~= 1G properties */
#endif

/* higher value conserves memory; also note that linear scan is cache friendly */
#define DUK_HOBJECT_E_USE_HASH_LIMIT 32

/* hash size relative to entries size: for value X, approx. hash_prime(e_size + e_size / X) */
#define DUK_HOBJECT_H_SIZE_DIVISOR 4 /* hash size approx. 1.25 times entries size */

/* if new_size < L * old_size, resize without abandon check; L = 3-bit fixed point, e.g. 9 -> 9/8 = 112.5% */
#define DUK_HOBJECT_A_FAST_RESIZE_LIMIT 9 /* 112.5%, i.e. new size less than 12.5% higher -> fast resize */

/* if density < L, abandon array part, L = 3-bit fixed point, e.g. 2 -> 2/8 = 25% */
/* limit is quite low: one array entry is 8 bytes, one normal entry is 4+1+8+4 = 17 bytes (with hash entry) */
#define DUK_HOBJECT_A_ABANDON_LIMIT 2 /* 25%, i.e. less than 25% used -> abandon */

/* internal align target for props allocation, must be 2*n for some n */
#if (DUK_USE_ALIGN_BY == 4)
#define DUK_HOBJECT_ALIGN_TARGET 4
Expand All @@ -678,18 +665,6 @@
#error invalid DUK_USE_ALIGN_BY
#endif

/* controls for minimum entry part growth */
#define DUK_HOBJECT_E_MIN_GROW_ADD 16
#define DUK_HOBJECT_E_MIN_GROW_DIVISOR 8 /* 2^3 -> 1/8 = 12.5% min growth */

/* controls for minimum array part growth */
#define DUK_HOBJECT_A_MIN_GROW_ADD 16
#define DUK_HOBJECT_A_MIN_GROW_DIVISOR 8 /* 2^3 -> 1/8 = 12.5% min growth */

/* probe sequence */
#define DUK_HOBJECT_HASH_INITIAL(hash,h_size) ((hash) % (h_size))
#define DUK_HOBJECT_HASH_PROBE_STEP(hash) DUK_UTIL_GET_HASH_PROBE_STEP((hash))

/*
* PC-to-line constants
*/
Expand Down
Loading

0 comments on commit 299efb0

Please sign in to comment.