* [tarantool-patches] [PATCH 0/3] Optimize and simplify some tuple_update things
@ 2018-04-27 22:36 Vladislav Shpilevoy
2018-04-27 22:36 ` [tarantool-patches] [PATCH 1/3] vinyl: remove vy_apply_upsert_ops Vladislav Shpilevoy
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Vladislav Shpilevoy @ 2018-04-27 22:36 UTC (permalink / raw)
To: tarantool-patches; +Cc: kostja
Branch: http://github.com/tarantool/tarantool/tree/gh-1261-update-by-json-preliminary
Issue: https://github.com/tarantool/tarantool/issues/1261
The patchset is a preparation for tuple update by JSON. It removes big amount of
legacy and not effective code, especially from tuple_update.c, and improves
tuple update performance by elimination of function pointers from rope library.
Vladislav Shpilevoy (3):
vinyl: remove vy_apply_upsert_ops
tuple_update: remove alloc and alloc_ctx args
rope: make rope library be C template using macros
debian/copyright | 2 +-
src/box/memtx_space.c | 18 +-
src/box/space.c | 23 +-
src/box/tuple.c | 6 +-
src/box/tuple_update.c | 123 ++++---
src/box/tuple_update.h | 14 +-
src/box/vinyl.c | 10 +-
src/box/vy_upsert.c | 64 +---
src/lib/salad/CMakeLists.txt | 2 +-
src/lib/salad/rope.c | 664 ------------------------------------
src/lib/salad/rope.h | 790 ++++++++++++++++++++++++++++++++++++++-----
test/unit/column_mask.c | 30 +-
test/unit/rope.c | 1 -
test/unit/rope_avl.c | 1 -
test/unit/rope_basic.c | 1 -
test/unit/rope_common.h | 21 +-
test/unit/rope_stress.c | 5 +-
17 files changed, 834 insertions(+), 941 deletions(-)
delete mode 100644 src/lib/salad/rope.c
--
2.15.1 (Apple Git-101)
^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] [PATCH 1/3] vinyl: remove vy_apply_upsert_ops
2018-04-27 22:36 [tarantool-patches] [PATCH 0/3] Optimize and simplify some tuple_update things Vladislav Shpilevoy
@ 2018-04-27 22:36 ` Vladislav Shpilevoy
2018-05-08 20:18 ` [tarantool-patches] " Konstantin Osipov
2018-04-27 22:36 ` [tarantool-patches] [PATCH 2/3] tuple_update: remove alloc and alloc_ctx args Vladislav Shpilevoy
2018-04-27 22:36 ` [tarantool-patches] [PATCH 3/3] rope: make rope library be C template using macros Vladislav Shpilevoy
2 siblings, 1 reply; 7+ messages in thread
From: Vladislav Shpilevoy @ 2018-04-27 22:36 UTC (permalink / raw)
To: tarantool-patches; +Cc: kostja
Function vy_apply_upsert_opts originaly appears in this commit:
5627e53bf020bf83b8f74d3d04b93047bdf221b4, where it is a
refactored version of a sophia upsertion. But when a vy_stmt was
introduced, vinyl_apply_upsert_ops works just like ordinary
tuple_upsert_execute. Remove this useless wrapper.
---
src/box/vy_upsert.c | 43 +++++--------------------------------------
1 file changed, 5 insertions(+), 38 deletions(-)
diff --git a/src/box/vy_upsert.c b/src/box/vy_upsert.c
index 67f9ef09f..7af58d967 100644
--- a/src/box/vy_upsert.c
+++ b/src/box/vy_upsert.c
@@ -49,42 +49,6 @@ vy_update_alloc(void *arg, size_t size)
return data;
}
-/**
- * vinyl wrapper of tuple_upsert_execute.
- * vibyl upsert opts are slightly different from tarantool ops,
- * so they need some preparation before tuple_upsert_execute call.
- * The function does this preparation.
- * On successfull upsert the result is placed into stmt and stmt_end args.
- * On fail the stmt and stmt_end args are not changed.
- * Possibly allocates new stmt via fiber region alloc,
- * so call fiber_gc() after usage
- */
-static void
-vy_apply_upsert_ops(struct region *region, const char **stmt,
- const char **stmt_end, const char *ops, const char *ops_end,
- bool suppress_error, uint64_t *column_mask)
-{
- if (ops == ops_end)
- return;
-
-#ifndef NDEBUG
- const char *serie_end_must_be = ops;
- mp_next(&serie_end_must_be);
- assert(ops_end == serie_end_must_be);
-#endif
- const char *result;
- uint32_t size;
- result = tuple_upsert_execute(vy_update_alloc, region,
- ops, ops_end,
- *stmt, *stmt_end,
- &size, 0, suppress_error, column_mask);
- if (result != NULL) {
- /* if failed, just skip it and leave stmt the same */
- *stmt = result;
- *stmt_end = result + size;
- }
-}
-
/**
* Try to squash two upsert series (msgspacked index_base + ops)
* Try to create a tuple with squahed operations
@@ -165,8 +129,11 @@ vy_apply_upsert(const struct tuple *new_stmt, const struct tuple *old_stmt,
size_t region_svp = region_used(region);
uint8_t old_type = vy_stmt_type(old_stmt);
uint64_t column_mask = COLUMN_MASK_FULL;
- vy_apply_upsert_ops(region, &result_mp, &result_mp_end, new_ops,
- new_ops_end, suppress_error, &column_mask);
+ result_mp = tuple_upsert_execute(vy_update_alloc, region, new_ops,
+ new_ops_end, result_mp, result_mp_end,
+ &mp_size, 0, suppress_error,
+ &column_mask);
+ result_mp_end = result_mp + mp_size;
if (old_type != IPROTO_UPSERT) {
assert(old_type == IPROTO_INSERT ||
old_type == IPROTO_REPLACE);
--
2.15.1 (Apple Git-101)
^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] [PATCH 2/3] tuple_update: remove alloc and alloc_ctx args
2018-04-27 22:36 [tarantool-patches] [PATCH 0/3] Optimize and simplify some tuple_update things Vladislav Shpilevoy
2018-04-27 22:36 ` [tarantool-patches] [PATCH 1/3] vinyl: remove vy_apply_upsert_ops Vladislav Shpilevoy
@ 2018-04-27 22:36 ` Vladislav Shpilevoy
2018-05-08 20:18 ` [tarantool-patches] " Konstantin Osipov
2018-04-27 22:36 ` [tarantool-patches] [PATCH 3/3] rope: make rope library be C template using macros Vladislav Shpilevoy
2 siblings, 1 reply; 7+ messages in thread
From: Vladislav Shpilevoy @ 2018-04-27 22:36 UTC (permalink / raw)
To: tarantool-patches; +Cc: kostja
They are always region_aligned_alloc and region pointer. Lets use
them always inside tuple_update.c, with no necessity to pass via
args.
---
src/box/memtx_space.c | 18 +++++-------
src/box/space.c | 23 ++++++++-------
src/box/tuple.c | 6 ++--
src/box/tuple_update.c | 75 +++++++++++++++++++++++++------------------------
src/box/tuple_update.h | 14 +++------
src/box/vinyl.c | 10 ++-----
src/box/vy_upsert.c | 29 +++++--------------
test/unit/column_mask.c | 30 ++++++++------------
8 files changed, 85 insertions(+), 120 deletions(-)
diff --git a/src/box/memtx_space.c b/src/box/memtx_space.c
index 73df4c7d5..b40a91e67 100644
--- a/src/box/memtx_space.c
+++ b/src/box/memtx_space.c
@@ -385,8 +385,7 @@ memtx_space_execute_update(struct space *space, struct txn *txn,
uint32_t new_size = 0, bsize;
const char *old_data = tuple_data_range(stmt->old_tuple, &bsize);
const char *new_data =
- tuple_update_execute(region_aligned_alloc_cb, &fiber()->gc,
- request->tuple, request->tuple_end,
+ tuple_update_execute(request->tuple, request->tuple_end,
old_data, old_data + bsize,
&new_size, request->index_base, NULL);
if (new_data == NULL)
@@ -452,9 +451,8 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
* we get it here, it's also OK to throw it
* @sa https://github.com/tarantool/tarantool/issues/1156
*/
- if (tuple_update_check_ops(region_aligned_alloc_cb, &fiber()->gc,
- request->ops, request->ops_end,
- request->index_base)) {
+ if (tuple_update_check_ops(request->ops, request->ops_end,
+ request->index_base)) {
return -1;
}
stmt->new_tuple = memtx_tuple_new(space->format,
@@ -475,12 +473,10 @@ memtx_space_execute_upsert(struct space *space, struct txn *txn,
*/
uint64_t column_mask = COLUMN_MASK_FULL;
const char *new_data =
- tuple_upsert_execute(region_aligned_alloc_cb,
- &fiber()->gc, request->ops,
- request->ops_end, old_data,
- old_data + bsize, &new_size,
- request->index_base, false,
- &column_mask);
+ tuple_upsert_execute(request->ops, request->ops_end,
+ old_data, old_data + bsize,
+ &new_size, request->index_base,
+ false, &column_mask);
if (new_data == NULL)
return -1;
diff --git a/src/box/space.c b/src/box/space.c
index 02a97926e..3e5fa86c1 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -275,7 +275,6 @@ space_before_replace(struct space *space, struct txn *txn,
return 0;
}
- struct region *gc = &fiber()->gc;
enum iproto_type type = request->type;
struct index *pk = space->index[0];
@@ -332,10 +331,10 @@ space_before_replace(struct space *space, struct txn *txn,
}
old_data = tuple_data_range(old_tuple, &old_size);
old_data_end = old_data + old_size;
- new_data = tuple_update_execute(region_aligned_alloc_cb, gc,
- request->tuple, request->tuple_end,
- old_data, old_data_end, &new_size,
- request->index_base, NULL);
+ new_data = tuple_update_execute(request->tuple,
+ request->tuple_end, old_data,
+ old_data_end, &new_size,
+ request->index_base, NULL);
if (new_data == NULL)
return -1;
new_data_end = new_data + new_size;
@@ -355,18 +354,18 @@ space_before_replace(struct space *space, struct txn *txn,
*/
new_data = request->tuple;
new_data_end = request->tuple_end;
- if (tuple_update_check_ops(region_aligned_alloc_cb, gc,
- request->ops, request->ops_end,
- request->index_base) != 0)
+ if (tuple_update_check_ops(request->ops,
+ request->ops_end,
+ request->index_base) != 0)
return -1;
break;
}
old_data = tuple_data_range(old_tuple, &old_size);
old_data_end = old_data + old_size;
- new_data = tuple_upsert_execute(region_aligned_alloc_cb, gc,
- request->ops, request->ops_end,
- old_data, old_data_end, &new_size,
- request->index_base, false, NULL);
+ new_data = tuple_upsert_execute(request->ops, request->ops_end,
+ old_data, old_data_end,
+ &new_size, request->index_base,
+ false, NULL);
new_data_end = new_data + new_size;
break;
default:
diff --git a/src/box/tuple.c b/src/box/tuple.c
index d4760f3b1..e10129fe2 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -403,8 +403,7 @@ box_tuple_update(const box_tuple_t *tuple, const char *expr,
struct region *region = &fiber()->gc;
size_t used = region_used(region);
const char *new_data =
- tuple_update_execute(region_aligned_alloc_cb, region, expr,
- expr_end, old_data, old_data + bsize,
+ tuple_update_execute(expr, expr_end, old_data, old_data + bsize,
&new_size, 1, NULL);
if (new_data == NULL) {
region_truncate(region, used);
@@ -429,8 +428,7 @@ box_tuple_upsert(const box_tuple_t *tuple, const char *expr,
struct region *region = &fiber()->gc;
size_t used = region_used(region);
const char *new_data =
- tuple_upsert_execute(region_aligned_alloc_cb, region, expr,
- expr_end, old_data, old_data + bsize,
+ tuple_upsert_execute(expr, expr_end, old_data, old_data + bsize,
&new_size, 1, false, NULL);
if (new_data == NULL) {
region_truncate(region, used);
diff --git a/src/box/tuple_update.c b/src/box/tuple_update.c
index 7bf210433..03a293785 100644
--- a/src/box/tuple_update.c
+++ b/src/box/tuple_update.c
@@ -43,7 +43,7 @@
#include <bit/int96.h>
#include <salad/rope.h>
#include "column_mask.h"
-
+#include "fiber.h"
/** UPDATE request implementation.
* UPDATE request is represented by a sequence of operations, each
@@ -96,10 +96,28 @@
* deleted one.
*/
+static inline void *
+update_alloc(void *ctx, size_t size)
+{
+ void *ptr = region_aligned_alloc((struct region *) ctx, size,
+ alignof(uint64_t));
+ if (ptr == NULL)
+ diag_set(OutOfMemory, size, "region_aligned_alloc",
+ "update internals");
+ return ptr;
+}
+
+/** Free rope node - do nothing, since we use a pool allocator. */
+static void
+update_free(void *ctx, void *mem)
+{
+ (void) ctx;
+ (void) mem;
+}
+
/** Update internal state */
struct tuple_update
{
- tuple_update_alloc_func alloc;
void *alloc_ctx;
struct rope *rope;
struct update_op *ops;
@@ -508,7 +526,7 @@ do_op_insert(struct tuple_update *update, struct update_op *op)
if (op_adjust_field_no(update, op, rope_size(update->rope) + 1))
return -1;
struct update_field *field = (struct update_field *)
- update->alloc(update->alloc_ctx, sizeof(*field));
+ update_alloc(update->alloc_ctx, sizeof(*field));
if (field == NULL)
return -1;
update_field_init(field, op->arg.set.value, op->arg.set.length, 0);
@@ -778,7 +796,7 @@ update_field_split(void *split_ctx, void *data, size_t size, size_t offset)
struct update_field *prev = (struct update_field *) data;
struct update_field *next = (struct update_field *)
- update->alloc(update->alloc_ctx, sizeof(*next));
+ update_alloc(update->alloc_ctx, sizeof(*next));
if (next == NULL)
return NULL;
assert(offset > 0 && prev->tail_len > 0);
@@ -799,14 +817,6 @@ update_field_split(void *split_ctx, void *data, size_t size, size_t offset)
return next;
}
-/** Free rope node - do nothing, since we use a pool allocator. */
-static void
-region_alloc_free_stub(void *ctx, void *mem)
-{
- (void) ctx;
- (void) mem;
-}
-
/**
* We found a tuple to do the update on. Prepare a rope
* to perform operations on.
@@ -822,14 +832,14 @@ int
update_create_rope(struct tuple_update *update, const char *tuple_data,
const char *tuple_data_end, uint32_t field_count)
{
- update->rope = rope_new(update_field_split, update, update->alloc,
- region_alloc_free_stub, update->alloc_ctx);
+ update->rope = rope_new(update_field_split, update, update_alloc,
+ update_free, update->alloc_ctx);
if (update->rope == NULL)
return -1;
/* Initialize the rope with the old tuple. */
struct update_field *first = (struct update_field *)
- update->alloc(update->alloc_ctx, sizeof(*first));
+ update_alloc(update->alloc_ctx, sizeof(*first));
if (first == NULL)
return -1;
const char *field = tuple_data;
@@ -968,7 +978,7 @@ update_read_ops(struct tuple_update *update, const char *expr,
}
/* Read update operations. */
- update->ops = (struct update_op *) update->alloc(update->alloc_ctx,
+ update->ops = (struct update_op *) update_alloc(update->alloc_ctx,
update->op_count * sizeof(struct update_op));
if (update->ops == NULL)
return -1;
@@ -1158,13 +1168,10 @@ upsert_do_ops(struct tuple_update *update, const char *old_data,
}
static void
-update_init(struct tuple_update *update,
- tuple_update_alloc_func alloc, void *alloc_ctx,
- int index_base)
+update_init(struct tuple_update *update, int index_base)
{
memset(update, 0, sizeof(*update));
- update->alloc = alloc;
- update->alloc_ctx = alloc_ctx;
+ update->alloc_ctx = &fiber()->gc;
/*
* Base field offset, e.g. 0 for C and 1 for Lua. Used only for
* error messages. All fields numbers must be zero-based!
@@ -1176,7 +1183,7 @@ const char *
update_finish(struct tuple_update *update, uint32_t *p_tuple_len)
{
uint32_t tuple_len = update_calc_tuple_length(update);
- char *buffer = (char *) update->alloc(update->alloc_ctx, tuple_len);
+ char *buffer = (char *) update_alloc(update->alloc_ctx, tuple_len);
if (buffer == NULL)
return NULL;
*p_tuple_len = update_write_tuple(update, buffer, buffer + tuple_len);
@@ -1184,23 +1191,21 @@ update_finish(struct tuple_update *update, uint32_t *p_tuple_len)
}
int
-tuple_update_check_ops(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr, const char *expr_end, int index_base)
+tuple_update_check_ops(const char *expr, const char *expr_end, int index_base)
{
struct tuple_update update;
- update_init(&update, alloc, alloc_ctx, index_base);
+ update_init(&update, index_base);
return update_read_ops(&update, expr, expr_end, 0);
}
const char *
-tuple_update_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr,const char *expr_end,
+tuple_update_execute(const char *expr,const char *expr_end,
const char *old_data, const char *old_data_end,
uint32_t *p_tuple_len, int index_base,
uint64_t *column_mask)
{
struct tuple_update update;
- update_init(&update, alloc, alloc_ctx, index_base);
+ update_init(&update, index_base);
uint32_t field_count = mp_decode_array(&old_data);
if (update_read_ops(&update, expr, expr_end, field_count) != 0)
@@ -1214,14 +1219,13 @@ tuple_update_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
}
const char *
-tuple_upsert_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr,const char *expr_end,
+tuple_upsert_execute(const char *expr,const char *expr_end,
const char *old_data, const char *old_data_end,
uint32_t *p_tuple_len, int index_base, bool suppress_error,
uint64_t *column_mask)
{
struct tuple_update update;
- update_init(&update, alloc, alloc_ctx, index_base);
+ update_init(&update, index_base);
uint32_t field_count = mp_decode_array(&old_data);
if (update_read_ops(&update, expr, expr_end, field_count) != 0)
@@ -1236,8 +1240,7 @@ tuple_upsert_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
}
const char *
-tuple_upsert_squash(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr1, const char *expr1_end,
+tuple_upsert_squash(const char *expr1, const char *expr1_end,
const char *expr2, const char *expr2_end,
size_t *result_size, int index_base)
{
@@ -1245,7 +1248,7 @@ tuple_upsert_squash(tuple_update_alloc_func alloc, void *alloc_ctx,
const char *expr_end[2] = {expr1_end, expr2_end};
struct tuple_update update[2];
for (int j = 0; j < 2; j++) {
- update_init(&update[j], alloc, alloc_ctx, index_base);
+ update_init(&update[j], index_base);
if (update_read_ops(&update[j], expr[j], expr_end[j], 0))
return NULL;
mp_decode_array(&expr[j]);
@@ -1262,8 +1265,8 @@ tuple_upsert_squash(tuple_update_alloc_func alloc, void *alloc_ctx,
}
size_t possible_size = expr1_end - expr1 + expr2_end - expr2;
const uint32_t space_for_arr_tag = 5;
- char *buf = (char *)alloc(alloc_ctx,
- possible_size + space_for_arr_tag);
+ char *buf = (char *) update_alloc(&fiber()->gc,
+ possible_size + space_for_arr_tag);
if (buf == NULL)
return NULL;
/* reserve some space for mp array header */
diff --git a/src/box/tuple_update.h b/src/box/tuple_update.h
index 706edd55a..37faa1918 100644
--- a/src/box/tuple_update.h
+++ b/src/box/tuple_update.h
@@ -44,22 +44,17 @@ enum {
BOX_UPDATE_OP_CNT_MAX = 4000,
};
-typedef void *(*tuple_update_alloc_func)(void *, size_t);
-
int
-tuple_update_check_ops(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr, const char *expr_end, int index_base);
+tuple_update_check_ops(const char *expr, const char *expr_end, int index_base);
const char *
-tuple_update_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr,const char *expr_end,
+tuple_update_execute(const char *expr,const char *expr_end,
const char *old_data, const char *old_data_end,
uint32_t *p_new_size, int index_base,
uint64_t *column_mask);
const char *
-tuple_upsert_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr, const char *expr_end,
+tuple_upsert_execute(const char *expr, const char *expr_end,
const char *old_data, const char *old_data_end,
uint32_t *p_new_size, int index_base, bool suppress_error,
uint64_t *column_mask);
@@ -75,8 +70,7 @@ tuple_upsert_execute(tuple_update_alloc_func alloc, void *alloc_ctx,
* If it isn't possible to merge expressions NULL is returned.
*/
const char *
-tuple_upsert_squash(tuple_update_alloc_func alloc, void *alloc_ctx,
- const char *expr1, const char *expr1_end,
+tuple_upsert_squash(const char *expr1, const char *expr1_end,
const char *expr2, const char *expr2_end,
size_t *result_size, int index_base);
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index ff4c28314..30261e41d 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -1877,8 +1877,7 @@ vy_update(struct vy_env *env, struct vy_tx *tx, struct txn_stmt *stmt,
uint32_t new_size, old_size;
const char *old_tuple = tuple_data_range(stmt->old_tuple, &old_size);
const char *old_tuple_end = old_tuple + old_size;
- new_tuple = tuple_update_execute(region_aligned_alloc_cb, &fiber()->gc,
- request->tuple, request->tuple_end,
+ new_tuple = tuple_update_execute(request->tuple, request->tuple_end,
old_tuple, old_tuple_end, &new_size,
request->index_base, &column_mask);
if (new_tuple == NULL)
@@ -2090,8 +2089,7 @@ vy_upsert(struct vy_env *env, struct vy_tx *tx, struct txn_stmt *stmt,
if (vy_is_committed(env, space))
return 0;
/* Check update operations. */
- if (tuple_update_check_ops(region_aligned_alloc_cb, &fiber()->gc,
- request->ops, request->ops_end,
+ if (tuple_update_check_ops(request->ops, request->ops_end,
request->index_base)) {
return -1;
}
@@ -2154,9 +2152,7 @@ vy_upsert(struct vy_env *env, struct vy_tx *tx, struct txn_stmt *stmt,
old_tuple_end = old_tuple + old_size;
/* Apply upsert operations to the old tuple. */
- new_tuple = tuple_upsert_execute(region_aligned_alloc_cb,
- &fiber()->gc, ops, ops_end,
- old_tuple, old_tuple_end,
+ new_tuple = tuple_upsert_execute(ops, ops_end, old_tuple, old_tuple_end,
&new_size, 0, false, &column_mask);
if (new_tuple == NULL)
return -1;
diff --git a/src/box/vy_upsert.c b/src/box/vy_upsert.c
index 7af58d967..8211ba968 100644
--- a/src/box/vy_upsert.c
+++ b/src/box/vy_upsert.c
@@ -38,17 +38,6 @@
#include "fiber.h"
#include "column_mask.h"
-static void *
-vy_update_alloc(void *arg, size_t size)
-{
- /* TODO: rewrite tuple_upsert_execute() without exceptions */
- struct region *region = (struct region *) arg;
- void *data = region_aligned_alloc(region, size, sizeof(uint64_t));
- if (data == NULL)
- diag_set(OutOfMemory, size, "region", "upsert");
- return data;
-}
-
/**
* Try to squash two upsert series (msgspacked index_base + ops)
* Try to create a tuple with squahed operations
@@ -58,7 +47,7 @@ vy_update_alloc(void *arg, size_t size)
* @retval -1 - memory error
*/
static int
-vy_upsert_try_to_squash(struct tuple_format *format, struct region *region,
+vy_upsert_try_to_squash(struct tuple_format *format,
const char *key_mp, const char *key_mp_end,
const char *old_serie, const char *old_serie_end,
const char *new_serie, const char *new_serie_end,
@@ -68,8 +57,7 @@ vy_upsert_try_to_squash(struct tuple_format *format, struct region *region,
size_t squashed_size;
const char *squashed =
- tuple_upsert_squash(vy_update_alloc, region,
- old_serie, old_serie_end,
+ tuple_upsert_squash(old_serie, old_serie_end,
new_serie, new_serie_end,
&squashed_size, 0);
if (squashed == NULL)
@@ -129,10 +117,9 @@ vy_apply_upsert(const struct tuple *new_stmt, const struct tuple *old_stmt,
size_t region_svp = region_used(region);
uint8_t old_type = vy_stmt_type(old_stmt);
uint64_t column_mask = COLUMN_MASK_FULL;
- result_mp = tuple_upsert_execute(vy_update_alloc, region, new_ops,
- new_ops_end, result_mp, result_mp_end,
- &mp_size, 0, suppress_error,
- &column_mask);
+ result_mp = tuple_upsert_execute(new_ops, new_ops_end, result_mp,
+ result_mp_end, &mp_size, 0,
+ suppress_error, &column_mask);
result_mp_end = result_mp + mp_size;
if (old_type != IPROTO_UPSERT) {
assert(old_type == IPROTO_INSERT ||
@@ -162,10 +149,8 @@ vy_apply_upsert(const struct tuple *new_stmt, const struct tuple *old_stmt,
* UPSERT + UPSERT case: combine operations
*/
assert(old_ops_end - old_ops > 0);
- if (vy_upsert_try_to_squash(format, region,
- result_mp, result_mp_end,
- old_ops, old_ops_end,
- new_ops, new_ops_end,
+ if (vy_upsert_try_to_squash(format, result_mp, result_mp_end,
+ old_ops, old_ops_end, new_ops, new_ops_end,
&result_stmt) != 0) {
region_truncate(region, region_svp);
return NULL;
diff --git a/test/unit/column_mask.c b/test/unit/column_mask.c
index 7859626f8..3ffd351ac 100644
--- a/test/unit/column_mask.c
+++ b/test/unit/column_mask.c
@@ -3,6 +3,9 @@
#include "unit.h"
#include "msgpuck.h"
#include "trivia/util.h"
+#include "fiber.h"
+#include "memory.h"
+#include "tuple.h"
#define MAX_OPS 20
#define MAX_FIELDS 100
@@ -102,20 +105,6 @@ tuple_new_update(const struct tuple_update_template *update, char **end)
return ret;
}
-static char buffer[2048];
-static size_t pos = 0;
-
-/** Allocator for the tuple_update function. */
-static void *
-tuple_update_alloc_f(void *unused, size_t size)
-{
- (void) unused;
- fail_if(pos + size > sizeof(buffer));
- char *ret = &buffer[pos];
- pos += size;
- return ret;
-}
-
/**
* Execute an update operation from the template and compare it
* with the expected tuple and expected column_mask.
@@ -138,20 +127,19 @@ check_update_result(const struct tuple_template *original,
uint32_t actual_len;
uint64_t column_mask;
+ struct region *region = &fiber()->gc;
const char *actual =
- tuple_update_execute(tuple_update_alloc_f, NULL, ops, ops_end,
- old, old_end, &actual_len, 1,
+ tuple_update_execute(ops, ops_end, old, old_end, &actual_len, 1,
&column_mask);
fail_if(actual == NULL);
is((int32_t)actual_len, new_end - new, "check result length");
is(memcmp(actual, new, actual_len), 0, "tuple update is correct");
is(column_mask, expected_mask, "column_mask is correct");
+ fiber_gc();
free(old);
free(new);
free(ops);
- /* reset the global buffer. */
- pos = 0;
}
static inline void
@@ -239,6 +227,9 @@ basic_test()
int
main()
{
+ memory_init();
+ fiber_init(fiber_c_invoke);
+ tuple_init(NULL);
header();
plan(27);
@@ -246,4 +237,7 @@ main()
footer();
check_plan();
+ tuple_free();
+ fiber_free();
+ memory_free();
}
--
2.15.1 (Apple Git-101)
^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] [PATCH 3/3] rope: make rope library be C template using macros
2018-04-27 22:36 [tarantool-patches] [PATCH 0/3] Optimize and simplify some tuple_update things Vladislav Shpilevoy
2018-04-27 22:36 ` [tarantool-patches] [PATCH 1/3] vinyl: remove vy_apply_upsert_ops Vladislav Shpilevoy
2018-04-27 22:36 ` [tarantool-patches] [PATCH 2/3] tuple_update: remove alloc and alloc_ctx args Vladislav Shpilevoy
@ 2018-04-27 22:36 ` Vladislav Shpilevoy
2018-05-08 20:22 ` [tarantool-patches] " Konstantin Osipov
2 siblings, 1 reply; 7+ messages in thread
From: Vladislav Shpilevoy @ 2018-04-27 22:36 UTC (permalink / raw)
To: tarantool-patches; +Cc: kostja
Rope library stores alloc, split and free functions by pointer,
that is a huge slog at performance. There is no reason, why the
rope library can not become a template.
Inline a concrete functions on compilation instead of pointers on
runtime.
---
debian/copyright | 2 +-
src/box/tuple_update.c | 84 ++---
src/lib/salad/CMakeLists.txt | 2 +-
src/lib/salad/rope.c | 664 ------------------------------------
src/lib/salad/rope.h | 790 ++++++++++++++++++++++++++++++++++++++-----
test/unit/rope.c | 1 -
test/unit/rope_avl.c | 1 -
test/unit/rope_basic.c | 1 -
test/unit/rope_common.h | 21 +-
test/unit/rope_stress.c | 5 +-
10 files changed, 766 insertions(+), 805 deletions(-)
delete mode 100644 src/lib/salad/rope.c
diff --git a/debian/copyright b/debian/copyright
index a48171371..c56b8a246 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -218,7 +218,7 @@ Files: third_party/lua-cjson/*
Copyright: 2010-2012 Mark Pulford <mark@kyne.com.au>
License: Expat
-Files: src/lib/salad/rope.c
+Files: src/lib/salad/rope.h
Copyright: 1993-1994 by Xerox Corporation.
License: BSD-2-Clause
diff --git a/src/box/tuple_update.c b/src/box/tuple_update.c
index 03a293785..b1e341d55 100644
--- a/src/box/tuple_update.c
+++ b/src/box/tuple_update.c
@@ -30,8 +30,6 @@
*/
#include "tuple_update.h"
-
-#include <stdio.h>
#include <stdbool.h>
#include "say.h"
@@ -41,7 +39,6 @@
#include "third_party/queue.h"
#include <msgpuck/msgpuck.h>
#include <bit/int96.h>
-#include <salad/rope.h>
#include "column_mask.h"
#include "fiber.h"
@@ -97,28 +94,22 @@
*/
static inline void *
-update_alloc(void *ctx, size_t size)
+update_alloc(struct region *region, size_t size)
{
- void *ptr = region_aligned_alloc((struct region *) ctx, size,
- alignof(uint64_t));
+ void *ptr = region_aligned_alloc(region, size, alignof(uint64_t));
if (ptr == NULL)
diag_set(OutOfMemory, size, "region_aligned_alloc",
"update internals");
return ptr;
}
-/** Free rope node - do nothing, since we use a pool allocator. */
-static void
-update_free(void *ctx, void *mem)
-{
- (void) ctx;
- (void) mem;
-}
+struct update_field;
+struct update_op;
/** Update internal state */
struct tuple_update
{
- void *alloc_ctx;
+ struct region *region;
struct rope *rope;
struct update_op *ops;
uint32_t op_count;
@@ -127,6 +118,18 @@ struct tuple_update
uint64_t column_mask;
};
+static struct update_field *
+update_field_split(struct tuple_update *split_ctx, struct update_field *data,
+ size_t size, size_t offset);
+
+#define ROPE_SPLIT_F update_field_split
+#define ROPE_ALLOC_F update_alloc
+#define rope_data_t struct update_field *
+#define rope_alloc_ctx_t struct region *
+#define rope_split_ctx_t struct tuple_update *
+
+#include "salad/rope.h"
+
/** Argument of SET (and INSERT) operation. */
struct op_set_arg {
uint32_t length;
@@ -204,9 +207,6 @@ union update_op_arg {
struct op_splice_arg splice;
};
-struct update_field;
-struct update_op;
-
typedef int (*do_op_func)(struct tuple_update *update, struct update_op *op);
typedef int (*read_arg_func)(int index_base, struct update_op *op,
const char **expr);
@@ -526,7 +526,7 @@ do_op_insert(struct tuple_update *update, struct update_op *op)
if (op_adjust_field_no(update, op, rope_size(update->rope) + 1))
return -1;
struct update_field *field = (struct update_field *)
- update_alloc(update->alloc_ctx, sizeof(*field));
+ update_alloc(update->region, sizeof(*field));
if (field == NULL)
return -1;
update_field_init(field, op->arg.set.value, op->arg.set.length, 0);
@@ -541,8 +541,7 @@ do_op_set(struct tuple_update *update, struct update_op *op)
return do_op_insert(update, op);
if (op_adjust_field_no(update, op, rope_size(update->rope)))
return -1;
- struct update_field *field = (struct update_field *)
- rope_extract(update->rope, op->field_no);
+ struct update_field *field = rope_extract(update->rope, op->field_no);
if (field == NULL)
return -1;
/* Ignore the previous op, if any. */
@@ -579,8 +578,7 @@ do_op_arith(struct tuple_update *update, struct update_op *op)
if (op_adjust_field_no(update, op, rope_size(update->rope)))
return -1;
- struct update_field *field = (struct update_field *)
- rope_extract(update->rope, op->field_no);
+ struct update_field *field = rope_extract(update->rope, op->field_no);
if (field == NULL)
return -1;
if (field->op) {
@@ -609,8 +607,7 @@ do_op_bit(struct tuple_update *update, struct update_op *op)
{
if (op_adjust_field_no(update, op, rope_size(update->rope)))
return -1;
- struct update_field *field = (struct update_field *)
- rope_extract(update->rope, op->field_no);
+ struct update_field *field = rope_extract(update->rope, op->field_no);
if (field == NULL)
return -1;
struct op_bit_arg *arg = &op->arg.bit;
@@ -647,8 +644,7 @@ do_op_splice(struct tuple_update *update, struct update_op *op)
{
if (op_adjust_field_no(update, op, rope_size(update->rope)))
return -1;
- struct update_field *field = (struct update_field *)
- rope_extract(update->rope, op->field_no);
+ struct update_field *field = rope_extract(update->rope, op->field_no);
if (field == NULL)
return -1;
if (field->op) {
@@ -787,16 +783,14 @@ static const struct update_op_meta op_delete =
/** Split a range of fields in two, allocating update_field
* context for the new range.
*/
-static void *
-update_field_split(void *split_ctx, void *data, size_t size, size_t offset)
+static struct update_field *
+update_field_split(struct tuple_update *update, struct update_field *prev,
+ size_t size, size_t offset)
{
- (void)size;
- struct tuple_update *update = (struct tuple_update *) split_ctx;
-
- struct update_field *prev = (struct update_field *) data;
-
- struct update_field *next = (struct update_field *)
- update_alloc(update->alloc_ctx, sizeof(*next));
+ (void) size;
+ struct update_field *next =
+ (struct update_field *) update_alloc(update->region,
+ sizeof(*next));
if (next == NULL)
return NULL;
assert(offset > 0 && prev->tail_len > 0);
@@ -804,9 +798,8 @@ update_field_split(void *split_ctx, void *data, size_t size, size_t offset)
const char *field = prev->tail;
const char *end = field + prev->tail_len;
- for (uint32_t i = 1; i < offset; i++) {
+ for (uint32_t i = 1; i < offset; i++)
mp_next(&field);
- }
prev->tail_len = field - prev->tail;
const char *f = field;
@@ -832,14 +825,13 @@ int
update_create_rope(struct tuple_update *update, const char *tuple_data,
const char *tuple_data_end, uint32_t field_count)
{
- update->rope = rope_new(update_field_split, update, update_alloc,
- update_free, update->alloc_ctx);
+ update->rope = rope_new(update, update->region);
if (update->rope == NULL)
return -1;
/* Initialize the rope with the old tuple. */
struct update_field *first = (struct update_field *)
- update_alloc(update->alloc_ctx, sizeof(*first));
+ update_alloc(update->region, sizeof(*first));
if (first == NULL)
return -1;
const char *field = tuple_data;
@@ -863,8 +855,7 @@ update_calc_tuple_length(struct tuple_update *update)
rope_iter_create(&it, update->rope);
for (node = rope_iter_start(&it); node; node = rope_iter_next(&it)) {
- struct update_field *field =
- (struct update_field *) rope_leaf_data(node);
+ struct update_field *field = rope_leaf_data(node);
uint32_t field_len = (field->op ? field->op->new_field_len :
(uint32_t)(field->tail - field->old));
res += field_len + field->tail_len;
@@ -888,8 +879,7 @@ update_write_tuple(struct tuple_update *update, char *buffer, char *buffer_end)
rope_iter_create(&it, update->rope);
for (node = rope_iter_start(&it); node; node = rope_iter_next(&it)) {
- struct update_field *field = (struct update_field *)
- rope_leaf_data(node);
+ struct update_field *field = rope_leaf_data(node);
uint32_t field_count = rope_leaf_size(node);
const char *old_field = field->old;
struct update_op *op = field->op;
@@ -978,7 +968,7 @@ update_read_ops(struct tuple_update *update, const char *expr,
}
/* Read update operations. */
- update->ops = (struct update_op *) update_alloc(update->alloc_ctx,
+ update->ops = (struct update_op *) update_alloc(update->region,
update->op_count * sizeof(struct update_op));
if (update->ops == NULL)
return -1;
@@ -1171,7 +1161,7 @@ static void
update_init(struct tuple_update *update, int index_base)
{
memset(update, 0, sizeof(*update));
- update->alloc_ctx = &fiber()->gc;
+ update->region = &fiber()->gc;
/*
* Base field offset, e.g. 0 for C and 1 for Lua. Used only for
* error messages. All fields numbers must be zero-based!
@@ -1183,7 +1173,7 @@ const char *
update_finish(struct tuple_update *update, uint32_t *p_tuple_len)
{
uint32_t tuple_len = update_calc_tuple_length(update);
- char *buffer = (char *) update_alloc(update->alloc_ctx, tuple_len);
+ char *buffer = (char *) update_alloc(update->region, tuple_len);
if (buffer == NULL)
return NULL;
*p_tuple_len = update_write_tuple(update, buffer, buffer + tuple_len);
diff --git a/src/lib/salad/CMakeLists.txt b/src/lib/salad/CMakeLists.txt
index 5263c16e4..41cb08898 100644
--- a/src/lib/salad/CMakeLists.txt
+++ b/src/lib/salad/CMakeLists.txt
@@ -1,3 +1,3 @@
-set(lib_sources rope.c rtree.c guava.c bloom.c)
+set(lib_sources rtree.c guava.c bloom.c)
set_source_files_compile_flags(${lib_sources})
add_library(salad STATIC ${lib_sources})
diff --git a/src/lib/salad/rope.c b/src/lib/salad/rope.c
deleted file mode 100644
index 06324ab65..000000000
--- a/src/lib/salad/rope.c
+++ /dev/null
@@ -1,664 +0,0 @@
-/*
- * Copyright 2010-2016, Tarantool AUTHORS, please see AUTHORS file.
- *
- * 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.
- */
-/*
- * Copyright (c) 1993-1994 by Xerox Corporation. All rights reserved.
- *
- * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
- * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
- *
- * Permission is hereby granted to use or copy this program
- * for any purpose, provided the above notices are retained on all copies.
- * Permission to modify the code and to distribute modified code is granted,
- * provided the above notices are retained, and a notice that the code was
- * modified is included with the above copyright notice.
- *
- * Author: Hans-J. Boehm (boehm@parc.xerox.com)
- */
-/*
- * This is a rope implementation which uses AVL tree
- * balancing algorithm for rope tree balance.
- */
-#include "rope.h"
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <assert.h>
-
-static inline int
-rope_node_height(struct rope_node *node)
-{
- return node ? node->height : 0;
-}
-
-#if !defined(MAX)
-#define MAX(a, b) ((a) > (b) ? (a) : (b))
-#endif /* MAX */
-
-static inline void
-rope_relink(struct rope_node *node)
-{
- node->tree_size = (rope_node_size(node->link[0]) +
- rope_node_size(node->link[1]) +
- node->leaf_size);
- node->height = MAX(rope_node_height(node->link[0]),
- rope_node_height(node->link[1])) + 1;
-}
-
-static inline struct rope_node *
-rope_node_new(struct rope *rope, void *data, rope_size_t size)
-{
- struct rope_node *node =
- (struct rope_node *) rope->alloc(rope->alloc_ctx,
- sizeof(struct rope_node));
-
- if (node == NULL)
- return NULL;
-
- node->height = 1;
- node->tree_size = node->leaf_size = size;
- node->data = data;
- node->link[0] = node->link[1] = NULL;
-
- return node;
-}
-
-void
-rope_clear(struct rope *rope)
-{
- struct rope_node *it = rope->root;
- struct rope_node *save;
-
- /* Destruction by rotation */
- while (it != NULL) {
- if (it->link[0] == NULL) {
- /* Remove node */
- save = it->link[1];
- rope->free(rope->alloc_ctx, it);
- } else {
- /* Rotate right */
- save = it->link[0];
- it->link[0] = save->link[1];
- save->link[1] = it;
- }
- it = save;
- }
- rope->root = NULL;
-}
-
-static struct rope_node *
-rope_node_split(struct rope *rope, struct rope_node *node, rope_size_t offset)
-{
- rope_size_t old_size = node->leaf_size;
- node->leaf_size = offset;
-
- void *data = rope->split(rope->split_ctx, node->data, old_size, offset);
- return rope_node_new(rope, data, old_size - offset);
-}
-
-static inline struct rope_node *
-avl_rotate_single(struct rope_node *parent, int direction)
-{
- struct rope_node *save = parent->link[!direction];
-
- parent->link[!direction] = save->link[direction];
- save->link[direction] = parent;
-
- /* First relink the parent, since it's now a child. */
- rope_relink(parent);
- rope_relink(save);
-
- return save;
-}
-
-static inline struct rope_node *
-avl_rotate_double(struct rope_node *parent, int direction)
-{
- parent->link[!direction] =
- avl_rotate_single(parent->link[!direction], !direction);
- return avl_rotate_single(parent, direction);
-}
-
-/** Rebalance the tree. */
-static inline void
-avl_rebalance_after_insert(struct rope_node ***path,
- struct rope_node ***p_end, int insert_height)
-{
- while (p_end > path) {
- struct rope_node *left = **p_end--;
- struct rope_node *parent = **p_end;
- /*
- * To use the same rotation functions, set mirror
- * to 1 if left is right and right is left.
- */
- int mirror = left != parent->link[0];
- struct rope_node *right = parent->link[!mirror];
-
- int left_height = rope_node_height(left);
- int right_height = rope_node_height(right);
- parent->height = MAX(left_height, right_height) + 1;
- /*
- * Rotations flattened the tree, so there is no
- * further changes in height up the insertion
- * path.
- */
- if (left_height == right_height)
- break;
- /*
- * We've been adding a new child (children) to the
- * 'left' subtree, so it couldn't get shorter.
- * The old difference between subtrees was in the
- * range -1..1. So the new difference can only be
- * in the range -1..1 + height(new_node).
- */
- if (left_height - right_height >= 2) {
- struct rope_node *l_left = left->link[mirror];
- struct rope_node *l_right = left->link[!mirror];
- int l_left_height = rope_node_height(l_left);
- int l_right_height = rope_node_height(l_right);
- /*
- * Rotate in the direction, opposite to
- * the skew. E.g. if we have two left-left
- * nodes hanging off the tree, rotate the
- * parent clockwise. If we have a left
- * node with a right child, rotate the
- * child counterclockwise, and then the whole
- * thing clockwise.
- */
- if (l_left_height >= l_right_height)
- **p_end = avl_rotate_single(parent,
- !mirror);
- else
- **p_end = avl_rotate_double(parent,
- !mirror);
- /*
- * If we inserted only one node, no more
- * than 1 rotation is required (see
- * D. Knuth, Introduction to Algorithms,
- * vol. 3.). For 2 nodes, its max
- * 2 rotations.
- */
- if (l_left_height != l_right_height &&
- --insert_height == 0)
- break;
- }
- }
-}
-
-/* This is a copy-cat of the previous loop,
- * with the exception that the heuristic to break
- * the loop is different.
- */
-static inline void
-avl_rebalance_after_delete(struct rope_node ***path,
- struct rope_node ***p_end)
-{
- while (p_end > path) {
- struct rope_node *left = **p_end--;
- struct rope_node *parent = **p_end;
-
- int mirror = left != parent->link[0];
-
- struct rope_node *right = parent->link[!mirror];
-
- int left_height = rope_node_height(left);
- int right_height = rope_node_height(right);
-
- parent->height = MAX(left_height, right_height) + 1;
- /*
- * Right was taller, and we deleted from the left.
- * We can break the loop since there can be no
- * changes in height up in the route.
- */
- if (left_height - right_height == -1)
- break;
-
- if (left_height - right_height <= -2) {
- struct rope_node *r_left = right->link[mirror];
- struct rope_node *r_right = right->link[!mirror];
- int r_left_height = rope_node_height(r_left);
- int r_right_height = rope_node_height(r_right);
-
- if (r_left_height <= r_right_height)
- **p_end = avl_rotate_single(parent, mirror);
- else
- **p_end = avl_rotate_double(parent, mirror);
- }
- }
-}
-
-/**
- * Find a rope node which contains the substring at offset,
- * adjusting tree size with adjust_size and saving the path
- * in path.
- *
- * @return the end of the route.
- */
-static inline struct rope_node ***
-avl_route_to_offset(struct rope_node ***path, rope_size_t *p_offset,
- ssize_t adjust_size)
-{
- rope_size_t offset = *p_offset;
- while (**path) {
- struct rope_node *node = **path;
-
- node->tree_size += adjust_size;
-
- rope_size_t left_size = rope_node_size(node->link[0]);
-
- if (offset < left_size) {
- /* The offset lays in the left subtree. */
- *++path = &node->link[0];
- } else {
- /* Make the new offset relative to the parent. */
- offset -= left_size;
-
- if (offset < node->leaf_size) {
- /* Found. */
- break;
- } else {
- /*
- * Make the offset relative to the
- * leftmost node in the right subtree.
- */
- offset -= node->leaf_size;
- }
- *++path = &node->link[1];
- }
- }
- *p_offset = offset;
- return path;
-}
-
-/**
- * Route to successor or predecessor node of the node
- * in **path. It's either the rightmost leaf of the left child
- * (previous node) or leftmost leaf of the right child.
- */
-static inline struct rope_node ***
-avl_route_to_next(struct rope_node ***path, int dir, rope_ssize_t adjust_size)
-{
- struct rope_node *node = **path;
- *++path = &node->link[dir];
- while (**path) {
- node = **path;
- node->tree_size += adjust_size;
- *++path = &node->link[!dir];
- }
- return path;
-}
-
-/**
- * A new node is always inserted at a leaf position.
- * If insertion unbalances the tree, the rebalancing
- * procedure may put the node into an intermediate position.
- *
- * While traversing the tree, we simultaneously update
- * tree sizes of all intermediate nodes, taking into account
- * the size of the new node.
- *
- * When insertion offset falls at the middle of an existing node,
- * we truncate this node and attach its tail to the left leaf
- * of the new node. This trim operation doesn't decrease the old
- * subtree height, and, while it does change subtree size
- * temporarily, as long as we attach the new node to the right
- * subtree of the truncated node, truncation has no effect on the
- * tree size either.
- *
- * Rebalancing, when it occurs, will correctly update subtree
- * heights and sizes of all modified nodes.
- */
-int
-rope_insert(struct rope *rope, rope_size_t offset, void *data, rope_size_t size)
-{
- if (offset > rope_size(rope))
- offset = rope_size(rope);
-
- assert(size);
-
- struct rope_node *new_node = rope_node_new(rope, data, size);
-
- if (new_node == NULL)
- return -1;
-
- struct rope_node **path[ROPE_HEIGHT_MAX];
- path[0] = &rope->root;
-
- struct rope_node ***p_end = avl_route_to_offset(path, &offset, size);
- if (**p_end != NULL) {
- /*
- * The offset is inside an existing
- * substring in the rope. If offset is 0,
- * then insert the new node at the rightmost leaf
- * of the left child. Otherwise, cut the tail of
- * the substring, make it a prefix of the inserted
- * string, and insert the result at the leftmost
- * leaf of the right child.
- */
- if (offset != 0) {
- struct rope_node *split_node;
- split_node = rope_node_split(rope, **p_end, offset);
- if (split_node == NULL)
- return -1;
- split_node->link[0] = new_node;
- split_node->height++;
- split_node->tree_size += new_node->tree_size;
- new_node = split_node;
- }
- p_end = avl_route_to_next(p_end, offset != 0,
- new_node->tree_size);
- }
- **p_end = new_node;
- avl_rebalance_after_insert(path, p_end, new_node->height);
- return 0;
-}
-
-/** Make sure there is a rope node at the given offset. */
-struct rope_node *
-rope_extract_node(struct rope *rope, rope_size_t offset)
-{
- assert(offset < rope_size(rope));
-
- struct rope_node **path[ROPE_HEIGHT_MAX];
- path[0] = &rope->root;
-
- struct rope_node ***p_end = avl_route_to_offset(path, &offset, 0);
- if (offset == 0)
- return **p_end;
- struct rope_node *new_node = rope_node_split(rope, **p_end, offset);
- if (new_node == NULL)
- return NULL;
- p_end = avl_route_to_next(p_end, 1, new_node->tree_size);
- **p_end = new_node;
- avl_rebalance_after_insert(path, p_end, new_node->height);
- return new_node;
-}
-
-/**
- * Erase a single element from the rope.
- * This is a straightforward implementation for a single-element
- * deletion from a rope. A generic cut from a rope involves
- * 2 tree splits and one merge.
- *
- * When deleting a single element, 3 cases are possible:
- * - offset falls at a node with a single element. In this
- * case we perform a normal AVL tree delete.
- * - offset falls at the end or the beginning of an existing node
- * with leaf_size > 1. In that case we trim the existing node
- * and return.
- * - offset falls inside an existing node. In that case
- * we split the existing node at offset, and insert the tail.
- *
- * The implementation is a copycat of rope_insert(). If you're
- * trying to understand the code, it's recommended to start
- * from rope_insert().
- */
-int
-rope_erase(struct rope *rope, rope_size_t offset)
-{
- assert(offset < rope_size(rope));
-
- struct rope_node **path[ROPE_HEIGHT_MAX];
- path[0] = &rope->root;
-
- struct rope_node ***p_end = avl_route_to_offset(path, &offset, -1);
-
- struct rope_node *node = **p_end;
-
- if (node->leaf_size > 1) {
- /* Check if we can simply trim the node. */
- if (offset == 0) {
- /* Cut the head. */
- node->data = rope->split(rope->split_ctx, node->data,
- node->leaf_size, 1);
- node->leaf_size -= 1;
- return 0;
- }
- rope_size_t size = node->leaf_size;
- /* Cut the tail */
- void *next = rope->split(rope->split_ctx, node->data,
- node->leaf_size, offset);
- node->leaf_size = offset;
- if (offset == size - 1)
- return 0; /* Trimmed the tail, nothing else to do */
- /*
- * Offset falls inside a substring. Erase the
- * first field and insert the tail.
- */
- next = rope->split(rope->split_ctx, next, size - offset, 1);
- struct rope_node *new_node =
- rope_node_new(rope, next, size - offset - 1);
- if (new_node == NULL)
- return -1;
- /* Trim the old node. */
- p_end = avl_route_to_next(p_end, 1, new_node->tree_size);
- **p_end = new_node;
- avl_rebalance_after_insert(path, p_end, new_node->height);
- return 0;
- }
- /* We need to delete the node. */
- assert(offset == 0);
- int direction;
- if (node->link[0] != NULL && node->link[1] != NULL) {
- /*
- * The node has two non-NULL leaves. We can't
- * simply delete the node since in that case we
- * won't know what to do with one of the leaves.
- * Instead of deleting the node, store in it data
- * from the rightmost node in the left subtree, or
- * the leftmost node in the right subtree,
- * (depending on which subtree is taller), and
- * delete this leftmost/rightmost node instead.
- */
- struct rope_node *save = node;
- direction = node->link[1]->height > node->link[0]->height;
- p_end = avl_route_to_next(p_end, direction, 0) - 1;
- node = **p_end;
- /* Move the data pointers. */
- save->data = node->data;
- save->leaf_size = node->leaf_size;
- /*
- * Now follow the path again and update tree_size
- * in the parents of the moved child.
- */
- save = save->link[direction];
- while (save != node) {
- save->tree_size -= node->leaf_size;
- save = save->link[!direction];
- }
- } else {
- /*
- * Left or right subtree are NULL, so we
- * can simply put the non-NULL leaf in place
- * of the parent.
- */
- direction = node->link[0] == NULL;
- }
- **p_end = node->link[direction];
- rope->free(rope, node);
- avl_rebalance_after_delete(path, p_end);
- return 0;
-}
-
-/**
- * Traverse left until the left subtree is NULL,
- * save the path in iter->path.
- * @pre iter->path[iter->top] is not NULL
- * @post iter->path[iter->top] is not NULL and points to the last
- * not-NULL node.
- */
-static inline void
-rope_iter_down_to_leaf(struct rope_iter *it)
-{
- while (it->top[0]->link[0] != NULL) {
- it->top[1] = it->top[0]->link[0];
- it->top++;
- }
-}
-
-struct rope_node *
-rope_iter_start(struct rope_iter *it)
-{
- it->top = it->path;
- it->top[0] = it->rope->root;
-
- if (it->top[0] != NULL)
- rope_iter_down_to_leaf(it);
- return it->top[0];
-}
-
-struct rope_node *
-rope_iter_next(struct rope_iter *it)
-{
- if (it->top[0]->link[1] != NULL) {
- it->top[1] = it->top[0]->link[1];
- it->top++;
- rope_iter_down_to_leaf(it);
- } else {
- /*
- * Right subtree is NULL. Left subtree is fully
- * traversed (guaranteed by the order in which we
- * iterate). Pop up the path until the current
- * node points to a link we haven't visited
- * yet: this is the case when we return to the
- * parent from its left child.
- */
- do {
- /*
- * Returned to the root from the right
- * subtree: the tree is fully traversed.
- */
- if (it->top == it->path) {
- /*
- * Crash, rather than infinite loop
- * if next() is called beyond last.
- */
- it->top[0] = NULL;
- return NULL;
- }
- it->top--;
- } while (it->top[1] == it->top[0]->link[1]);
- }
- return *it->top;
-}
-
-/** Apply visit_leaf function to every rope leaf. */
-void
-rope_traverse(struct rope *rope, void (*visit_leaf)(void *, size_t))
-{
- struct rope_iter iter;
- rope_iter_create(&iter, rope);
-
- struct rope_node *leaf;
-
- for (leaf = rope_iter_start(&iter);
- leaf != NULL;
- leaf = rope_iter_next(&iter)) {
-
- visit_leaf(leaf->data, leaf->leaf_size);
- }
-}
-
-void
-rope_check(struct rope *rope)
-{
- struct rope_iter iter;
- rope_iter_create(&iter, rope);
-
- struct rope_node *node;
-
- for (node = rope_iter_start(&iter);
- node != NULL;
- node = rope_iter_next(&iter)) {
-
- assert(node->leaf_size != 0);
-
- assert(node->tree_size == rope_node_size(node->link[0])
- + rope_node_size(node->link[1]) + node->leaf_size);
-
- assert(node->height == (MAX(rope_node_height(node->link[0]),
- rope_node_height(node->link[1])) + 1));
- if (node->leaf_size == 0 ||
- node->tree_size != (rope_node_size(node->link[0]) +
- rope_node_size(node->link[1]) +
- node->leaf_size) ||
- node->height != MAX(rope_node_height(node->link[0]),
- rope_node_height(node->link[1])) + 1)
- abort();
- }
-}
-
-static void
-rope_node_print(struct rope_node *node,
- void (*print)(void *, size_t),
- const char *prefix, int dir)
-{
- const char *conn[] = { "┌──", "└──" };
-
- const char *padding[] = { "│ ", " " };
-
- rope_size_t child_prefix_len = strlen(prefix) + strlen(padding[0]) + 1;
- char *child_prefix = malloc(child_prefix_len);
-
- if (node && (node->link[0] || node->link[1])) {
- snprintf(child_prefix, child_prefix_len - 1,
- "%s%s", prefix, padding[!dir]);
- rope_node_print(node->link[0], print, child_prefix, 0);
- }
-
- snprintf(child_prefix, child_prefix_len - 1, "%s%s",
- prefix, padding[dir]);
- printf("%s%s", prefix, conn[dir]);
-
- if (node == NULL) {
- printf("nil\n");
- } else {
-
- printf("{ len = %zu, height = %d, data = '",
- (size_t) node->leaf_size, node->height);
- print(node->data, node->leaf_size);
- printf("'}\n");
-
- if (node->link[0] || node->link[1])
- rope_node_print(node->link[1], print, child_prefix, 1);
- }
-
- free(child_prefix);
-}
-
-void
-rope_pretty_print(struct rope *rope, void (*print_leaf)(void *, size_t))
-{
- printf("size = %zu\nstring = '", (size_t) rope_size(rope));
- rope_traverse(rope, print_leaf);
- printf("'\n");
- rope_node_print(rope->root, print_leaf, "", true);
- printf("\n");
-}
diff --git a/src/lib/salad/rope.h b/src/lib/salad/rope.h
index 719fd9c99..3e4585809 100644
--- a/src/lib/salad/rope.h
+++ b/src/lib/salad/rope.h
@@ -1,5 +1,3 @@
-#ifndef INCLUDES_TARANTOOL_ROPE_H
-#define INCLUDES_TARANTOOL_ROPE_H
/*
* Copyright 2010-2016, Tarantool AUTHORS, please see AUTHORS file.
*
@@ -30,19 +28,52 @@
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
-#include <stdint.h>
-#include <stddef.h>
+/*
+ * Copyright (c) 1993-1994 by Xerox Corporation. All rights reserved.
+ *
+ * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
+ * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
+ *
+ * Permission is hereby granted to use or copy this program
+ * for any purpose, provided the above notices are retained on all copies.
+ * Permission to modify the code and to distribute modified code is granted,
+ * provided the above notices are retained, and a notice that the code was
+ * modified is included with the above copyright notice.
+ *
+ * Author: Hans-J. Boehm (boehm@parc.xerox.com)
+ */
+/*
+ * This is a rope implementation which uses AVL tree
+ * balancing algorithm for rope tree balance.
+ */
#include <stdbool.h>
-
-#if defined(__cplusplus)
-extern "C" {
-#endif /* defined(__cplusplus) */
+#include <stdio.h>
+#include <assert.h>
typedef uint32_t rope_size_t;
typedef int32_t rope_ssize_t;
-typedef void *(*rope_split_func)(void *, void *, size_t, size_t);
-typedef void *(*rope_alloc_func)(void *, size_t);
-typedef void (*rope_free_func)(void *, void *);
+
+/**
+ * Needed definitions:
+ *
+ * rope_data_t
+ * rope_split_ctx_t
+ * rope_alloc_ctx_t
+ * ROPE_SPLIT_F
+ * ROPE_ALLOC_F
+ * ROPE_FREE_F [optional]
+ */
+
+#ifndef ROPE_FREE_F
+#define ROPE_FREE_F_IMPL(a, b) do { (void) a; (void) b; } while(0)
+#else
+#define ROPE_FREE_F_IMPL(a, b) ROPE_FREE_F(a, b)
+#endif /* ROPE_FREE_F */
+
+#ifndef MAX
+#define NEED_UNDEF_MAX
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif /* MAX */
/** Tallest allowable tree, 1.44*log(2^32) */
enum { ROPE_HEIGHT_MAX = 46 };
@@ -55,7 +86,7 @@ struct rope_node {
/* Substring size. */
rope_size_t leaf_size;
/* Substring. */
- void *data;
+ rope_data_t data;
/* Left (0) and right (1) links */
struct rope_node *link[2];
};
@@ -64,15 +95,10 @@ struct rope {
/** Top of the tree */
struct rope_node *root;
/** Memory management context. */
- void *alloc_ctx;
+ rope_alloc_ctx_t alloc_ctx;
/** Get a sequence tail, given offset. */
- rope_split_func split;
/** Split function context. */
- void *split_ctx;
- /** Allocate memory (context, size). */
- void *(*alloc)(void *, size_t);
- /** Free memory (context, pointer) */
- void (*free)(void *, void *);
+ rope_split_ctx_t split_ctx;
};
struct rope_iter {
@@ -84,6 +110,34 @@ struct rope_iter {
struct rope_node *path[ROPE_HEIGHT_MAX];
};
+/** Public API {{{ */
+
+/** Initialize an empty rope. */
+static inline void
+rope_create(struct rope *rope, rope_split_ctx_t split_ctx,
+ rope_alloc_ctx_t alloc_ctx)
+{
+ rope->root = NULL;
+ rope->split_ctx = split_ctx;
+ rope->alloc_ctx = alloc_ctx;
+}
+
+/** Create a new empty rope.
+ * @param alloc_ctx allocator context
+ *
+ * @return an empty rope, or NULL if failed
+ * to allocate memory
+ */
+static inline struct rope *
+rope_new(rope_split_ctx_t split_ctx, rope_alloc_ctx_t alloc_ctx)
+{
+ struct rope *rope=
+ (struct rope *) ROPE_ALLOC_F(alloc_ctx, sizeof(*rope));
+ if (rope != NULL)
+ rope_create(rope, split_ctx, alloc_ctx);
+ return rope;
+}
+
static inline rope_size_t
rope_node_size(struct rope_node *node)
{
@@ -96,7 +150,7 @@ rope_leaf_size(struct rope_node *node)
return node->leaf_size;
}
-static inline void *
+static inline rope_data_t
rope_leaf_data(struct rope_node *node)
{
return node->data;
@@ -108,63 +162,47 @@ rope_size(struct rope *rope)
return rope_node_size(rope->root);
}
-/** Initialize an empty rope. */
-static inline void
-rope_create(struct rope *rope, rope_split_func split_func, void *split_ctx,
- rope_alloc_func alloc_func, rope_free_func free_func,
- void *alloc_ctx)
-{
- rope->root = NULL;
- rope->split = split_func;
- rope->split_ctx = split_ctx;
- rope->alloc = alloc_func;
- rope->free = free_func;
- rope->alloc_ctx = alloc_ctx;
-}
-
-/** Create a new empty rope.
- * @param split_func a function which returns
- * a pointer to substring
- * given an offset. Used
- * to split substrings when
- * inserting into a rope.
- * @param alloc_func used to allocate memory
- * @param free_func used to free memory
- * @param alloc_ctx allocator context
- *
- * @return an empty rope, or NULL if failed
- * to allocate memory
- */
-static inline struct rope *
-rope_new(rope_split_func split_func, void *split_ctx,
- rope_alloc_func alloc_func, rope_free_func free_func, void *alloc_ctx)
-{
- struct rope *rope= (struct rope *) alloc_func(alloc_ctx,
- sizeof(struct rope));
- if (rope == NULL)
- return NULL;
- rope_create(rope, split_func, split_ctx, alloc_func, free_func, alloc_ctx);
- return rope;
-}
-
-
-/** Delete rope contents. Can also be used
+/**
+ * Delete rope contents. Can also be used
* to free a rope which is allocated on stack.
* Doesn't delete rope substrings, only
* rope nodes.
*/
void
-rope_clear(struct rope *rope);
+rope_clear(struct rope *rope)
+{
+#ifdef ROPE_FREE_F
+ struct rope_node *it = rope->root;
+ struct rope_node *save;
+
+ /* Destruction by rotation */
+ while (it != NULL) {
+ if (it->link[0] == NULL) {
+ /* Remove node */
+ save = it->link[1];
+ ROPE_FREE_F(rope->alloc_ctx, it);
+ } else {
+ /* Rotate right */
+ save = it->link[0];
+ it->link[0] = save->link[1];
+ save->link[1] = it;
+ }
+ it = save;
+ }
+#endif /* ROPE_FREE_F */
+ rope->root = NULL;
+}
/** Delete a rope allocated with rope_new() */
static inline void
rope_delete(struct rope *rope)
{
rope_clear(rope);
- rope->free(rope->alloc_ctx, rope);
+ ROPE_FREE_F_IMPL(rope->alloc_ctx, rope);
}
-/** Insert a substring into a rope at the given
+/**
+ * Insert a substring into a rope at the given
* offset.
* If offset is greater than rope size, insertion
* happens at the end.
@@ -174,17 +212,18 @@ rope_delete(struct rope *rope)
* tree node
*/
int
-rope_insert(struct rope *rope, rope_size_t offset, void *data,
+rope_insert(struct rope *rope, rope_size_t offset, rope_data_t data,
rope_size_t size);
/** Append a substring at rope tail. */
static inline int
-rope_append(struct rope *rope, void *data, size_t size)
+rope_append(struct rope *rope, rope_data_t data, size_t size)
{
return rope_insert(rope, rope_size(rope), data, size);
}
-/** Make sure there is a rope node which has a substring
+/**
+ * Make sure there is a rope node which has a substring
* which starts at the given offset. Useful when
* rope substrings carry additional information.
*
@@ -194,7 +233,7 @@ rope_append(struct rope *rope, void *data, size_t size)
struct rope_node *
rope_extract_node(struct rope *rope, rope_size_t offset);
-static inline void *
+static inline rope_data_t
rope_extract(struct rope *rope, rope_size_t offset)
{
return rope_leaf_data(rope_extract_node(rope, offset));
@@ -220,12 +259,10 @@ rope_iter_create(struct rope_iter *it, struct rope *rope)
static inline struct rope_iter *
rope_iter_new(struct rope *rope)
{
- struct rope_iter *it = (struct rope_iter *)
- rope->alloc(rope->alloc_ctx, sizeof(struct rope_iter));
-
- if (it == NULL)
- return NULL;
- rope_iter_create(it, rope);
+ struct rope_iter *it =
+ (struct rope_iter *) ROPE_ALLOC_F(rope->alloc_ctx, sizeof(*it));
+ if (it != NULL)
+ rope_iter_create(it, rope);
return it;
}
@@ -250,12 +287,12 @@ rope_iter_next(struct rope_iter *it);
static inline void
rope_iter_delete(struct rope_iter *it)
{
- it->rope->free(it->rope->alloc_ctx, it);
+ ROPE_FREE_F_IMPL(it->rope->alloc_ctx, it);
}
/** Apply visit_leaf function to every rope leaf. */
void
-rope_traverse(struct rope *rope, void (*visit_leaf)(void *, size_t));
+rope_traverse(struct rope *rope, void (*visit_leaf)(rope_data_t, size_t));
/** Check AVL tree consistency. */
void
@@ -263,10 +300,603 @@ rope_check(struct rope *rope);
/** Pretty print a rope. */
void
-rope_pretty_print(struct rope *rope, void (*print_leaf)(void *, size_t));
+rope_pretty_print(struct rope *rope, void (*print_leaf)(rope_data_t, size_t));
+
+/** }}} */
+
+static inline int
+rope_node_height(struct rope_node *node)
+{
+ return node ? node->height : 0;
+}
+
+static inline void
+rope_relink(struct rope_node *node)
+{
+ node->tree_size = (rope_node_size(node->link[0]) +
+ rope_node_size(node->link[1]) +
+ node->leaf_size);
+ node->height = MAX(rope_node_height(node->link[0]),
+ rope_node_height(node->link[1])) + 1;
+}
+
+static inline struct rope_node *
+rope_node_new(struct rope *rope, rope_data_t data, rope_size_t size)
+{
+ struct rope_node *node =
+ (struct rope_node *) ROPE_ALLOC_F(rope->alloc_ctx,
+ sizeof(*node));
+ if (node == NULL)
+ return NULL;
+ node->height = 1;
+ node->tree_size = node->leaf_size = size;
+ node->data = data;
+ node->link[0] = node->link[1] = NULL;
+
+ return node;
+}
+
+static struct rope_node *
+rope_node_split(struct rope *rope, struct rope_node *node, rope_size_t offset)
+{
+ rope_size_t old_size = node->leaf_size;
+ node->leaf_size = offset;
+
+ rope_data_t data = ROPE_SPLIT_F(rope->split_ctx, node->data, old_size,
+ offset);
+
+ return rope_node_new(rope, data, old_size - offset);
+}
+
+static inline struct rope_node *
+avl_rotate_single(struct rope_node *parent, int direction)
+{
+ struct rope_node *save = parent->link[!direction];
+
+ parent->link[!direction] = save->link[direction];
+ save->link[direction] = parent;
+
+ /* First relink the parent, since it's now a child. */
+ rope_relink(parent);
+ rope_relink(save);
+
+ return save;
+}
+
+static inline struct rope_node *
+avl_rotate_double(struct rope_node *parent, int direction)
+{
+ parent->link[!direction] =
+ avl_rotate_single(parent->link[!direction], !direction);
+ return avl_rotate_single(parent, direction);
+}
+
+/** Rebalance the tree. */
+static inline void
+avl_rebalance_after_insert(struct rope_node ***path,
+ struct rope_node ***p_end, int insert_height)
+{
+ while (p_end > path) {
+ struct rope_node *left = **p_end--;
+ struct rope_node *parent = **p_end;
+ /*
+ * To use the same rotation functions, set mirror
+ * to 1 if left is right and right is left.
+ */
+ int mirror = left != parent->link[0];
+ struct rope_node *right = parent->link[!mirror];
+
+ int left_height = rope_node_height(left);
+ int right_height = rope_node_height(right);
+ parent->height = MAX(left_height, right_height) + 1;
+ /*
+ * Rotations flattened the tree, so there is no
+ * further changes in height up the insertion
+ * path.
+ */
+ if (left_height == right_height)
+ break;
+ /*
+ * We've been adding a new child (children) to the
+ * 'left' subtree, so it couldn't get shorter.
+ * The old difference between subtrees was in the
+ * range -1..1. So the new difference can only be
+ * in the range -1..1 + height(new_node).
+ */
+ if (left_height - right_height >= 2) {
+ struct rope_node *l_left = left->link[mirror];
+ struct rope_node *l_right = left->link[!mirror];
+ int l_left_height = rope_node_height(l_left);
+ int l_right_height = rope_node_height(l_right);
+ /*
+ * Rotate in the direction, opposite to
+ * the skew. E.g. if we have two left-left
+ * nodes hanging off the tree, rotate the
+ * parent clockwise. If we have a left
+ * node with a right child, rotate the
+ * child counterclockwise, and then the whole
+ * thing clockwise.
+ */
+ if (l_left_height >= l_right_height)
+ **p_end = avl_rotate_single(parent,
+ !mirror);
+ else
+ **p_end = avl_rotate_double(parent,
+ !mirror);
+ /*
+ * If we inserted only one node, no more
+ * than 1 rotation is required (see
+ * D. Knuth, Introduction to Algorithms,
+ * vol. 3.). For 2 nodes, its max
+ * 2 rotations.
+ */
+ if (l_left_height != l_right_height &&
+ --insert_height == 0)
+ break;
+ }
+ }
+}
+
+/* This is a copy-cat of the previous loop,
+ * with the exception that the heuristic to break
+ * the loop is different.
+ */
+static inline void
+avl_rebalance_after_delete(struct rope_node ***path,
+ struct rope_node ***p_end)
+{
+ while (p_end > path) {
+ struct rope_node *left = **p_end--;
+ struct rope_node *parent = **p_end;
+
+ int mirror = left != parent->link[0];
+
+ struct rope_node *right = parent->link[!mirror];
+
+ int left_height = rope_node_height(left);
+ int right_height = rope_node_height(right);
+
+ parent->height = MAX(left_height, right_height) + 1;
+ /*
+ * Right was taller, and we deleted from the left.
+ * We can break the loop since there can be no
+ * changes in height up in the route.
+ */
+ if (left_height - right_height == -1)
+ break;
+
+ if (left_height - right_height <= -2) {
+ struct rope_node *r_left = right->link[mirror];
+ struct rope_node *r_right = right->link[!mirror];
+ int r_left_height = rope_node_height(r_left);
+ int r_right_height = rope_node_height(r_right);
+
+ if (r_left_height <= r_right_height)
+ **p_end = avl_rotate_single(parent, mirror);
+ else
+ **p_end = avl_rotate_double(parent, mirror);
+ }
+ }
+}
+
+/**
+ * Find a rope node which contains the substring at offset,
+ * adjusting tree size with adjust_size and saving the path
+ * in path.
+ *
+ * @return the end of the route.
+ */
+static inline struct rope_node ***
+avl_route_to_offset(struct rope_node ***path, rope_size_t *p_offset,
+ ssize_t adjust_size)
+{
+ rope_size_t offset = *p_offset;
+ while (**path) {
+ struct rope_node *node = **path;
+
+ node->tree_size += adjust_size;
+
+ rope_size_t left_size = rope_node_size(node->link[0]);
+
+ if (offset < left_size) {
+ /* The offset lays in the left subtree. */
+ *++path = &node->link[0];
+ } else {
+ /* Make the new offset relative to the parent. */
+ offset -= left_size;
+
+ if (offset < node->leaf_size) {
+ /* Found. */
+ break;
+ } else {
+ /*
+ * Make the offset relative to the
+ * leftmost node in the right subtree.
+ */
+ offset -= node->leaf_size;
+ }
+ *++path = &node->link[1];
+ }
+ }
+ *p_offset = offset;
+ return path;
+}
+
+/**
+ * Route to successor or predecessor node of the node
+ * in **path. It's either the rightmost leaf of the left child
+ * (previous node) or leftmost leaf of the right child.
+ */
+static inline struct rope_node ***
+avl_route_to_next(struct rope_node ***path, int dir, rope_ssize_t adjust_size)
+{
+ struct rope_node *node = **path;
+ *++path = &node->link[dir];
+ while (**path) {
+ node = **path;
+ node->tree_size += adjust_size;
+ *++path = &node->link[!dir];
+ }
+ return path;
+}
+
+/**
+ * A new node is always inserted at a leaf position.
+ * If insertion unbalances the tree, the rebalancing
+ * procedure may put the node into an intermediate position.
+ *
+ * While traversing the tree, we simultaneously update
+ * tree sizes of all intermediate nodes, taking into account
+ * the size of the new node.
+ *
+ * When insertion offset falls at the middle of an existing node,
+ * we truncate this node and attach its tail to the left leaf
+ * of the new node. This trim operation doesn't decrease the old
+ * subtree height, and, while it does change subtree size
+ * temporarily, as long as we attach the new node to the right
+ * subtree of the truncated node, truncation has no effect on the
+ * tree size either.
+ *
+ * Rebalancing, when it occurs, will correctly update subtree
+ * heights and sizes of all modified nodes.
+ */
+int
+rope_insert(struct rope *rope, rope_size_t offset, rope_data_t data,
+ rope_size_t size)
+{
+ if (offset > rope_size(rope))
+ offset = rope_size(rope);
+
+ assert(size);
+
+ struct rope_node *new_node = rope_node_new(rope, data, size);
+
+ if (new_node == NULL)
+ return -1;
+
+ struct rope_node **path[ROPE_HEIGHT_MAX];
+ path[0] = &rope->root;
+
+ struct rope_node ***p_end = avl_route_to_offset(path, &offset, size);
+ if (**p_end != NULL) {
+ /*
+ * The offset is inside an existing
+ * substring in the rope. If offset is 0,
+ * then insert the new node at the rightmost leaf
+ * of the left child. Otherwise, cut the tail of
+ * the substring, make it a prefix of the inserted
+ * string, and insert the result at the leftmost
+ * leaf of the right child.
+ */
+ if (offset != 0) {
+ struct rope_node *split_node;
+ split_node = rope_node_split(rope, **p_end, offset);
+ if (split_node == NULL)
+ return -1;
+ split_node->link[0] = new_node;
+ split_node->height++;
+ split_node->tree_size += new_node->tree_size;
+ new_node = split_node;
+ }
+ p_end = avl_route_to_next(p_end, offset != 0,
+ new_node->tree_size);
+ }
+ **p_end = new_node;
+ avl_rebalance_after_insert(path, p_end, new_node->height);
+ return 0;
+}
+
+/** Make sure there is a rope node at the given offset. */
+struct rope_node *
+rope_extract_node(struct rope *rope, rope_size_t offset)
+{
+ assert(offset < rope_size(rope));
+
+ struct rope_node **path[ROPE_HEIGHT_MAX];
+ path[0] = &rope->root;
+
+ struct rope_node ***p_end = avl_route_to_offset(path, &offset, 0);
+ if (offset == 0)
+ return **p_end;
+ struct rope_node *new_node = rope_node_split(rope, **p_end, offset);
+ if (new_node == NULL)
+ return NULL;
+ p_end = avl_route_to_next(p_end, 1, new_node->tree_size);
+ **p_end = new_node;
+ avl_rebalance_after_insert(path, p_end, new_node->height);
+ return new_node;
+}
+
+/**
+ * Erase a single element from the rope.
+ * This is a straightforward implementation for a single-element
+ * deletion from a rope. A generic cut from a rope involves
+ * 2 tree splits and one merge.
+ *
+ * When deleting a single element, 3 cases are possible:
+ * - offset falls at a node with a single element. In this
+ * case we perform a normal AVL tree delete.
+ * - offset falls at the end or the beginning of an existing node
+ * with leaf_size > 1. In that case we trim the existing node
+ * and return.
+ * - offset falls inside an existing node. In that case
+ * we split the existing node at offset, and insert the tail.
+ *
+ * The implementation is a copycat of rope_insert(). If you're
+ * trying to understand the code, it's recommended to start
+ * from rope_insert().
+ */
+int
+rope_erase(struct rope *rope, rope_size_t offset)
+{
+ assert(offset < rope_size(rope));
+
+ struct rope_node **path[ROPE_HEIGHT_MAX];
+ path[0] = &rope->root;
+
+ struct rope_node ***p_end = avl_route_to_offset(path, &offset, -1);
+
+ struct rope_node *node = **p_end;
+
+ if (node->leaf_size > 1) {
+ /* Check if we can simply trim the node. */
+ if (offset == 0) {
+ /* Cut the head. */
+ node->data = ROPE_SPLIT_F(rope->split_ctx, node->data,
+ node->leaf_size, 1);
+ node->leaf_size -= 1;
+ return 0;
+ }
+ rope_size_t size = node->leaf_size;
+ /* Cut the tail */
+ rope_data_t next = ROPE_SPLIT_F(rope->split_ctx, node->data,
+ node->leaf_size, offset);
+ node->leaf_size = offset;
+ if (offset == size - 1)
+ return 0; /* Trimmed the tail, nothing else to do */
+ /*
+ * Offset falls inside a substring. Erase the
+ * first field and insert the tail.
+ */
+ next = ROPE_SPLIT_F(rope->split_ctx, next, size - offset, 1);
+ struct rope_node *new_node =
+ rope_node_new(rope, next, size - offset - 1);
+ if (new_node == NULL)
+ return -1;
+ /* Trim the old node. */
+ p_end = avl_route_to_next(p_end, 1, new_node->tree_size);
+ **p_end = new_node;
+ avl_rebalance_after_insert(path, p_end, new_node->height);
+ return 0;
+ }
+ /* We need to delete the node. */
+ assert(offset == 0);
+ int direction;
+ if (node->link[0] != NULL && node->link[1] != NULL) {
+ /*
+ * The node has two non-NULL leaves. We can't
+ * simply delete the node since in that case we
+ * won't know what to do with one of the leaves.
+ * Instead of deleting the node, store in it data
+ * from the rightmost node in the left subtree, or
+ * the leftmost node in the right subtree,
+ * (depending on which subtree is taller), and
+ * delete this leftmost/rightmost node instead.
+ */
+ struct rope_node *save = node;
+ direction = node->link[1]->height > node->link[0]->height;
+ p_end = avl_route_to_next(p_end, direction, 0) - 1;
+ node = **p_end;
+ /* Move the data pointers. */
+ save->data = node->data;
+ save->leaf_size = node->leaf_size;
+ /*
+ * Now follow the path again and update tree_size
+ * in the parents of the moved child.
+ */
+ save = save->link[direction];
+ while (save != node) {
+ save->tree_size -= node->leaf_size;
+ save = save->link[!direction];
+ }
+ } else {
+ /*
+ * Left or right subtree are NULL, so we
+ * can simply put the non-NULL leaf in place
+ * of the parent.
+ */
+ direction = node->link[0] == NULL;
+ }
+ **p_end = node->link[direction];
+ ROPE_FREE_F_IMPL(rope->alloc_ctx, node);
+ avl_rebalance_after_delete(path, p_end);
+ return 0;
+}
+
+/**
+ * Traverse left until the left subtree is NULL,
+ * save the path in iter->path.
+ * @pre iter->path[iter->top] is not NULL
+ * @post iter->path[iter->top] is not NULL and points to the last
+ * not-NULL node.
+ */
+static inline void
+rope_iter_down_to_leaf(struct rope_iter *it)
+{
+ while (it->top[0]->link[0] != NULL) {
+ it->top[1] = it->top[0]->link[0];
+ it->top++;
+ }
+}
+
+struct rope_node *
+rope_iter_start(struct rope_iter *it)
+{
+ it->top = it->path;
+ it->top[0] = it->rope->root;
+
+ if (it->top[0] != NULL)
+ rope_iter_down_to_leaf(it);
+ return it->top[0];
+}
+
+struct rope_node *
+rope_iter_next(struct rope_iter *it)
+{
+ if (it->top[0]->link[1] != NULL) {
+ it->top[1] = it->top[0]->link[1];
+ it->top++;
+ rope_iter_down_to_leaf(it);
+ } else {
+ /*
+ * Right subtree is NULL. Left subtree is fully
+ * traversed (guaranteed by the order in which we
+ * iterate). Pop up the path until the current
+ * node points to a link we haven't visited
+ * yet: this is the case when we return to the
+ * parent from its left child.
+ */
+ do {
+ /*
+ * Returned to the root from the right
+ * subtree: the tree is fully traversed.
+ */
+ if (it->top == it->path) {
+ /*
+ * Crash, rather than infinite loop
+ * if next() is called beyond last.
+ */
+ it->top[0] = NULL;
+ return NULL;
+ }
+ it->top--;
+ } while (it->top[1] == it->top[0]->link[1]);
+ }
+ return *it->top;
+}
+
+/** Apply visit_leaf function to every rope leaf. */
+void
+rope_traverse(struct rope *rope, void (*visit_leaf)(rope_data_t, size_t))
+{
+ struct rope_iter iter;
+ rope_iter_create(&iter, rope);
+
+ struct rope_node *leaf;
+
+ for (leaf = rope_iter_start(&iter);
+ leaf != NULL;
+ leaf = rope_iter_next(&iter)) {
+
+ visit_leaf(leaf->data, leaf->leaf_size);
+ }
+}
+
+void
+rope_check(struct rope *rope)
+{
+ struct rope_iter iter;
+ rope_iter_create(&iter, rope);
+
+ struct rope_node *node;
+
+ for (node = rope_iter_start(&iter);
+ node != NULL;
+ node = rope_iter_next(&iter)) {
-#if defined(__cplusplus)
+ assert(node->leaf_size != 0);
+
+ assert(node->tree_size == rope_node_size(node->link[0])
+ + rope_node_size(node->link[1]) + node->leaf_size);
+
+ assert(node->height == (MAX(rope_node_height(node->link[0]),
+ rope_node_height(node->link[1])) + 1));
+ if (node->leaf_size == 0 ||
+ node->tree_size != (rope_node_size(node->link[0]) +
+ rope_node_size(node->link[1]) +
+ node->leaf_size) ||
+ node->height != MAX(rope_node_height(node->link[0]),
+ rope_node_height(node->link[1])) + 1)
+ abort();
+ }
}
-#endif /* defined(__cplusplus) */
-#endif /* INCLUDES_TARANTOOL_ROPE_H */
+static void
+rope_node_print(struct rope_node *node, void (*print)(rope_data_t, size_t),
+ const char *prefix, int dir)
+{
+ const char *conn[] = { "┌──", "└──" };
+
+ const char *padding[] = { "│ ", " " };
+
+ rope_size_t child_prefix_len = strlen(prefix) + strlen(padding[0]) + 1;
+ char *child_prefix = malloc(child_prefix_len);
+
+ if (node && (node->link[0] || node->link[1])) {
+ snprintf(child_prefix, child_prefix_len - 1,
+ "%s%s", prefix, padding[!dir]);
+ rope_node_print(node->link[0], print, child_prefix, 0);
+ }
+
+ snprintf(child_prefix, child_prefix_len - 1, "%s%s",
+ prefix, padding[dir]);
+ printf("%s%s", prefix, conn[dir]);
+
+ if (node == NULL) {
+ printf("nil\n");
+ } else {
+
+ printf("{ len = %zu, height = %d, data = '",
+ (size_t) node->leaf_size, node->height);
+ print(node->data, node->leaf_size);
+ printf("'}\n");
+
+ if (node->link[0] || node->link[1])
+ rope_node_print(node->link[1], print, child_prefix, 1);
+ }
+
+ free(child_prefix);
+}
+
+void
+rope_pretty_print(struct rope *rope, void (*print_leaf)(rope_data_t, size_t))
+{
+ printf("size = %zu\nstring = '", (size_t) rope_size(rope));
+ rope_traverse(rope, print_leaf);
+ printf("'\n");
+ rope_node_print(rope->root, print_leaf, "", true);
+ printf("\n");
+}
+
+#undef rope_alloc_ctx_t
+#undef rope_split_ctx_t
+#undef rope_data_t
+#undef ROPE_SPLIT_F
+#undef ROPE_ALLOC_F
+#undef ROPE_FREE_F
+#undef ROPE_FREE_F_IMPL
+
+#ifdef NEED_UNDEF_MAX
+#undef NEED_UNDEF_MAX
+#undef MAX
+#endif
diff --git a/test/unit/rope.c b/test/unit/rope.c
index e19dcaa10..5ab4c51f7 100644
--- a/test/unit/rope.c
+++ b/test/unit/rope.c
@@ -1,4 +1,3 @@
-#include "salad/rope.h"
#include "unit.h"
#include "rope_common.h"
diff --git a/test/unit/rope_avl.c b/test/unit/rope_avl.c
index 9e978f46c..b095fb1a6 100644
--- a/test/unit/rope_avl.c
+++ b/test/unit/rope_avl.c
@@ -1,4 +1,3 @@
-#include "salad/rope.h"
#include "unit.h"
#include "rope_common.h"
diff --git a/test/unit/rope_basic.c b/test/unit/rope_basic.c
index b4a0e7876..d9e116292 100644
--- a/test/unit/rope_basic.c
+++ b/test/unit/rope_basic.c
@@ -1,4 +1,3 @@
-#include "salad/rope.h"
#include "unit.h"
#include "rope_common.h"
diff --git a/test/unit/rope_common.h b/test/unit/rope_common.h
index 6c52153b2..3b3823ced 100644
--- a/test/unit/rope_common.h
+++ b/test/unit/rope_common.h
@@ -3,17 +3,17 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-static inline void *
-str_getn(void *ctx, void *data, size_t size, size_t offset)
+static inline char *
+str_getn(void *ctx, char *data, size_t size, size_t offset)
{
(void) ctx;
- return (char *) data + offset;
+ return data + offset;
}
static inline void
-str_print(void *data, size_t n)
+str_print(char *data, size_t n)
{
- printf("%.*s", (int) n, (char *) data);
+ printf("%.*s", (int) n, data);
}
static inline void *
@@ -30,10 +30,19 @@ mem_free(void *data, void *ptr)
free(ptr);
}
+#define ROPE_ALLOC_F mem_alloc
+#define ROPE_FREE_F mem_free
+#define ROPE_SPLIT_F str_getn
+#define rope_data_t char *
+#define rope_alloc_ctx_t void *
+#define rope_split_ctx_t void *
+
+#include "salad/rope.h"
+
static inline struct rope *
test_rope_new()
{
- return rope_new(str_getn, NULL, mem_alloc, mem_free, NULL);
+ return rope_new(NULL, NULL);
}
static inline void
diff --git a/test/unit/rope_stress.c b/test/unit/rope_stress.c
index dc3f2817e..bc312d7c0 100644
--- a/test/unit/rope_stress.c
+++ b/test/unit/rope_stress.c
@@ -1,4 +1,3 @@
-#include "salad/rope.h"
#include <time.h>
#include "unit.h"
#include "rope_common.h"
@@ -10,7 +9,7 @@ test_rope_stress_small()
{
header();
- struct rope *rope = rope_new(str_getn, NULL, mem_alloc, mem_free, NULL);
+ struct rope *rope = test_rope_new();
const int iterations = 500;
int i = 0;
for (i = 0; i < iterations; ++i) {
@@ -39,7 +38,7 @@ test_rope_stress_large()
{
header();
- struct rope *rope = rope_new(str_getn, NULL, mem_alloc, mem_free, NULL);
+ struct rope *rope = test_rope_new();
const int iterations = 50000;
int i = 0;
for (i = 0; i < iterations; ++i) {
--
2.15.1 (Apple Git-101)
^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: [PATCH 1/3] vinyl: remove vy_apply_upsert_ops
2018-04-27 22:36 ` [tarantool-patches] [PATCH 1/3] vinyl: remove vy_apply_upsert_ops Vladislav Shpilevoy
@ 2018-05-08 20:18 ` Konstantin Osipov
0 siblings, 0 replies; 7+ messages in thread
From: Konstantin Osipov @ 2018-05-08 20:18 UTC (permalink / raw)
To: Vladislav Shpilevoy; +Cc: tarantool-patches
* Vladislav Shpilevoy <v.shpilevoy@tarantool.org> [18/04/28 08:50]:
> Function vy_apply_upsert_opts originaly appears in this commit:
> 5627e53bf020bf83b8f74d3d04b93047bdf221b4, where it is a
> refactored version of a sophia upsertion. But when a vy_stmt was
> introduced, vinyl_apply_upsert_ops works just like ordinary
> tuple_upsert_execute. Remove this useless wrapper.
> ---
> src/box/vy_upsert.c | 43 +++++--------------------------------------
> 1 file changed, 5 insertions(+), 38 deletions(-)
Pushed.
--
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov
^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: [PATCH 2/3] tuple_update: remove alloc and alloc_ctx args
2018-04-27 22:36 ` [tarantool-patches] [PATCH 2/3] tuple_update: remove alloc and alloc_ctx args Vladislav Shpilevoy
@ 2018-05-08 20:18 ` Konstantin Osipov
0 siblings, 0 replies; 7+ messages in thread
From: Konstantin Osipov @ 2018-05-08 20:18 UTC (permalink / raw)
To: Vladislav Shpilevoy; +Cc: tarantool-patches
* Vladislav Shpilevoy <v.shpilevoy@tarantool.org> [18/04/28 08:50]:
> They are always region_aligned_alloc and region pointer. Lets use
> them always inside tuple_update.c, with no necessity to pass via
> args.
I think it's bikeshed, but I can push it if you like.
--
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov
^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: [PATCH 3/3] rope: make rope library be C template using macros
2018-04-27 22:36 ` [tarantool-patches] [PATCH 3/3] rope: make rope library be C template using macros Vladislav Shpilevoy
@ 2018-05-08 20:22 ` Konstantin Osipov
0 siblings, 0 replies; 7+ messages in thread
From: Konstantin Osipov @ 2018-05-08 20:22 UTC (permalink / raw)
To: Vladislav Shpilevoy; +Cc: tarantool-patches
* Vladislav Shpilevoy <v.shpilevoy@tarantool.org> [18/04/28 08:50]:
> Rope library stores alloc, split and free functions by pointer,
> that is a huge slog at performance. There is no reason, why the
> rope library can not become a template.
I believe you still should keep some "slowpath" code in rope.c -
smaller binary footprint is usually faster.
I'm not sure though it'd be easy to extract.
In any case I'd wait with pushing this patch until we see the
rest.
--
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2018-05-08 20:22 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-04-27 22:36 [tarantool-patches] [PATCH 0/3] Optimize and simplify some tuple_update things Vladislav Shpilevoy
2018-04-27 22:36 ` [tarantool-patches] [PATCH 1/3] vinyl: remove vy_apply_upsert_ops Vladislav Shpilevoy
2018-05-08 20:18 ` [tarantool-patches] " Konstantin Osipov
2018-04-27 22:36 ` [tarantool-patches] [PATCH 2/3] tuple_update: remove alloc and alloc_ctx args Vladislav Shpilevoy
2018-05-08 20:18 ` [tarantool-patches] " Konstantin Osipov
2018-04-27 22:36 ` [tarantool-patches] [PATCH 3/3] rope: make rope library be C template using macros Vladislav Shpilevoy
2018-05-08 20:22 ` [tarantool-patches] " Konstantin Osipov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox