Permalink
Browse files

Minor changes to hashing and cache aging algorithms.

Hashing was changed slightly to exploit the fact that a significant amount of real world JSON strings / keys will have a high percentage of ASCII characters.

Cache aging was modified to use an AIMD (additive increase, multiplicative decrease) policy.  When an item is found in the cache, its age is incremented by one using saturating arithmetic.  Ages are "quasi-randomly" aged using unsigned right shifts, or in other words its age is divided in half. Since ages decrease far more quickly than they increase, the cache can quickly adapt and converge on the "hot set".
  • Loading branch information...
1 parent 0aff3de commit 02b983fa8f26521e47dc1d49e3a47ed062e20058 @johnezang committed Mar 22, 2012
Showing with 17 additions and 17 deletions.
  1. +17 −17 JSONKit.m
View
34 JSONKit.m
@@ -175,7 +175,7 @@ The code in isValidCodePoint() is derived from the ICU code in
#define JK_CACHE_SLOTS (1UL << JK_CACHE_SLOTS_BITS)
// JK_CACHE_PROBES is the number of probe attempts.
#define JK_CACHE_PROBES (4UL)
-// JK_INIT_CACHE_AGE must be (1 << AGE) - 1
+// JK_INIT_CACHE_AGE must be < (1 << AGE) - 1, where AGE is sizeof(typeof(AGE)) * 8.
#define JK_INIT_CACHE_AGE (0)
// JK_TOKENBUFFER_SIZE is the default stack size for the temporary buffer used to hold "non-simple" strings (i.e., contains \ escapes)
@@ -609,7 +609,7 @@ - (void)releaseState;
JK_STATIC_INLINE size_t jk_min(size_t a, size_t b);
JK_STATIC_INLINE size_t jk_max(size_t a, size_t b);
-JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c);
+JK_STATIC_INLINE JKHash jk_calculateHash(JKHash currentHash, unsigned char c);
// JSONKit v1.4 used both a JKArray : NSArray and JKMutableArray : NSMutableArray, and the same for the dictionary collection type.
// However, Louis Gerbarg (via cocoa-dev) pointed out that Cocoa / Core Foundation actually implements only a single class that inherits from the
@@ -1111,7 +1111,8 @@ - (id)mutableCopyWithZone:(NSZone *)zone
JK_STATIC_INLINE size_t jk_min(size_t a, size_t b) { return((a < b) ? a : b); }
JK_STATIC_INLINE size_t jk_max(size_t a, size_t b) { return((a > b) ? a : b); }
-JK_STATIC_INLINE JKHash calculateHash(JKHash currentHash, unsigned char c) { return(((currentHash << 5) + currentHash) + c); }
+JK_STATIC_INLINE JKHash jk_calculateHash(JKHash currentHash, unsigned char c) { return((((currentHash << 5) + currentHash) + (c - 29)) ^ (currentHash >> 19)); }
+
static void jk_error(JKParseState *parseState, NSString *format, ...) {
NSCParameterAssert((parseState != NULL) && (format != NULL));
@@ -1408,7 +1409,7 @@ JK_STATIC_INLINE int jk_string_add_unicodeCodePoint(JKParseState *parseState, ui
if((result = ConvertUTF32toUTF8(unicodeCodePoint, &u8s, (parseState->token.tokenBuffer.bytes.ptr + parseState->token.tokenBuffer.bytes.length))) != conversionOK) { if(result == targetExhausted) { return(1); } }
size_t utf8len = u8s - &parseState->token.tokenBuffer.bytes.ptr[*tokenBufferIdx], nextIdx = (*tokenBufferIdx) + utf8len;
- while(*tokenBufferIdx < nextIdx) { *stringHash = calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); }
+ while(*tokenBufferIdx < nextIdx) { *stringHash = jk_calculateHash(*stringHash, parseState->token.tokenBuffer.bytes.ptr[(*tokenBufferIdx)++]); }
return(0);
}
@@ -1442,8 +1443,8 @@ static int jk_parse_string(JKParseState *parseState) {
ConversionResult result;
if(JK_EXPECT_F((result = ConvertSingleCodePointInUTF8(atStringCharacter - 1, endOfBuffer, (UTF8 const **)&nextValidCharacter, &u32ch)) != conversionOK)) { goto switchToSlowPath; }
- stringHash = calculateHash(stringHash, currentChar);
- while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = calculateHash(stringHash, *atStringCharacter++); }
+ stringHash = jk_calculateHash(stringHash, currentChar);
+ while(atStringCharacter < nextValidCharacter) { NSCParameterAssert(JK_AT_STRING_PTR(parseState) <= JK_END_STRING_PTR(parseState)); stringHash = jk_calculateHash(stringHash, *atStringCharacter++); }
continue;
} else {
if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; goto finishedParsing; }
@@ -1460,7 +1461,7 @@ static int jk_parse_string(JKParseState *parseState) {
if(JK_EXPECT_F(currentChar < 0x20UL)) { jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; }
- stringHash = calculateHash(stringHash, currentChar);
+ stringHash = jk_calculateHash(stringHash, currentChar);
}
}
@@ -1478,7 +1479,7 @@ static int jk_parse_string(JKParseState *parseState) {
if(JK_EXPECT_T(currentChar < (unsigned long)0x80)) { // Not a UTF8 sequence
if(JK_EXPECT_F(currentChar == (unsigned long)'"')) { stringState = JSONStringStateFinished; atStringCharacter++; goto finishedParsing; }
if(JK_EXPECT_F(currentChar == (unsigned long)'\\')) { stringState = JSONStringStateEscape; continue; }
- stringHash = calculateHash(stringHash, currentChar);
+ stringHash = jk_calculateHash(stringHash, currentChar);
tokenBuffer[tokenBufferIdx++] = currentChar;
continue;
} else { // UTF8 sequence
@@ -1493,7 +1494,7 @@ static int jk_parse_string(JKParseState *parseState) {
atStringCharacter = nextValidCharacter - 1;
continue;
} else {
- while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = calculateHash(stringHash, *atStringCharacter++); }
+ while(atStringCharacter < nextValidCharacter) { tokenBuffer[tokenBufferIdx++] = *atStringCharacter; stringHash = jk_calculateHash(stringHash, *atStringCharacter++); }
atStringCharacter--;
continue;
}
@@ -1521,7 +1522,7 @@ static int jk_parse_string(JKParseState *parseState) {
parsedEscapedChar:
stringState = JSONStringStateParsing;
- stringHash = calculateHash(stringHash, escapedChar);
+ stringHash = jk_calculateHash(stringHash, escapedChar);
tokenBuffer[tokenBufferIdx++] = escapedChar;
break;
@@ -1709,7 +1710,7 @@ static int jk_parse_number(JKParseState *parseState) {
if(JK_EXPECT_F(endOfNumber != &numberTempBuf[parseState->token.tokenPtrRange.length]) && JK_EXPECT_F(numberState != JSONNumberStateError)) { numberState = JSONNumberStateError; jk_error(parseState, @"The conversion function did not consume all of the number tokens characters."); }
size_t hashIndex = 0UL;
- for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); }
+ for(hashIndex = 0UL; hashIndex < parseState->token.value.ptrRange.length; hashIndex++) { parseState->token.value.hash = jk_calculateHash(parseState->token.value.hash, parseState->token.value.ptrRange.ptr[hashIndex]); }
}
if(JK_EXPECT_F(numberState != JSONNumberStateFinished)) { jk_error(parseState, @"Invalid number."); }
@@ -1972,7 +1973,7 @@ static id json_parse_it(JKParseState *parseState) {
#pragma mark Object cache
// This uses a Galois Linear Feedback Shift Register (LFSR) PRNG to pick which item in the cache to age. It has a period of (2^32)-1.
-// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value.
+// NOTE: A LFSR *MUST* be initialized to a non-zero value and must always have a non-zero value. The LFSR is initalized to 1 in -initWithParseOptions:
JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) {
NSCParameterAssert((parseState != NULL) && (parseState->cache.prng_lfsr != 0U));
parseState->cache.prng_lfsr = (parseState->cache.prng_lfsr >> 1) ^ ((0U - (parseState->cache.prng_lfsr & 1U)) & 0x80200003U);
@@ -1983,9 +1984,8 @@ JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) {
//
// The hash table is a linear C array of JKTokenCacheItem. The terms "item" and "bucket" are synonymous with the index in to the cache array, i.e. cache.items[bucket].
//
-// Items in the cache have an age associated with them. The age is the number of rightmost 1 bits, i.e. 0000 = 0, 0001 = 1, 0011 = 2, 0111 = 3, 1111 = 4.
-// This allows us to use left and right shifts to add or subtract from an items age. Add = (age << 1) | 1. Subtract = age >> 0. Subtract is synonymous with "age" (i.e., age an item).
-// The reason for this is it allows us to perform saturated adds and subtractions and is branchless.
+// Items in the cache have an age associated with them. An items age is incremented using saturating unsigned arithmetic and decremeted using unsigned right shifts.
+// Thus, an items age is managed using an AIMD policy- additive increase, multiplicative decrease. All age calculations and manipulations are branchless.
// The primitive C type MUST be unsigned. It is currently a "char", which allows (at a minimum and in practice) 8 bits.
//
// A "useable bucket" is a bucket that is not in use (never populated), or has an age == 0.
@@ -2000,12 +2000,12 @@ JK_STATIC_INLINE void jk_cache_age(JKParseState *parseState) {
void *parsedAtom = NULL;
if(JK_EXPECT_F(parseState->token.value.ptrRange.length == 0UL) && JK_EXPECT_T(parseState->token.value.type == JKValueTypeString)) { return(@""); }
-
+
for(x = 0UL; x < JK_CACHE_PROBES; x++) {
if(JK_EXPECT_F(parseState->cache.items[bucket].object == NULL)) { setBucket = 1UL; useableBucket = bucket; break; }
if((JK_EXPECT_T(parseState->cache.items[bucket].hash == parseState->token.value.hash)) && (JK_EXPECT_T(parseState->cache.items[bucket].size == parseState->token.value.ptrRange.length)) && (JK_EXPECT_T(parseState->cache.items[bucket].type == parseState->token.value.type)) && (JK_EXPECT_T(parseState->cache.items[bucket].bytes != NULL)) && (JK_EXPECT_T(memcmp(parseState->cache.items[bucket].bytes, parseState->token.value.ptrRange.ptr, parseState->token.value.ptrRange.length) == 0U))) {
- parseState->cache.age[bucket] = (parseState->cache.age[bucket] << 1) | 1U;
+ parseState->cache.age[bucket] = (((uint32_t)parseState->cache.age[bucket]) + 1U) - (((((uint32_t)parseState->cache.age[bucket]) + 1U) >> 31) ^ 1U);
parseState->token.value.cacheItem = &parseState->cache.items[bucket];
NSCParameterAssert(parseState->cache.items[bucket].object != NULL);
return((void *)CFRetain(parseState->cache.items[bucket].object));

0 comments on commit 02b983f

Please sign in to comment.