diff --git a/src/box/memtx_hash.cc b/src/box/memtx_hash.cc index 61ac03c7c2a21ad1ea8e7a69f1d7574bfbedc78a..edc46d8ef922955ee96bde3eaf4f60e443ce71dd 100644 --- a/src/box/memtx_hash.cc +++ b/src/box/memtx_hash.cc @@ -30,6 +30,15 @@ #include "say.h" #include "tuple.h" #include "errinj.h" +#include "memory.h" + +/** For all memory used by all tree indexes. */ +extern struct quota memtx_quota; +static struct slab_arena index_arena; +static struct slab_cache index_arena_slab_cache; +static struct mempool hash_extent_pool; +/** Number of allocated extents. */ +static bool index_arena_initialized = false; #include "third_party/PMurHash.h" @@ -38,17 +47,17 @@ enum { }; static inline bool -mh_index_eq(struct tuple *const *tuple_a, struct tuple *const *tuple_b, +equal(struct tuple *tuple_a, struct tuple *tuple_b, const struct key_def *key_def) { - return tuple_compare(*tuple_a, *tuple_b, key_def) == 0; + return tuple_compare(tuple_a, tuple_b, key_def) == 0; } static inline bool -mh_index_eq_key(const char *key, struct tuple *const *tuple, +equal_key(struct tuple *tuple, const char *key, const struct key_def *key_def) { - return tuple_compare_with_key(*tuple, key, key_def->part_count, + return tuple_compare_with_key(tuple, key, key_def->part_count, key_def) == 0; } @@ -88,15 +97,15 @@ mh_hash_field(uint32_t *ph1, uint32_t *pcarry, const char **field, } static inline uint32_t -mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def) +tuple_hash(struct tuple *tuple, const struct key_def *key_def) { const struct key_part *part = key_def->parts; /* * Speed up the simplest case when we have a - * single-part hash over an integer field. + * single-part hash_table over an integer field. */ if (key_def->part_count == 1 && part->type == NUM) { - const char *field = tuple_field(*tuple, part->fieldno); + const char *field = tuple_field(tuple, part->fieldno); uint64_t val = mp_decode_uint(&field); if (likely(val <= UINT32_MAX)) return val; @@ -108,7 +117,7 @@ mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def) uint32_t total_size = 0; for ( ; part < key_def->parts + key_def->part_count; part++) { - const char *field = tuple_field(*tuple, part->fieldno); + const char *field = tuple_field(tuple, part->fieldno); total_size += mh_hash_field(&h, &carry, &field, part->type); } @@ -116,7 +125,7 @@ mh_index_hash(struct tuple *const *tuple, const struct key_def *key_def) } static inline uint32_t -mh_index_hash_key(const char *key, const struct key_def *key_def) +key_hash(const char *key, const struct key_def *key_def) { const struct key_part *part = key_def->parts; @@ -138,26 +147,21 @@ mh_index_hash_key(const char *key, const struct key_def *key_def) return PMurHash32_Result(h, carry, total_size); } -#define mh_int_t uint32_t -#define mh_arg_t const struct key_def * - -#define mh_hash(a, arg) mh_index_hash(a, arg) -#define mh_hash_key(a, arg) mh_index_hash_key(a, arg) -#define mh_eq(a, b, arg) mh_index_eq(a, b, arg) -#define mh_eq_key(a, b, arg) mh_index_eq_key(a, b, arg) - -#define mh_key_t const char * -typedef struct tuple * mh_node_t; -#define mh_name _index -#define MH_SOURCE 1 -#define mh_bytemap 1 -#include "salad/mhash.h" +#define LIGHT_NAME _index +#define LIGHT_DATA_TYPE struct tuple * +#define LIGHT_KEY_TYPE const char * +#define LIGHT_CMP_ARG_TYPE struct key_def * +#define LIGHT_EQUAL(a, b, c) equal(a, b, c) +#define LIGHT_EQUAL_KEY(a, b, c) equal_key(a, b, c) +#define HASH_INDEX_EXTENT_SIZE (16 * 1024) +typedef uint32_t hash_t; +#include "salad/light.h" /* {{{ MemtxHash Iterators ****************************************/ struct hash_iterator { struct iterator base; /* Must be the first member. */ - struct mh_index_t *hash; + struct light_index_core *hash_table; uint32_t h_pos; }; @@ -174,10 +178,13 @@ hash_iterator_ge(struct iterator *ptr) assert(ptr->free == hash_iterator_free); struct hash_iterator *it = (struct hash_iterator *) ptr; - while (it->h_pos < mh_end(it->hash)) { - if (mh_exist(it->hash, it->h_pos)) - return *mh_index_node(it->hash, it->h_pos++); + if (it->h_pos < it->hash_table->table_size) { + struct tuple *res = light_index_get(it->hash_table, it->h_pos); it->h_pos++; + while (it->h_pos < it->hash_table->table_size + && !light_index_pos_valid(it->hash_table, it->h_pos)) + it->h_pos++; + return res; } return NULL; } @@ -197,48 +204,80 @@ hash_iterator_eq(struct iterator *it) /* }}} */ +static void * +extent_alloc() +{ + ERROR_INJECT(ERRINJ_INDEX_ALLOC, return 0); + return mempool_alloc(&hash_extent_pool); +} + +static void +extent_free(void *extent) +{ + return mempool_free(&hash_extent_pool, extent); +} + /* {{{ MemtxHash -- implementation of all hashes. **********************/ MemtxHash::MemtxHash(struct key_def *key_def) : Index(key_def) { - hash = mh_index_new(); - if (hash == NULL) { - tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(hash), - "MemtxHash", "hash"); + if (index_arena_initialized == false) { + const uint32_t SLAB_SIZE = 4 * 1024 * 1024; + if (slab_arena_create(&index_arena, &memtx_quota, + 0, SLAB_SIZE, MAP_PRIVATE)) { + panic_syserror("failed to initialize index arena"); + } + slab_cache_create(&index_arena_slab_cache, &index_arena, + SLAB_SIZE); + mempool_create(&hash_extent_pool, &index_arena_slab_cache, + HASH_INDEX_EXTENT_SIZE); + index_arena_initialized = true; } + hash_table = (struct light_index_core *) malloc(sizeof(*hash_table)); + if (hash_table == NULL) { + tnt_raise(ClientError, ER_MEMORY_ISSUE, sizeof(hash_table), + "MemtxHash", "hash_table"); + } + light_index_create(hash_table, HASH_INDEX_EXTENT_SIZE, + extent_alloc, extent_free, this->key_def); } MemtxHash::~MemtxHash() { - mh_index_delete(hash); + light_index_destroy(hash_table); + free(hash_table); } void MemtxHash::reserve(uint32_t size_hint) { - mh_index_reserve(hash, size_hint, key_def); + (void)size_hint; } size_t MemtxHash::size() const { - return mh_size(hash); + return hash_table->count; } size_t MemtxHash::memsize() const { - return mh_index_memsize(hash); + return matras_extents_count(&hash_table->mtable) * HASH_INDEX_EXTENT_SIZE; } struct tuple * MemtxHash::random(uint32_t rnd) const { - uint32_t k = mh_index_random(hash, rnd); - if (k != mh_end(hash)) - return *mh_index_node(hash, k); - return NULL; + if (hash_table->count == 0) + return NULL; + rnd %= (hash_table->table_size); + while (!light_index_pos_valid(hash_table, rnd)) { + rnd++; + rnd %= (hash_table->table_size); + } + return light_index_get(hash_table, rnd); } struct tuple * @@ -248,9 +287,10 @@ MemtxHash::findByKey(const char *key, uint32_t part_count) const (void) part_count; struct tuple *ret = NULL; - uint32_t k = mh_index_find(hash, key, key_def); - if (k != mh_end(hash)) - ret = *mh_index_node(hash, k); + uint32_t h = key_hash(key, key_def); + uint32_t k = light_index_find_key(hash_table, h, key); + if (k != light_index_end) + ret = light_index_get(hash_table, k); return ret; } @@ -261,31 +301,31 @@ MemtxHash::replace(struct tuple *old_tuple, struct tuple *new_tuple, uint32_t errcode; if (new_tuple) { + uint32_t h = tuple_hash(new_tuple, key_def); struct tuple *dup_tuple = NULL; - struct tuple **dup_node = &dup_tuple; - uint32_t pos = mh_index_put(hash, &new_tuple, - &dup_node, key_def); + hash_t pos = light_index_replace(hash_table, h, new_tuple, &dup_tuple); + if (pos == light_index_end) + pos = light_index_insert(hash_table, h, new_tuple); ERROR_INJECT(ERRINJ_INDEX_ALLOC, { - mh_index_del(hash, pos, key_def); - pos = mh_end(hash); + light_index_delete(hash_table, pos); + pos = light_index_end; }); - if (pos == mh_end(hash)) { - tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) pos, - "hash", "key"); + if (pos == light_index_end) { + tnt_raise(LoggedError, ER_MEMORY_ISSUE, (ssize_t) hash_table->count, + "hash_table", "key"); } errcode = replace_check_dup(old_tuple, dup_tuple, mode); if (errcode) { - mh_index_remove(hash, &new_tuple, key_def); + light_index_delete(hash_table, pos); if (dup_tuple) { - pos = mh_index_put(hash, &dup_tuple, NULL, - key_def); - if (pos == mh_end(hash)) { + uint32_t pos = light_index_insert(hash_table, h, dup_tuple); + if (pos == light_index_end) { panic("Failed to allocate memory in " - "recover of int hash"); + "recover of int hash_table"); } } tnt_raise(ClientError, errcode, index_id(this)); @@ -296,7 +336,10 @@ MemtxHash::replace(struct tuple *old_tuple, struct tuple *new_tuple, } if (old_tuple) { - mh_index_remove(hash, &old_tuple, key_def); + uint32_t h = tuple_hash(old_tuple, key_def); + hash_t slot = light_index_find(hash_table, h, old_tuple); + assert(slot != light_index_end); + light_index_delete(hash_table, slot); } return old_tuple; } @@ -326,21 +369,24 @@ MemtxHash::initIterator(struct iterator *ptr, enum iterator_type type, assert(ptr->free == hash_iterator_free); struct hash_iterator *it = (struct hash_iterator *) ptr; + it->hash_table = hash_table; switch (type) { case ITER_ALL: - it->h_pos = mh_begin(hash); + it->h_pos = 0; + while (it->h_pos < it->hash_table->table_size + && !light_index_pos_valid(it->hash_table, it->h_pos)) + it->h_pos++; it->base.next = hash_iterator_ge; break; case ITER_EQ: assert(part_count > 0); - it->h_pos = mh_index_find(hash, key, key_def); + it->h_pos = light_index_find_key(hash_table, key_hash(key, key_def), key); it->base.next = hash_iterator_eq; break; default: tnt_raise(ClientError, ER_UNSUPPORTED, "Hash index", "requested iterator type"); } - it->hash = hash; } /* }}} */ diff --git a/src/box/memtx_hash.h b/src/box/memtx_hash.h index 51848d3f40aacbdf36e07f10ea53fa0f75417cea..711256981258c5506112543971f3f3255582250d 100644 --- a/src/box/memtx_hash.h +++ b/src/box/memtx_hash.h @@ -31,7 +31,7 @@ #include "index.h" -struct mh_index_t; +struct light_index_core; class MemtxHash: public Index { public: @@ -53,7 +53,7 @@ class MemtxHash: public Index { virtual size_t memsize() const; protected: - struct mh_index_t *hash; + struct light_index_core *hash_table; }; #endif /* TARANTOOL_BOX_MEMTX_HASH_H_INCLUDED */ diff --git a/src/lib/salad/light.h b/src/lib/salad/light.h new file mode 100644 index 0000000000000000000000000000000000000000..bb418ea47d18fdd4397ee8ebb2f0a60c577b338d --- /dev/null +++ b/src/lib/salad/light.h @@ -0,0 +1,771 @@ +/* + * *No header guard*: the header is allowed to be included twice + * with different sets of defines. + */ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "small/matras.h" + +/** + * Additional user defined name that appended to prefix 'light' + * for all names of structs and functions in this header file. + * All names use pattern: light<LIGHT_NAME>_<name of func/struct> + * May be empty, but still have to be defined (just #define LIGHT_NAME) + * Example: + * #define LIGHT_NAME _test + * ... + * struct light_test_core hash_table; + * light_test_create(&hash_table, ...); + */ +#ifndef LIGHT_NAME +#error "LIGHT_NAME must be defined" +#endif + +/** + * Data type that hash table holds. Must be less tant 8 bytes. + */ +#ifndef LIGHT_DATA_TYPE +#error "LIGHT_DATA_TYPE must be defined" +#endif + +/** + * Data type that used to for finding values. + */ +#ifndef LIGHT_KEY_TYPE +#error "LIGHT_KEY_TYPE must be defined" +#endif + +/** + * Type of optional third parameter of comparing function. + * If not needed, simply use #define LIGHT_CMP_ARG_TYPE int + */ +#ifndef LIGHT_CMP_ARG_TYPE +#error "LIGHT_CMP_ARG_TYPE must be defined" +#endif + +/** + * Data comparing function. Takes 3 parameters - value1, value2 and + * optional value that stored in hash table struct. + * Third parameter may be simply ignored like that: + * #define LIGHT_EQUAL(a, b, garb) a == b + */ +#ifndef LIGHT_EQUAL +#error "LIGHT_EQUAL must be defined" +#endif + +/** + * Data comparing function. Takes 3 parameters - value, key and + * optional value that stored in hash table struct. + * Third parameter may be simply ignored like that: + * #define LIGHT_EQUAL_KEY(a, b, garb) a == b + */ +#ifndef LIGHT_EQUAL_KEY +#error "LIGHT_EQUAL_KEY must be defined" +#endif + +/** + * Tools for name substitution: + */ +#ifndef CONCAT4 +#define CONCAT4_R(a, b, c, d) a##b##c##d +#define CONCAT4(a, b, c, d) CONCAT4_R(a, b, c, d) +#endif + +#ifdef _ +#error '_' must be undefinded! +#endif +#define LIGHT(name) CONCAT4(light, LIGHT_NAME, _, name) + +/** + * Struct for one record of the hash table + */ +struct LIGHT(record) { + /* hash of a value */ + uint32_t hash; + /* slot of the next record in chain */ + uint32_t next; + /* the value */ + LIGHT_DATA_TYPE value; +}; + +/** + * Main struct for holding hash table + */ +struct LIGHT(core) { + /* Number of records added while grow iteration */ + enum { GROW_INCREMENT = 8 }; + /* count of values in hash table */ + uint32_t count; + /* size of hash table ( equal to mtable.size ) */ + uint32_t table_size; + /* + * cover is power of two; + * if count is positive, then cover/2 < count <= cover + * cover_mask is cover - 1 + */ + uint32_t cover_mask; + + /* + * Start of chain of empty slots + */ + uint32_t empty_slot; + struct LIGHT(record) *empty_record; + + /* additional parameter for data comparison */ + LIGHT_CMP_ARG_TYPE arg; + + /* dynamic storage for records */ + struct matras mtable; +}; + +/** + * Type of functions for memory allocation and deallocation + */ +typedef void *(*LIGHT(extent_alloc_t))(); +typedef void (*LIGHT(extent_free_t))(void *); + +/** + * Special result of light_find that means that nothing was found + */ +static const uint32_t LIGHT(end) = 0xFFFFFFFF; + +/** + * @brief Hash table construction. Fills struct light members. + * @param ht - pointer to a hash table struct + * @param extent_size - size of allocating memory blocks + * @param extent_alloc_func - memory blocks allocation function + * @param extent_free_func - memory blocks allocation function + * @param arg - optional parameter to save for comparing function + */ +void +LIGHT(create)(struct LIGHT(core) *ht, size_t extent_size, + LIGHT(extent_alloc_t) extent_alloc_func, + LIGHT(extent_free_t) extent_free_func, + LIGHT_CMP_ARG_TYPE arg); + +/** + * @brief Hash table destruction. Frees all allocated memory + * @param ht - pointer to a hash table struct + */ +void +LIGHT(destroy)(struct LIGHT(core) *ht); + +/** + * @brief Find a record with given hash and value + * @param ht - pointer to a hash table struct + * @param hash - hash to find + * @param data - value to find + * @return integer ID of found record or light_end if nothing found + */ +uint32_t +LIGHT(find)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE data); + +/** + * @brief Find a record with given hash and key + * @param ht - pointer to a hash table struct + * @param hash - hash to find + * @param data - key to find + * @return integer ID of found record or light_end if nothing found + */ +uint32_t +LIGHT(find_key)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_KEY_TYPE data); + +/** + * @brief Insert a record with given hash and value + * @param ht - pointer to a hash table struct + * @param hash - hash to insert + * @param data - value to insert + * @return integer ID of inserted record or light_end if failed + */ +uint32_t +LIGHT(insert)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE data); + +/** + * @brief Replace a record with given hash and value + * @param ht - pointer to a hash table struct + * @param hash - hash to find + * @param data - value to find and replace + * @param replaced - pointer to a value that was stored in table before replace + * @return integer ID of found record or light_end if nothing found + */ +uint32_t +LIGHT(replace)(struct LIGHT(core) *ht, uint32_t hash, + LIGHT_DATA_TYPE data, LIGHT_DATA_TYPE *replaced); + +/** + * @brief Delete a record from a hash table by given record ID + * @param ht - pointer to a hash table struct + * @param slotpos - ID of an record. See LIGHT(find) for details. + */ +void +LIGHT(delete)(struct LIGHT(core) *ht, uint32_t slotpos); + +/** + * @brief Get a value from a desired position + * @param ht - pointer to a hash table struct + * @param slotpos - ID of an record + */ +LIGHT_DATA_TYPE +LIGHT(get)(struct LIGHT(core) *ht, uint32_t slotpos); + +/** + * @brief Determine if posision holds a value + * @param ht - pointer to a hash table struct + * @param slotpos - ID of an record + */ +bool +LIGHT(pos_valid)(struct LIGHT(core) *ht, uint32_t slotpos); + + + +inline void +LIGHT(create)(struct LIGHT(core) *ht, size_t extent_size, + LIGHT(extent_alloc_t) extent_alloc_func, + LIGHT(extent_free_t) extent_free_func, + LIGHT_CMP_ARG_TYPE arg) +{ + assert((ht->GROW_INCREMENT & (ht->GROW_INCREMENT - 1)) == 0); + assert(sizeof(LIGHT_DATA_TYPE) >= sizeof(uint32_t)); + ht->count = 0; + ht->table_size = 0; + ht->empty_slot = LIGHT(end); + ht->empty_record = 0; + ht->arg = arg; + matras_create(&ht->mtable, + extent_size, sizeof(struct LIGHT(record)), + extent_alloc_func, extent_free_func); +} + +inline void +LIGHT(destroy)(struct LIGHT(core) *ht) +{ + matras_destroy(&ht->mtable); +} + +inline uint32_t +LIGHT(slot)(const struct LIGHT(core) *ht, uint32_t hash) +{ + uint32_t cover_mask = ht->cover_mask; + uint32_t res = hash & cover_mask; + uint32_t probe = (ht->table_size - res - 1) >> 31; + uint32_t shift = __builtin_ctz(~(cover_mask >> 1)); + res ^= (probe << shift); + return res; + +} + +inline uint32_t +LIGHT(find)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value) +{ + if (ht->count == 0) + return LIGHT(end); + uint32_t slot = LIGHT(slot)(ht, hash); + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + if (record->next == slot) + return LIGHT(end); + while (1) { + if (record->hash == hash && LIGHT_EQUAL((record->value), (value), (ht->arg))) + return slot; + slot = record->next; + if (slot == LIGHT(end)) + return LIGHT(end); + record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + } + /* unreachable */ + return LIGHT(end); +} + +inline uint32_t +LIGHT(replace)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value, LIGHT_DATA_TYPE *replaced) +{ + if (ht->count == 0) + return LIGHT(end); + uint32_t slot = LIGHT(slot)(ht, hash); + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + if (record->next == slot) + return LIGHT(end); + while (1) { + if (record->hash == hash && LIGHT_EQUAL((record->value), (value), (ht->arg))) { + *replaced = record->value; + record->value = value; + return slot; + } + slot = record->next; + if (slot == LIGHT(end)) + return LIGHT(end); + record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + } + /* unreachable */ + return LIGHT(end); +} + + + +inline uint32_t +LIGHT(find_key)(const struct LIGHT(core) *ht, uint32_t hash, LIGHT_KEY_TYPE key) +{ + if (ht->count == 0) + return LIGHT(end); + uint32_t slot = LIGHT(slot)(ht, hash); + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + if (record->next == slot) + return LIGHT(end); + while (1) { + if (record->hash == hash && LIGHT_EQUAL_KEY((record->value), (key), (ht->arg))) + return slot; + slot = record->next; + if (slot == LIGHT(end)) + return LIGHT(end); + record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + } + /* unreachable */ + return LIGHT(end); +} + +inline uint32_t +LIGHT(get_empty_prev)(struct LIGHT(record) *record) +{ + return record->hash; +} + +inline void +LIGHT(set_empty_prev)(struct LIGHT(record) *record, uint32_t pos) +{ + record->hash = pos; +} + +inline uint32_t +LIGHT(get_empty_next)(struct LIGHT(record) *record) +{ + return (uint32_t)(uint64_t)record->value; +} + +inline void +LIGHT(set_empty_next)(struct LIGHT(record) *record, uint32_t pos) +{ + record->value = (LIGHT_DATA_TYPE)(int64_t)pos; +} + +inline void +LIGHT(enqueue_empty)(struct LIGHT(core) *ht, uint32_t slot, struct LIGHT(record) *record) +{ + record->next = slot; + if (ht->empty_record) + LIGHT(set_empty_prev)(ht->empty_record, slot); + LIGHT(set_empty_prev)(record, LIGHT(end)); + LIGHT(set_empty_next)(record, ht->empty_slot); + ht->empty_record = record; + ht->empty_slot = slot; +} + +inline void +LIGHT(detach_first_empty)(struct LIGHT(core) *ht) +{ + assert(ht->empty_record); + ht->empty_slot = LIGHT(get_empty_next)(ht->empty_record); + if (ht->empty_slot == LIGHT(end)) { + ht->empty_record = 0; + } else { + ht->empty_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, ht->empty_slot); + LIGHT(set_empty_prev)(ht->empty_record, LIGHT(end)); + } +} + +inline void +LIGHT(detach_empty)(struct LIGHT(core) *ht, struct LIGHT(record) *record) +{ + uint32_t prev_slot = LIGHT(get_empty_prev)(record); + uint32_t next_slot = LIGHT(get_empty_next)(record); + if (prev_slot == LIGHT(end)) { + ht->empty_slot = next_slot; + if (next_slot == LIGHT(end)) + ht->empty_record = 0; + else + ht->empty_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, next_slot); + } else { + struct LIGHT(record) *prev_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, prev_slot); + LIGHT(set_empty_next)(prev_record, next_slot); + } + if (next_slot != LIGHT(end)) { + struct LIGHT(record) *next_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, next_slot); + LIGHT(set_empty_prev)(next_record, prev_slot); + } +} + +inline bool +LIGHT(prepare_first_insert)(struct LIGHT(core) *ht) +{ + assert(ht->count == 0); + assert(ht->table_size == 0); + assert(ht->mtable.block_count == 0); + + uint32_t slot; + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_alloc_range(&ht->mtable, &slot, ht->GROW_INCREMENT); + if (!record) + return false; + assert(slot == 0); + ht->table_size = ht->GROW_INCREMENT; + ht->cover_mask = ht->GROW_INCREMENT - 1; + ht->empty_slot = 0; + ht->empty_record = record; + for (int i = 0; i < ht->GROW_INCREMENT; i++) { + record[i].next = i; + LIGHT(set_empty_prev)(record + i, i - 1); + LIGHT(set_empty_next)(record + i, i + 1); + } + LIGHT(set_empty_prev)(record, LIGHT(end)); + LIGHT(set_empty_next)(record + ht->GROW_INCREMENT - 1, LIGHT(end)); + return true; +} + +inline bool +LIGHT(grow)(struct LIGHT(core) *ht) +{ + uint32_t new_slot; + struct LIGHT(record) *new_record = (struct LIGHT(record) *) + matras_alloc_range(&ht->mtable, &new_slot, ht->GROW_INCREMENT); + if (!new_record) /* memory failure */ + return false; + ht->table_size += ht->GROW_INCREMENT; + + if (ht->cover_mask < ht->table_size - 1) + ht->cover_mask = (ht->cover_mask << 1) | (uint32_t)1; + + uint32_t split_comm_mask = (ht->cover_mask >> 1); + uint32_t split_diff_mask = ht->cover_mask ^ split_comm_mask; + + uint32_t susp_slot = new_slot & split_comm_mask; + struct LIGHT(record) *susp_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, susp_slot); + + for (int i = 0; i < ht->GROW_INCREMENT; i++, susp_slot++, susp_record++, new_slot++, new_record++) { + if (susp_record->next == susp_slot) { + /* Suspicious slot is empty, nothing to split */ + LIGHT(enqueue_empty)(ht, new_slot, new_record); + continue; + } + if ((susp_record->hash & split_comm_mask) != susp_slot) { + /* Another chain in suspicious slot, nothing to split */ + LIGHT(enqueue_empty)(ht, new_slot, new_record); + continue; + } + + uint32_t chain_head_slot[2] = {susp_slot, new_slot}; + struct LIGHT(record) *chain_head[2] = {susp_record, new_record}; + struct LIGHT(record) *chain_tail[2] = {0, 0}; + uint32_t shift = __builtin_ctz(split_diff_mask); + assert(split_diff_mask == (((uint32_t)1) << shift)); + + uint32_t last_empty_slot = new_slot; + struct LIGHT(record) *last_empty_record = new_record; + uint32_t prev_flag = 0; + struct LIGHT(record) *test_record = susp_record; + uint32_t test_slot = susp_slot; + struct LIGHT(record) *prev_record = 0; + while (1) { + uint32_t test_flag = (test_record->hash >> shift) & ((uint32_t)1); + if (test_flag != prev_flag) { + chain_tail[prev_flag] = prev_record; + if (chain_tail[test_flag]) { + chain_tail[test_flag]->next = test_slot; + } else { + *chain_head[test_flag] = *test_record; + last_empty_slot = test_slot; + last_empty_record = test_record; + test_record = chain_head[test_flag]; + test_slot = chain_head_slot[test_flag]; + } + prev_flag = test_flag; + } + test_slot = test_record->next; + if (test_slot == LIGHT(end)) + break; + prev_record = test_record; + test_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, test_slot); + } + prev_flag = prev_flag ^ ((uint32_t)1); + if (chain_tail[prev_flag]) + chain_tail[prev_flag]->next = LIGHT(end); + + LIGHT(enqueue_empty)(ht, last_empty_slot, last_empty_record); + } + return true; +} + +inline uint32_t +LIGHT(insert)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value) +{ + if (ht->table_size == 0) + if (!LIGHT(prepare_first_insert)(ht)) + return LIGHT(end); + if (!ht->empty_record) + if (!LIGHT(grow)(ht)) + return LIGHT(end); + + ht->count++; + uint32_t slot = LIGHT(slot)(ht, hash); + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + + if (record->next == slot) { + /* Inserting to an empty slot */ + LIGHT(detach_empty)(ht, record); + record->value = value; + record->hash = hash; + record->next = LIGHT(end); + return slot; + } + + uint32_t empty_slot = ht->empty_slot; + struct LIGHT(record) *empty_record = ht->empty_record; + LIGHT(detach_first_empty)(ht); + + uint32_t chain_slot = LIGHT(slot)(ht, record->hash); + if (chain_slot == slot) { + /* add to existing chain */ + empty_record->value = value; + empty_record->hash = hash; + empty_record->next = record->next; + record->next = empty_slot; + return empty_slot; + } else { + /* create new chain */ + struct LIGHT(record) *chain = (struct LIGHT(record) *) + matras_get(&ht->mtable, chain_slot); + while (chain->next != slot) { + chain_slot = chain->next; + chain = (struct LIGHT(record) *) + matras_get(&ht->mtable, chain_slot); + } + *empty_record = *record; + chain->next = empty_slot; + record->value = value; + record->hash = hash; + record->next = LIGHT(end); + return slot; + } +} + +inline void +LIGHT(delete)(struct LIGHT(core) *ht, uint32_t slot) +{ + assert(slot < ht->table_size); + uint32_t empty_slot; + struct LIGHT(record) *empty_record; + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + assert(record->next != slot); + if (record->next != LIGHT(end)) { + empty_slot = record->next; + empty_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, empty_slot); + *record = *empty_record; + } else { + empty_slot = slot; + empty_record = record; + uint32_t chain_slot = LIGHT(slot)(ht, record->hash); + if (chain_slot != slot) { + /* deleting a last record of chain */ + struct LIGHT(record) *chain = (struct LIGHT(record) *) + matras_get(&ht->mtable, chain_slot); + uint32_t chain_next_slot = chain->next; + assert(chain_next_slot != LIGHT(end)); + while (chain_next_slot != slot) { + chain_slot = chain_next_slot; + chain = (struct LIGHT(record) *) + matras_get(&ht->mtable, chain_next_slot); + chain_next_slot = chain->next; + assert(chain_next_slot != LIGHT(end)); + } + chain->next = LIGHT(end); + } + } + LIGHT(enqueue_empty)(ht, empty_slot, empty_record); + ht->count--; +} + +inline void +LIGHT(delete_value)(struct LIGHT(core) *ht, uint32_t hash, LIGHT_DATA_TYPE value) +{ + if (ht->count == 0) + return; + uint32_t slot = LIGHT(slot)(ht, hash); + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + if (record->next == slot) + return; + struct LIGHT(record) *prev_record = 0; + while (1) { + if (record->hash == hash && LIGHT_EQUAL((record->value), (value), (ht->arg))) + break; + slot = record->next; + if (slot == LIGHT(end)) + return; + prev_record = record; + record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + } + ht->count--; + if (prev_record) { + prev_record->next = record->next; + LIGHT(enqueue_empty)(ht, slot, record); + return; + } + if (record->next == LIGHT(end)) { + LIGHT(enqueue_empty)(ht, slot, record); + return; + } + uint32_t next_slot = record->next; + struct LIGHT(record) *next_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, next_slot); + *record = *next_record; + LIGHT(enqueue_empty)(ht, next_slot, next_record); +} + +inline LIGHT_DATA_TYPE +LIGHT(get)(struct LIGHT(core) *ht, uint32_t slotpos) +{ + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slotpos); + return record->value; +} + +/** + * @brief Determine if posision holds a value + * @param ht - pointer to a hash table struct + * @param slotpos - ID of an record + */ +inline bool +LIGHT(pos_valid)(LIGHT(core) *ht, uint32_t slotpos) +{ + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slotpos); + return record->next != slotpos; +} + + +inline int +LIGHT(selfcheck)(const struct LIGHT(core) *ht) +{ + int res = 0; + if (ht->table_size != ht->mtable.block_count) + res |= 64; + uint32_t empty_slot = ht->empty_slot; + if (empty_slot == LIGHT(end)) { + if (ht->empty_record) + res |= 512; + } else { + struct LIGHT(record) *should_be = (struct LIGHT(record) *) + matras_get(&ht->mtable, empty_slot); + if (ht->empty_record != should_be) + res |= 1024; + } + uint32_t prev_empty_slot = LIGHT(end); + while (empty_slot != LIGHT(end)) { + struct LIGHT(record) *empty_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, empty_slot); + if (empty_record->next != empty_slot) + res |= 2048; + if (LIGHT(get_empty_prev)(empty_record) != prev_empty_slot) + res |= 4096; + prev_empty_slot = empty_slot; + empty_slot = LIGHT(get_empty_next)(empty_record); + } + for (uint32_t i = 0; i < ht->table_size; i++) { + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, i); + if (record->next == i) { + uint32_t empty_slot = ht->empty_slot; + while (empty_slot != LIGHT(end) && empty_slot != i) { + struct LIGHT(record) *empty_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, empty_slot); + empty_slot = LIGHT(get_empty_next)(empty_record); + } + if (empty_slot != i) + res |= 256; + continue; + } + uint32_t slot = LIGHT(slot)(ht, record->hash); + if (slot != i) { + bool found = false; + uint32_t chain_slot = slot; + uint32_t chain_start_slot = slot; + do { + struct LIGHT(record) *chain_record = (struct LIGHT(record) *) + matras_get(&ht->mtable, chain_slot); + chain_slot = chain_record->next; + if (chain_slot >= ht->table_size) { + res |= 16; /* out of bounds (1) */ + break; + } + if (chain_slot == i) { + found = true; + break; + } + if (chain_slot == chain_start_slot) { + res |= 4; /* cycles in chain (1) */ + break; + } + } while (chain_slot != LIGHT(end)); + if (!found) + res |= 1; /* slot is out of chain */ + } else { + do { + struct LIGHT(record) *record = (struct LIGHT(record) *) + matras_get(&ht->mtable, slot); + if (LIGHT(slot)(ht, record->hash) != i) + res |= 2; /* wrong value in chain */ + slot = record->next; + if (slot != LIGHT(end) && slot >= ht->table_size) { + res |= 32; /* out of bounds (2) */ + break; + } + if (slot == i) { + res |= 8; /* cycles in chain (2) */ + break; + } + } while (slot != LIGHT(end)); + } + } + return res; +} + diff --git a/src/lib/small/matras.c b/src/lib/small/matras.c index 95cdd196c973d4d3c27f6206b1f4974a5ad7e6a0..09929583788745bbff44b1819878ab25958de91b 100644 --- a/src/lib/small/matras.c +++ b/src/lib/small/matras.c @@ -4,6 +4,13 @@ #include "matras.h" #include <limits.h> +#ifdef WIN32 +#include <intrin.h> +#pragma intrinsic (_BitScanReverse) +#ifndef _DEBUG +#define __OPTIMIZE__ 1 +#endif +#endif /* * Binary logarithm of value (exact if the value is a power of 2, @@ -13,8 +20,15 @@ static matras_id_t pt_log2(matras_id_t val) { assert(val > 0); - return sizeof(unsigned long) * CHAR_BIT - - __builtin_clzl((unsigned long) val) - 1; +#ifdef WIN32 + unsigned long res = 0; + unsigned char nonzero = _BitScanReverse(&res, val); + assert(nonzero); (void)nonzero; + return (matras_id_t)res; +#else + return sizeof(unsigned int) * CHAR_BIT - + __builtin_clz((unsigned int) val) - 1; +#endif } /** @@ -92,7 +106,7 @@ matras_destroy(struct matras *m) m->block_count = 0; } #ifndef __OPTIMIZE__ - m->extent = (void *)0xDEADBEEF; + m->extent = (void *)(long long)0xDEADBEEF; #endif } @@ -221,6 +235,40 @@ matras_dealloc(struct matras *m) } } +/** + * Allocate a range_count of blocks. Return both, first block pointer + * and first block id. This method only works if current number of blocks and + * number of blocks in one extent are divisible by range_count. + * range_count must also be less or equal to number of blocks in one extent. + * + * @retval NULL failed to allocate memory + */ +void * +matras_alloc_range(struct matras *m, matras_id_t *id, matras_id_t range_count) +{ + assert(m->block_count % range_count == 0); + assert(m->extent_size / m->block_size % range_count == 0); + void *res = matras_alloc(m, id); + if (res) + m->block_count += (range_count - 1); + return res; +} + +/* + * Deallocate last range_count of blocks (blocks with maximum ID) + * This method only works if current number of blocks and + * number of blocks in one extent are divisible by range_count. + * range_count must also be less or equal to number of blocks in one extent. + */ +void +matras_dealloc_range(struct matras *m, matras_id_t range_count) +{ + assert(m->block_count % range_count == 0); + assert(m->extent_size / m->block_size % range_count == 0); + m->block_count -= (range_count - 1); + matras_dealloc(m); +} + /** * Return the number of allocated extents (of size m->extent_size each) */ diff --git a/src/lib/small/matras.h b/src/lib/small/matras.h index 06bd34f7400328a36026a7ff47eef02323f9441d..24595995a65ded6134ead750b8c537477337ee74 100644 --- a/src/lib/small/matras.h +++ b/src/lib/small/matras.h @@ -82,7 +82,13 @@ */ /* }}} */ +#ifdef WIN32 +typedef unsigned __int32 matras_id_t; +#else #include <stdint.h> +typedef uint32_t matras_id_t; +#endif + #include <assert.h> #if defined(__cplusplus) @@ -92,7 +98,6 @@ extern "C" { /** * Type of a block ID. */ -typedef uint32_t matras_id_t; /** * Type of the extent allocator (the allocator for regions @@ -109,7 +114,7 @@ typedef void (*prov_free_func)(void *); struct matras { /* Pointer to the root extent of matras */ void *extent; - /* Block size (M) */ + /* Block size (N) */ matras_id_t block_size; /* Extent size (M) */ matras_id_t extent_size; @@ -186,6 +191,26 @@ matras_alloc(struct matras *m, matras_id_t *id); void matras_dealloc(struct matras *m); +/** + * Allocate a range_count of blocks. Return both, first block pointer + * and first block id. This method only works if current number of blocks and + * number of blocks in one extent are divisible by range_count. + * range_count must also be less or equal to number of blocks in one extent. + * + * @retval NULL failed to allocate memory + */ +void * +matras_alloc_range(struct matras *m, matras_id_t *id, matras_id_t range_count); + +/* + * Deallocate last range_count of blocks (blocks with maximum ID) + * This method only works if current number of blocks and + * number of blocks in one extent are divisible by range_count. + * range_count must also be less or equal to number of blocks in one extent. + */ +void +matras_dealloc_range(struct matras *m, matras_id_t range_count); + /** * Convert block id into block address. */ @@ -215,8 +240,6 @@ matras_get(const struct matras *m, matras_id_t id) return (((char***)m->extent)[n1][n2] + n3 * m->block_size); } - - #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/test/box/access_misc.result b/test/box/access_misc.result index 52e788ab82c63a65cb52272990db158e47bc9738..35c5509b940fb21e6c6ee9d66f7be87381e72e47 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -178,8 +178,8 @@ session.su('admin') ... s:select() --- -- - [3] - - [2] +- - [2] + - [3] ... -- -- Create user with universe read&write grants @@ -244,8 +244,8 @@ box.schema.user.drop('uniuser_testus') -- box.space.admin_space:select() --- -- - [3] - - [2] +- - [2] + - [3] ... box.space._user:select(1) --- diff --git a/test/box/errinj_index.result b/test/box/errinj_index.result index 47b26fc5a53ebfb09d39b6a2384875d4142ecd59..c232a89b22d7c00cd9e8d52702c5764a8d02aebf 100644 --- a/test/box/errinj_index.result +++ b/test/box/errinj_index.result @@ -352,7 +352,7 @@ res ... for i = 501,2500 do s:insert{i, i} end --- -- error: Failed to allocate 23 bytes in hash for key +- error: Failed to allocate 10 bytes in hash_table for key ... s:delete{1} --- @@ -406,7 +406,7 @@ check_iter_and_size(9) ... for i = 2501,3500 do s:insert{i, i} end --- -- error: Failed to allocate 23 bytes in hash for key +- error: Failed to allocate 9 bytes in hash_table for key ... s:delete{2} --- @@ -435,7 +435,7 @@ res ... for i = 3501,4500 do s:insert{i, i} end --- -- error: Failed to allocate 23 bytes in hash for key +- error: Failed to allocate 8 bytes in hash_table for key ... s:delete{3} --- diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index c15d0861ab701aebcd96d20a6437834b30463572..143288c05cb049b6ebd13749225947f9a93a0e29 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -48,6 +48,8 @@ add_executable(rtree_itr.test rtree_itr.cc ${CMAKE_SOURCE_DIR}/src/lib/salad/rtr target_link_libraries(rtree_itr.test) add_executable(matras.test matras.cc) target_link_libraries(matras.test small) +add_executable(light.test light.cc) +target_link_libraries(light.test small) add_executable(vclock.test vclock.cc unit.c ${CMAKE_SOURCE_DIR}/src/box/vclock.c ${CMAKE_SOURCE_DIR}/src/box/errcode.c diff --git a/test/unit/light.cc b/test/unit/light.cc new file mode 100644 index 0000000000000000000000000000000000000000..2dee5dcba3040cb2260857dfaea5e4ac08d5ce19 --- /dev/null +++ b/test/unit/light.cc @@ -0,0 +1,193 @@ +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <stdbool.h> +#include <inttypes.h> +#include <vector> + +#include "unit.h" + +typedef uint64_t hash_value_t; +typedef uint32_t hash_t; + +static const size_t light_extent_size = 16 * 1024; +static size_t extents_count = 0; + +hash_t +hash(hash_value_t value) +{ + return (hash_t) value; +} + +bool +equal(hash_value_t v1, hash_value_t v2) +{ + return v1 == v2; +} + +bool +equal_key(hash_value_t v1, hash_value_t v2) +{ + return v1 == v2; +} + +inline void * +my_light_alloc() +{ + extents_count++; + char *draft = (char *)malloc(light_extent_size + 64 + 8); + void *result = draft + 8 + (63 - ((uint64_t)(draft + 8) % 64)); + ((void **)result)[-1] = draft; + return result; +} + +inline void +my_light_free(void *p) +{ + extents_count--; + free(((void **)p)[-1]); +} + +#define LIGHT_NAME +#define LIGHT_DATA_TYPE uint64_t +#define LIGHT_KEY_TYPE uint64_t +#define LIGHT_CMP_ARG_TYPE int +#define LIGHT_EQUAL(a, b, arg) equal(a, b) +#define LIGHT_EQUAL_KEY(a, b, arg) equal_key(a, b) +#include "salad/light.h" + + +static void +simple_test() +{ + header(); + + struct light_core ht; + light_create(&ht, light_extent_size, my_light_alloc, my_light_free, 0); + std::vector<bool> vect; + size_t count = 0; + const size_t rounds = 1000; + const size_t start_limits = 20; + for(size_t limits = start_limits; limits <= 2 * rounds; limits *= 10) { + while (vect.size() < limits) + vect.push_back(false); + for (size_t i = 0; i < rounds; i++) { + + hash_value_t val = rand() % limits; + hash_t h = hash(val); + hash_t fnd = light_find(&ht, h, val); + bool has1 = fnd != light_end; + bool has2 = vect[val]; + assert(has1 == has2); + if (has1 != has2) { + fail("find key failed!", "true"); + return; + } + + if (!has1) { + count++; + vect[val] = true; + light_insert(&ht, h, val); + } else { + count--; + vect[val] = false; + light_delete(&ht, fnd); + } + + if (count != ht.count) + fail("count check failed!", "true"); + + bool identical = true; + for (hash_value_t test = 0; test < limits; test++) { + if (vect[test]) { + if (light_find(&ht, hash(test), test) == light_end) + identical = false; + } else { + if (light_find(&ht, hash(test), test) != light_end) + identical = false; + } + } + if (!identical) + fail("internal test failed!", "true"); + + int check = light_selfcheck(&ht); + if (check) + fail("internal test failed!", "true"); + } + } + light_destroy(&ht); + + footer(); +} + +static void +collision_test() +{ + header(); + + struct light_core ht; + light_create(&ht, light_extent_size, my_light_alloc, my_light_free, 0); + std::vector<bool> vect; + size_t count = 0; + const size_t rounds = 100; + const size_t start_limits = 20; + for(size_t limits = start_limits; limits <= 2 * rounds; limits *= 10) { + while (vect.size() < limits) + vect.push_back(false); + for (size_t i = 0; i < rounds; i++) { + + hash_value_t val = rand() % limits; + hash_t h = hash(val); + hash_t fnd = light_find(&ht, h * 1024, val); + bool has1 = fnd != light_end; + bool has2 = vect[val]; + assert(has1 == has2); + if (has1 != has2) { + fail("find key failed!", "true"); + return; + } + + if (!has1) { + count++; + vect[val] = true; + light_insert(&ht, h * 1024, val); + } else { + count--; + vect[val] = false; + light_delete(&ht, fnd); + } + + if (count != ht.count) + fail("count check failed!", "true"); + + bool identical = true; + for (hash_value_t test = 0; test < limits; test++) { + if (vect[test]) { + if (light_find(&ht, hash(test) * 1024, test) == light_end) + identical = false; + } else { + if (light_find(&ht, hash(test) * 1024, test) != light_end) + identical = false; + } + } + if (!identical) + fail("internal test failed!", "true"); + + int check = light_selfcheck(&ht); + if (check) + fail("internal test failed!", "true"); + } + } + light_destroy(&ht); + + footer(); +} + +int +main(int, const char**) +{ + simple_test(); + collision_test(); + if (extents_count != 0) + fail("memory leak!", "true"); +} diff --git a/test/unit/light.result b/test/unit/light.result new file mode 100644 index 0000000000000000000000000000000000000000..b37b0ed568394b2cebd65e2e951c4a5c5c7528f9 --- /dev/null +++ b/test/unit/light.result @@ -0,0 +1,5 @@ + *** simple_test *** + *** simple_test: done *** + *** collision_test *** + *** collision_test: done *** + \ No newline at end of file diff --git a/test/wal/oom.result b/test/wal/oom.result index 1256e4946451bae12e270fa922f59beffc3d6022..7487cd4252efadeca13d0263545e7c3de65ad122 100644 --- a/test/wal/oom.result +++ b/test/wal/oom.result @@ -15,11 +15,11 @@ while true do i = i + 1 end; --- -- error: Failed to allocate 252 bytes in slab allocator for tuple +- error: Failed to allocate 244 bytes in slab allocator for tuple ... space:len(); --- -- 58 +- 56 ... i = 1; --- @@ -29,11 +29,11 @@ while true do i = i + 1 end; --- -- error: Failed to allocate 252 bytes in slab allocator for tuple +- error: Failed to allocate 244 bytes in slab allocator for tuple ... space:len(); --- -- 116 +- 112 ... i = 1; --- @@ -43,12 +43,12 @@ while true do i = i + 1 end; --- -- error: Failed to allocate 249 bytes in slab allocator for tuple +- error: Failed to allocate 241 bytes in slab allocator for tuple ... --# setopt delimiter '' space:len() --- -- 173 +- 167 ... space.index['primary']:get{0} ---