From 55dd9804b0d6da91706c1eae6f4327b8019e28e6 Mon Sep 17 00:00:00 2001
From: Alexandr Lyapunov <a.lyapunov@corp.mail.ru>
Date: Fri, 31 Jul 2015 18:12:22 +0300
Subject: [PATCH] gh-891: multidimensional RTREE implemented

Conflicts:
	src/box/alter.cc
	test/app/snapshot.test.lua
---
 src/box/alter.cc                      |   2 +
 src/box/errcode.h                     |   1 +
 src/box/index.cc                      |  49 +-
 src/box/memtx_rtree.cc                | 306 ++++-----
 src/box/memtx_rtree.h                 |   3 +-
 src/lib/salad/rtree.c                 | 869 +++++++++++++++-----------
 src/lib/salad/rtree.h                 |  71 ++-
 test/box/misc.result                  |   1 +
 test/box/rtree_array.result           | 187 +++++-
 test/box/rtree_array.test.lua         |  43 ++
 test/box/rtree_misc.result            |  12 +-
 test/box/rtree_point.result           |  12 +-
 test/box/rtree_point_r2.result        |  12 +-
 test/unit/CMakeLists.txt              |  18 +-
 test/unit/rtree.cc                    |  51 +-
 test/unit/rtree_itr.cc                |  10 +-
 test/unit/rtree_itr.result            |   2 +-
 test/unit/rtree_multidim.cc           | 436 +++++++++++++
 test/unit/rtree_multidim.result       |  16 +
 test/wal_off/rtree_benchmark.result   | 139 +++-
 test/wal_off/rtree_benchmark.test.lua | 102 ++-
 21 files changed, 1631 insertions(+), 711 deletions(-)
 create mode 100644 test/unit/rtree_multidim.cc
 create mode 100644 test/unit/rtree_multidim.result

diff --git a/src/box/alter.cc b/src/box/alter.cc
index 3a609dfcdb..031f75fa23 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -219,6 +219,8 @@ key_def_new_from_tuple(struct tuple *tuple)
 			key_def_set_part(key_def, i, fieldno, field_type);
 		}
 	}
+	if (type == RTREE && key_def->opts.dimension == 0)
+		key_def->opts.dimension = 2;
 	key_def_check(key_def);
 	scoped_guard.is_active = false;
 	return key_def;
diff --git a/src/box/errcode.h b/src/box/errcode.h
index 142e931a29..2f87bf7295 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -152,6 +152,7 @@ struct errcode_record {
 	/* 98 */_(ER_UNSUPPORTED_ROLE_PRIV,     2, "Unsupported role privilege '%s'") \
 	/* 99 */_(ER_LOAD_FUNCTION,		2, "Failed to dynamically load function '%s': %s") \
 	/*100 */_(ER_FUNCTION_LANGUAGE,		2, "Unsupported language '%s' specified for function '%s'") \
+	/*101 */_(ER_RTREE_RECT_ERROR,		2, "RTree: %s must be an array with %u (point) or %u (rectangle/box) numeric coordinates") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/index.cc b/src/box/index.cc
index fbe5afcd69..147329a26a 100644
--- a/src/box/index.cc
+++ b/src/box/index.cc
@@ -69,41 +69,42 @@ key_validate(struct key_def *key_def, enum iterator_type type, const char *key,
 		/* Fall through. */
 	}
 
-        if (key_def->type == RTREE) {
-                if (part_count != 1 && part_count != 2 && part_count != 4)
-                        tnt_raise(ClientError, ER_KEY_PART_COUNT, 4, part_count);
-                if (part_count == 1) {
+	if (key_def->type == RTREE) {
+		unsigned d = key_def->opts.dimension;
+		if (part_count != 1 && part_count != d && part_count != d * 2)
+			tnt_raise(ClientError, ER_KEY_PART_COUNT,
+				  d  * 2, part_count);
+		if (part_count == 1) {
 			enum mp_type mp_type = mp_typeof(*key);
 			key_mp_type_validate(ARRAY, mp_type, ER_KEY_PART_TYPE, 0);
-			uint32_t arr_size = mp_decode_array(&key);
-			if (arr_size != 2 && arr_size != 4)
-				tnt_raise(ClientError, ER_UNSUPPORTED,
-					  "R-Tree key", "Key should contain 2 (point) "
-					  "or 4 (rectangle) numeric coordinates");
-			for (uint32_t part = 0; part < arr_size; part++) {
+			uint32_t array_size = mp_decode_array(&key);
+			if (array_size != d && array_size != d * 2)
+				tnt_raise(ClientError, ER_RTREE_RECT_ERROR,
+					  "Key", d, d * 2);
+			for (uint32_t part = 0; part < array_size; part++) {
 				enum mp_type mp_type = mp_typeof(*key);
 				mp_next(&key);
 				key_mp_type_validate(NUMBER, mp_type, ER_KEY_PART_TYPE, 0);
 			}
-                } else {
+		} else {
 			for (uint32_t part = 0; part < part_count; part++) {
 				enum mp_type mp_type = mp_typeof(*key);
 				mp_next(&key);
 				key_mp_type_validate(NUMBER, mp_type, ER_KEY_PART_TYPE, part);
 			}
-                }
-        } else {
-                if (part_count > key_def->part_count)
-                        tnt_raise(ClientError, ER_KEY_PART_COUNT,
-                                  key_def->part_count, part_count);
-
-                /* Partial keys are allowed only for TREE index type. */
-                if (key_def->type != TREE && part_count < key_def->part_count) {
-                        tnt_raise(ClientError, ER_EXACT_MATCH,
-                                  key_def->part_count, part_count);
-                }
-                key_validate_parts(key_def, key, part_count);
-        }
+		}
+	} else {
+		if (part_count > key_def->part_count)
+			tnt_raise(ClientError, ER_KEY_PART_COUNT,
+				  key_def->part_count, part_count);
+
+		/* Partial keys are allowed only for TREE index type. */
+		if (key_def->type != TREE && part_count < key_def->part_count) {
+			tnt_raise(ClientError, ER_EXACT_MATCH,
+				  key_def->part_count, part_count);
+		}
+		key_validate_parts(key_def, key, part_count);
+	}
 }
 
 void
diff --git a/src/box/memtx_rtree.cc b/src/box/memtx_rtree.cc
index d0eec301d3..e854b877a1 100644
--- a/src/box/memtx_rtree.cc
+++ b/src/box/memtx_rtree.cc
@@ -36,60 +36,51 @@
 
 /* {{{ Utilities. *************************************************/
 
-inline void extract_rectangle(struct rtree_rect *rect,
-			      const struct tuple *tuple, struct key_def *kd)
+inline int
+mp_decode_rect(struct rtree_rect *rect, unsigned dimension,
+	       const char *mp, unsigned count)
 {
-        assert(kd->part_count == 1);
-	const char *elems = tuple_field(tuple, kd->parts[0].fieldno);
-	uint32_t size = mp_decode_array(&elems);
-	switch (size) {
-	case 1: // array
-	{
-		const char* elems = tuple_field(tuple, kd->parts[0].fieldno);
-		uint32_t size = mp_decode_array(&elems);
-		switch (size) {
-		case 2: // point
-			rect->lower_point.coords[0] =
-				rect->upper_point.coords[0] =
-				mp_decode_num(&elems, 0);
-			rect->lower_point.coords[1] =
-				rect->upper_point.coords[1] =
-				mp_decode_num(&elems, 1);
-			break;
-		case 4:
-			rect->lower_point.coords[0] = mp_decode_num(&elems, 0);
-			rect->lower_point.coords[1] = mp_decode_num(&elems, 1);
-			rect->upper_point.coords[0] = mp_decode_num(&elems, 2);
-			rect->upper_point.coords[1] = mp_decode_num(&elems, 3);
-			break;
-		default:
-			tnt_raise(ClientError, ER_UNSUPPORTED,
-				  "R-Tree index", "Field should be array with "
-				  "size 2 (point) or 4 (rectangle)");
-
+	if (count == dimension) { /* point */
+		for (unsigned i = 0; i < dimension; i++) {
+			coord_t c = mp_decode_num(&mp, i);
+			rect->coords[i * 2] = c;
+			rect->coords[i * 2 + 1] = c;
 		}
-		break;
+	} else if (count == dimension * 2) { /* box */
+		for (unsigned i = 0; i < dimension; i++) {
+			coord_t c = mp_decode_num(&mp, i);
+			rect->coords[i * 2] = c;
+		}
+		for (unsigned i = 0; i < dimension; i++) {
+			coord_t c = mp_decode_num(&mp, i + dimension);
+			rect->coords[i * 2 + 1] = c;
+		}
+	} else {
+		return -1;
 	}
-	case 2: // point
-		rect->lower_point.coords[0] =
-			rect->upper_point.coords[0] =
-			mp_decode_num(&elems, 0);
-		rect->lower_point.coords[1] =
-			rect->upper_point.coords[1] =
-			mp_decode_num(&elems, 1);
-		break;
-	case 4:
-		rect->lower_point.coords[0] = mp_decode_num(&elems, 0);
-		rect->lower_point.coords[1] = mp_decode_num(&elems, 1);
-		rect->upper_point.coords[0] = mp_decode_num(&elems, 2);
-		rect->upper_point.coords[1] = mp_decode_num(&elems, 3);
-		break;
-	default:
-		tnt_raise(ClientError, ER_UNSUPPORTED,
-			  "R-Tree index", "Key should contain 2 (point) or 4 (rectangle) coordinates");
+	rtree_rect_normalize(rect, dimension);
+	return 0;
+}
 
+inline int
+mp_decode_rect(struct rtree_rect *rect, unsigned dimension,
+	       const char *mp)
+{
+	uint32_t size = mp_decode_array(&mp);
+	return mp_decode_rect(rect, dimension, mp, size);
+}
+
+inline void
+extract_rectangle(struct rtree_rect *rect, const struct tuple *tuple,
+		  struct key_def *key_def)
+{
+	assert(key_def->part_count == 1);
+	const char *elems = tuple_field(tuple, key_def->parts[0].fieldno);
+	unsigned dimension = key_def->opts.dimension;
+	if (mp_decode_rect(rect, dimension, elems)) {
+		tnt_raise(ClientError, ER_RTREE_RECT_ERROR,
+			  "Field", dimension, dimension * 2);
 	}
-	rtree_rect_normalize(rect);
 }
 /* {{{ MemtxRTree Iterators ****************************************/
 
@@ -103,7 +94,7 @@ index_rtree_iterator_free(struct iterator *i)
 {
 	struct index_rtree_iterator *itr = (struct index_rtree_iterator *)i;
 	rtree_iterator_destroy(&itr->impl);
-        delete itr;
+	delete itr;
 }
 
 static struct tuple *
@@ -124,102 +115,74 @@ MemtxRTree::~MemtxRTree()
 		index_rtree_iterator_free(m_position);
 		m_position = NULL;
 	}
-	rtree_destroy(&tree);
+	rtree_destroy(&m_tree);
 }
 
 MemtxRTree::MemtxRTree(struct key_def *key_def)
-  : Index(key_def)
+	: Index(key_def)
 {
 	assert(key_def->part_count == 1);
 	assert(key_def->parts[0].type = ARRAY);
 	assert(key_def->opts.is_unique == false);
 
+	m_dimension = key_def->opts.dimension;
+	if (m_dimension < 1 || m_dimension > RTREE_MAX_DIMENSION) {
+		char message[64];
+		snprintf(message, 64, "dimension (%u) must belong to range "
+			 "[%u, %u]", m_dimension, 1, RTREE_MAX_DIMENSION);
+		tnt_raise(ClientError, ER_UNSUPPORTED,
+			  "RTREE index", message);
+	}
+
 	memtx_index_arena_init();
-	rtree_init(&tree, MEMTX_EXTENT_SIZE,
+	rtree_init(&m_tree, m_dimension, MEMTX_EXTENT_SIZE,
 		   memtx_index_extent_alloc, memtx_index_extent_free);
 }
 
 size_t
 MemtxRTree::size() const
 {
-	return rtree_number_of_records(&tree);
+	return rtree_number_of_records(&m_tree);
 }
 
 size_t
 MemtxRTree::bsize() const
 {
-	return rtree_used_size(&tree);
+	return rtree_used_size(&m_tree);
 }
 
 struct tuple *
 MemtxRTree::findByKey(const char *key, uint32_t part_count) const
 {
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+
 	rtree_rect rect;
-        struct rtree_iterator iterator;
-        rtree_iterator_init(&iterator);
-        switch (part_count) {
-	case 1:
-	{
-		uint32_t size = mp_decode_array(&key);
-		switch (size) {
-		case 2:
-			rect.lower_point.coords[0] =
-				rect.upper_point.coords[0] =
-				mp_decode_num(&key, 0);
-			rect.lower_point.coords[1] =
-				rect.upper_point.coords[1] =
-				mp_decode_num(&key, 1);
-			break;
-		case 4:
-			rect.lower_point.coords[0] = mp_decode_num(&key, 0);
-			rect.lower_point.coords[1] = mp_decode_num(&key, 1);
-			rect.upper_point.coords[0] = mp_decode_num(&key, 2);
-			rect.upper_point.coords[1] = mp_decode_num(&key, 3);
-			break;
-		default:
-			assert(false);
-		}
-		break;
-	}
-	case 2:
-		rect.lower_point.coords[0] =
-			rect.upper_point.coords[0] =
-			mp_decode_num(&key, 0);
-		rect.lower_point.coords[1] =
-			rect.upper_point.coords[1] =
-			mp_decode_num(&key, 1);
-		break;
-	case 4:
-		rect.lower_point.coords[0] = mp_decode_num(&key, 0);
-		rect.lower_point.coords[1] = mp_decode_num(&key, 1);
-		rect.upper_point.coords[0] = mp_decode_num(&key, 2);
-		rect.upper_point.coords[1] = mp_decode_num(&key, 3);
-		break;
-	default:
+	if (mp_decode_rect(&rect, m_dimension, key, part_count))
 		assert(false);
-	}
-        struct tuple *result = NULL;
-        if (rtree_search(&tree, &rect, SOP_OVERLAPS, &iterator))
+
+	struct tuple *result = NULL;
+	if (rtree_search(&m_tree, &rect, SOP_OVERLAPS, &iterator))
 		result = (struct tuple *)rtree_iterator_next(&iterator);
-        rtree_iterator_destroy(&iterator);
-        return result;
+	rtree_iterator_destroy(&iterator);
+	return result;
 }
 
 struct tuple *
 MemtxRTree::replace(struct tuple *old_tuple, struct tuple *new_tuple,
-                    enum dup_replace_mode)
+		    enum dup_replace_mode)
 {
-        struct rtree_rect rect;
-        if (new_tuple) {
-                extract_rectangle(&rect, new_tuple, key_def);
-                rtree_insert(&tree, &rect, new_tuple);
-        }
+	struct rtree_rect rect;
+	if (new_tuple) {
+		extract_rectangle(&rect, new_tuple, key_def);
+		rtree_insert(&m_tree, &rect, new_tuple);
+	}
 	if (old_tuple) {
-                extract_rectangle(&rect, old_tuple, key_def);
-                if (!rtree_remove(&tree, &rect, old_tuple))
-                        old_tuple = NULL;
-        }
-        return old_tuple;
+		extract_rectangle(&rect, old_tuple, key_def);
+		if (!rtree_remove(&m_tree, &rect, old_tuple))
+			old_tuple = NULL;
+	}
+	return old_tuple;
 }
 
 struct iterator *
@@ -240,98 +203,59 @@ MemtxRTree::allocIterator() const
 
 void
 MemtxRTree::initIterator(struct iterator *iterator, enum iterator_type type,
-                         const char *key, uint32_t part_count) const
+			 const char *key, uint32_t part_count) const
 {
-        struct rtree_rect rect;
-        index_rtree_iterator *it = (index_rtree_iterator *)iterator;
-        switch (part_count) {
-        case 0:
-                if (type != ITER_ALL) {
-                        tnt_raise(ClientError, ER_UNSUPPORTED,
-				  "R-Tree index", "It is possible to omit key only for ITER_ALL");
-                }
-                break;
-	case 1:
-	{
-		uint32_t size = mp_decode_array(&key);
-		switch (size) {
-		case 2:
-			rect.lower_point.coords[0] =
-				rect.upper_point.coords[0] =
-				mp_decode_num(&key, 0);
-			rect.lower_point.coords[1] =
-				rect.upper_point.coords[1] =
-				mp_decode_num(&key, 1);
-			break;
-		case 4:
-			rect.lower_point.coords[0] = mp_decode_num(&key, 0);
-			rect.lower_point.coords[1] = mp_decode_num(&key, 1);
-			rect.upper_point.coords[0] = mp_decode_num(&key, 2);
-			rect.upper_point.coords[1] = mp_decode_num(&key, 3);
-			break;
-		default:
+	index_rtree_iterator *it = (index_rtree_iterator *)iterator;
+
+	struct rtree_rect rect;
+	if (part_count == 0) {
+		if (type != ITER_ALL) {
 			tnt_raise(ClientError, ER_UNSUPPORTED,
-				  "R-Tree index", "Key should be array of 2 (point) "
-				  "or 4 (rectangle) numeric coordinates");
+				  "R-Tree index",
+				  "It is possible to omit "
+				  "key only for ITER_ALL");
 		}
-		break;
+	} else if (mp_decode_rect(&rect, m_dimension, key, part_count)) {
+		tnt_raise(ClientError, ER_RTREE_RECT_ERROR,
+			  "Key", m_dimension, m_dimension * 2);
 	}
-	case 2:
-		rect.lower_point.coords[0] =
-			rect.upper_point.coords[0] =
-			mp_decode_num(&key, 0);
-		rect.lower_point.coords[1] =
-			rect.upper_point.coords[1] =
-			mp_decode_num(&key, 1);
+
+	spatial_search_op op;
+	switch (type) {
+	case ITER_ALL:
+		op = SOP_ALL;
+		break;
+	case ITER_EQ:
+		op = SOP_EQUALS;
+		break;
+	case ITER_GT:
+		op = SOP_STRICT_CONTAINS;
+		break;
+	case ITER_GE:
+		op = SOP_CONTAINS;
 		break;
-	case 4:
-		rect.lower_point.coords[0] = mp_decode_num(&key, 0);
-		rect.lower_point.coords[1] = mp_decode_num(&key, 1);
-		rect.upper_point.coords[0] = mp_decode_num(&key, 2);
-		rect.upper_point.coords[1] = mp_decode_num(&key, 3);
+	case ITER_LT:
+		op = SOP_STRICT_BELONGS;
+		break;
+	case ITER_LE:
+		op = SOP_BELONGS;
+		break;
+	case ITER_OVERLAPS:
+		op = SOP_OVERLAPS;
+		break;
+	case ITER_NEIGHBOR:
+		op = SOP_NEIGHBOR;
 		break;
 	default:
 		tnt_raise(ClientError, ER_UNSUPPORTED,
-			  "R-Tree index", "Key contain 2 (point) "
-			  "or 4 (rectangle) numeric coordinates");
+			  "RTREE index", "Unsupported search operation for RTREE");
 	}
-        spatial_search_op op;
-        switch (type) {
-        case ITER_ALL:
-                op = SOP_ALL;
-                break;
-        case ITER_EQ:
-                op = SOP_EQUALS;
-                break;
-        case ITER_GT:
-                op = SOP_STRICT_CONTAINS;
-                break;
-        case ITER_GE:
-                op = SOP_CONTAINS;
-                break;
-        case ITER_LT:
-                op = SOP_STRICT_BELONGS;
-                break;
-        case ITER_LE:
-                op = SOP_BELONGS;
-                break;
-        case ITER_OVERLAPS:
-                op = SOP_OVERLAPS;
-                break;
-        case ITER_NEIGHBOR:
-                op = SOP_NEIGHBOR;
-                break;
-        default:
-                tnt_raise(ClientError, ER_UNSUPPORTED,
-                          "R-Tree index", "Unsupported search operation for R-Tree");
-        }
-        rtree_search(&tree, &rect, op, &it->impl);
+	rtree_search(&m_tree, &rect, op, &it->impl);
 }
 
 void
 MemtxRTree::beginBuild()
 {
-	rtree_purge(&tree);
+	rtree_purge(&m_tree);
 }
 
-
diff --git a/src/box/memtx_rtree.h b/src/box/memtx_rtree.h
index f4aa0ca614..ef2b91b59f 100644
--- a/src/box/memtx_rtree.h
+++ b/src/box/memtx_rtree.h
@@ -53,7 +53,8 @@ class MemtxRTree: public Index
                                   const char *key, uint32_t part_count) const;
 
 protected:
-	struct rtree tree;
+	unsigned m_dimension;
+	struct rtree m_tree;
 };
 
 #endif /* TARANTOOL_BOX_MEMTX_RTREE_H_INCLUDED */
diff --git a/src/lib/salad/rtree.c b/src/lib/salad/rtree.c
index 70a43d410c..11da353dc6 100644
--- a/src/lib/salad/rtree.c
+++ b/src/lib/salad/rtree.c
@@ -29,49 +29,42 @@
 #include "rtree.h"
 #include <string.h>
 #include <assert.h>
+#include <limits.h>
+#include <sys/types.h>
 
 /*------------------------------------------------------------------------- */
 /* R-tree internal structures definition */
 /*------------------------------------------------------------------------- */
+enum {
+	/* rtree will try to determine optimal page size */
+	RTREE_OPTIMAL_BRANCHES_IN_PAGE = 18,
+	/* actual number of branches could be up to double of the previous
+	 * constant */
+	RTREE_MAXIMUM_BRANCHES_IN_PAGE = RTREE_OPTIMAL_BRANCHES_IN_PAGE * 2
+};
 
 struct rtree_page_branch {
-	struct rtree_rect rect;
 	union {
 		struct rtree_page *page;
 		record_t record;
 	} data;
+	struct rtree_rect rect;
 };
 
 enum {
-	/* maximal number of branches at page */
-	RTREE_MAX_FILL = (RTREE_PAGE_SIZE - sizeof(int)) /
-		sizeof(struct rtree_page_branch),
-	/* minimal number of branches at non-root page */
-	RTREE_MIN_FILL = RTREE_MAX_FILL / 2
+	RTREE_BRANCH_DATA_SIZE = sizeof(((struct rtree_page_branch *)0)->data)
 };
 
 struct rtree_page {
 	/* number of branches at page */
 	int n;
 	/* branches */
-	struct rtree_page_branch b[RTREE_MAX_FILL];
-};
-
-struct rtree_neighbor {
-	void *child;
-	struct rtree_neighbor *next;
-	int level;
-	sq_coord_t distance;
-};
-
-enum {
-	RTREE_NEIGHBORS_IN_PAGE = (RTREE_PAGE_SIZE - sizeof(void*)) /
-		sizeof(struct rtree_neighbor)
+	char data[];
 };
 
 struct rtree_neighbor_page {
 	struct rtree_neighbor_page* next;
-	struct rtree_neighbor buf[RTREE_NEIGHBORS_IN_PAGE];
+	struct rtree_neighbor buf[];
 };
 
 struct rtree_reinsert_list {
@@ -79,46 +72,77 @@ struct rtree_reinsert_list {
 	int level;
 };
 
+static int
+neighbor_cmp(struct rtree_neighbor *a, struct rtree_neighbor *b)
+{
+	return a->distance < b->distance ? -1 :
+	       a->distance > b->distance ? 1 :
+	       a->level < b->level ? -1 :
+	       a->level > b->level ? 1 :
+	       a < b ? -1 : a > b ? 1 : 0;
+	return 0;
+}
+
+rb_gen(, rtnt_, rtnt_t, struct rtree_neighbor, link, neighbor_cmp);
+
 /*------------------------------------------------------------------------- */
 /* R-tree rectangle methods */
 /*------------------------------------------------------------------------- */
 
 void
-rtree_rect_normalize(struct rtree_rect *rect)
+rtree_rect_normalize(struct rtree_rect *rect, unsigned dimension)
 {
-	for (int i = RTREE_DIMENSION; --i >= 0; ) {
-		if (rect->lower_point.coords[i] <= rect->upper_point.coords[i])
+	for (int i = dimension; --i >= 0; ) {
+		coord_t *coords = &rect->coords[2 * i];
+		if (coords[0] <= coords[1])
 			continue;
-		coord_t tmp = rect->lower_point.coords[i];
-		rect->lower_point.coords[i] = rect->upper_point.coords[i];
-		rect->upper_point.coords[i] = tmp;
+		coord_t tmp = coords[0];
+		coords[0] = coords[1];
+		coords[1] = tmp;
 	}
 }
 
+static void
+rtree_rect_copy(struct rtree_rect *to, const struct rtree_rect *from,
+		unsigned dimension)
+{
+	for (int i = dimension * 2; --i >= 0; )
+		to->coords[i] = from->coords[i];
+}
+
 void
 rtree_set2d(struct rtree_rect *rect,
 	    coord_t left, coord_t bottom, coord_t right, coord_t top)
 {
-	assert(RTREE_DIMENSION == 2);
-	rect->lower_point.coords[0] = left;
-	rect->lower_point.coords[1] = bottom;
-	rect->upper_point.coords[0] = right;
-	rect->upper_point.coords[1] = top;
+	rect->coords[0] = left;
+	rect->coords[1] = right;
+	rect->coords[2] = bottom;
+	rect->coords[3] = top;
+}
+
+void
+rtree_set2dp(struct rtree_rect *rect, coord_t x, coord_t y)
+{
+	rect->coords[0] = x;
+	rect->coords[1] = x;
+	rect->coords[2] = y;
+	rect->coords[3] = y;
 }
 
 static sq_coord_t
-rtree_rect_point_distance2(const struct rtree_rect *rect,
-			   const struct rtree_point *point)
+rtree_rect_neigh_distance2(const struct rtree_rect *rect,
+			   const struct rtree_rect *neigh_rect,
+			   unsigned dimension)
 {
 	sq_coord_t result = 0;
-	for (int i = RTREE_DIMENSION; --i >= 0; ) {
-		if (point->coords[i] < rect->lower_point.coords[i]) {
-			sq_coord_t diff = (sq_coord_t)(point->coords[i] -
-				rect->lower_point.coords[i]);
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords = &rect->coords[2 * i];
+		coord_t neigh_coord = neigh_rect->coords[2 * i];
+		if (neigh_coord < coords[0]) {
+			sq_coord_t diff = (sq_coord_t)(neigh_coord - coords[0]);
 			result += diff * diff;
-		} else if (point->coords[i] > rect->upper_point.coords[i]) {
-			sq_coord_t diff = (sq_coord_t)(point->coords[i] -
-				rect->upper_point.coords[i]);
+		} else if (neigh_coord > coords[1]) {
+			sq_coord_t diff = (sq_coord_t)(neigh_coord - coords[1]);
 			result += diff * diff;
 		}
 	}
@@ -126,24 +150,38 @@ rtree_rect_point_distance2(const struct rtree_rect *rect,
 }
 
 static area_t
-rtree_rect_area(const struct rtree_rect *rect)
+rtree_rect_area(const struct rtree_rect *rect, unsigned dimension)
 {
 	area_t area = 1;
-	for (int i = RTREE_DIMENSION; --i >= 0; ) {
-		area *= rect->upper_point.coords[i] -
-			rect->lower_point.coords[i];
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords = &rect->coords[2 * i];
+		area *= coords[1] - coords[0];
 	}
 	return area;
 }
 
+static coord_t
+rtree_rect_half_margin(const struct rtree_rect *rect, unsigned dimension)
+{
+	coord_t hm = 0;
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords = &rect->coords[2 * i];
+		hm += coords[1] - coords[0];
+	}
+	return hm;
+}
+
 static void
-rtree_rect_add(struct rtree_rect *to, const struct rtree_rect *item)
+rtree_rect_add(struct rtree_rect *to, const struct rtree_rect *item,
+	       unsigned dimension)
 {
-	for (int i = RTREE_DIMENSION; --i >= 0; ) {
-		if (to->lower_point.coords[i] > item->lower_point.coords[i])
-			to->lower_point.coords[i] = item->lower_point.coords[i];
-		if (to->upper_point.coords[i] < item->upper_point.coords[i])
-			to->upper_point.coords[i] = item->upper_point.coords[i];
+	for (int i = dimension; --i >= 0; ) {
+		coord_t *to_coords = &to->coords[2 * i];
+		const coord_t *item_coords = &item->coords[2 * i];
+		if (to_coords[0] > item_coords[0])
+			to_coords[0] = item_coords[0];
+		if (to_coords[1] < item_coords[1])
+			to_coords[1] = item_coords[1];
 	}
 }
 
@@ -159,86 +197,117 @@ rtree_max(coord_t a, coord_t b)
 	return a > b ? a : b;
 }
 
-static struct rtree_rect
+static void
 rtree_rect_cover(const struct rtree_rect *item1,
-		 const struct rtree_rect *item2)
+		 const struct rtree_rect *item2,
+		 struct rtree_rect *result,
+		 unsigned dimension)
 {
-	struct rtree_rect res;
-	for (int i = RTREE_DIMENSION; --i >= 0; ) {
-		res.lower_point.coords[i] =
-			rtree_min(item1->lower_point.coords[i],
-				  item2->lower_point.coords[i]);
-		res.upper_point.coords[i] =
-			rtree_max(item1->upper_point.coords[i],
-				  item2->upper_point.coords[i]);
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *i1_coords = &item1->coords[2 * i];
+		const coord_t *i2_coords = &item2->coords[2 * i];
+		coord_t *r_coords = &result->coords[2 * i];
+		r_coords[0] = rtree_min(i1_coords[0], i2_coords[0]);
+		r_coords[1] = rtree_max(i1_coords[1], i2_coords[1]);
+	}
+}
+
+static void
+rtree_rect_intersection(const struct rtree_rect *item1,
+			const struct rtree_rect *item2,
+			struct rtree_rect *result,
+			unsigned dimension)
+{
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *i1_coords = &item1->coords[2 * i];
+		const coord_t *i2_coords = &item2->coords[2 * i];
+		coord_t *r_coords = &result->coords[2 * i];
+		if (i1_coords[0] > i2_coords[1] || i1_coords[1] < i2_coords[0])
+			r_coords[0] = r_coords[1] = 0;
+		else {
+			r_coords[0] = rtree_max(i1_coords[0], i2_coords[0]);
+			r_coords[1] = rtree_min(i1_coords[1], i2_coords[1]);
+		}
 	}
-	return res;
 }
 
 static bool
 rtree_rect_intersects_rect(const struct rtree_rect *rt1,
-			   const struct rtree_rect *rt2)
+			   const struct rtree_rect *rt2,
+			   unsigned dimension)
 {
-	for (int i = RTREE_DIMENSION; --i >= 0; )
-		if (rt1->lower_point.coords[i] > rt2->upper_point.coords[i] ||
-		    rt1->upper_point.coords[i] < rt2->lower_point.coords[i])
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords1 = &rt1->coords[2 * i];
+		const coord_t *coords2 = &rt2->coords[2 * i];
+		if (coords1[0] > coords2[1] || coords1[1] < coords2[0])
 			return false;
+	}
 	return true;
 }
 
 static bool
 rtree_rect_in_rect(const struct rtree_rect *rt1,
-		   const struct rtree_rect *rt2)
+		   const struct rtree_rect *rt2,
+		   unsigned dimension)
 {
-	for (int i = RTREE_DIMENSION; --i >= 0; )
-		if (rt1->lower_point.coords[i] < rt2->lower_point.coords[i] ||
-		    rt1->upper_point.coords[i] > rt2->upper_point.coords[i])
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords1 = &rt1->coords[2 * i];
+		const coord_t *coords2 = &rt2->coords[2 * i];
+		if (coords1[0] < coords2[0] || coords1[1] > coords2[1])
 			return false;
+	}
 	return true;
 }
 
 static bool
 rtree_rect_strict_in_rect(const struct rtree_rect *rt1,
-			  const struct rtree_rect *rt2)
+			  const struct rtree_rect *rt2,
+			  unsigned dimension)
 {
-	for (int i = RTREE_DIMENSION; --i >= 0; )
-		if (rt1->lower_point.coords[i] <= rt2->lower_point.coords[i] ||
-		    rt1->upper_point.coords[i] >= rt2->upper_point.coords[i])
+	for (int i = dimension; --i >= 0; ) {
+		const coord_t *coords1 = &rt1->coords[2 * i];
+		const coord_t *coords2 = &rt2->coords[2 * i];
+		if (coords1[0] <= coords2[0] || coords1[1] >= coords2[1])
 			return false;
+	}
 	return true;
 }
 
 static bool
 rtree_rect_holds_rect(const struct rtree_rect *rt1,
-		      const struct rtree_rect *rt2)
+		      const struct rtree_rect *rt2,
+		      unsigned dimension)
 {
-	return rtree_rect_in_rect(rt2, rt1);
+	return rtree_rect_in_rect(rt2, rt1, dimension);
 }
 
 static bool
 rtree_rect_strict_holds_rect(const struct rtree_rect *rt1,
-			     const struct rtree_rect *rt2)
+			     const struct rtree_rect *rt2,
+			     unsigned dimension)
 {
-	return rtree_rect_strict_in_rect(rt2, rt1);
+	return rtree_rect_strict_in_rect(rt2, rt1, dimension);
 }
 
 static bool
 rtree_rect_equal_to_rect(const struct rtree_rect *rt1,
-			 const struct rtree_rect *rt2)
+			 const struct rtree_rect *rt2,
+			 unsigned dimension)
 {
-	for (int i = RTREE_DIMENSION; --i >= 0; )
-		if (rt1->lower_point.coords[i] != rt2->lower_point.coords[i] ||
-		    rt1->upper_point.coords[i] != rt2->upper_point.coords[i])
+	for (int i = dimension * 2; --i >= 0; )
+		if (rt1->coords[i] != rt2->coords[i])
 			return false;
 	return true;
 }
 
 static bool
 rtree_always_true(const struct rtree_rect *rt1,
-		  const struct rtree_rect *rt2)
+		  const struct rtree_rect *rt2,
+		  unsigned dimension)
 {
 	(void)rt1;
 	(void)rt2;
+	(void)dimension;
 	return true;
 }
 
@@ -268,199 +337,234 @@ rtree_free_page(struct rtree *tree, struct rtree_page *page)
 	tree->free_pages = (void *)page;
 }
 
+static struct rtree_page_branch *
+rtree_get_branch(const struct rtree *tree, const struct rtree_page *page,
+		 unsigned ind)
+{
+	return (struct rtree_page_branch *)
+		(page->data + ind * tree->page_branch_size);
+}
+
 static void
-set_next_reinsert_page(struct rtree_page *page, struct rtree_page *next_page)
+rtree_branch_copy(struct rtree_page_branch *to,
+		  const struct rtree_page_branch *from, unsigned dimension)
+{
+	to->data = from->data;
+	rtree_rect_copy(&to->rect, &from->rect, dimension);
+}
+
+
+static void
+set_next_reinsert_page(const struct rtree *tree, struct rtree_page *page,
+		       struct rtree_page *next_page)
 {
 	/* The page must be MIN_FILLed, so last branch is unused */
-	page->b[RTREE_MAX_FILL - 1].data.page = next_page;
+	struct rtree_page_branch *b = rtree_get_branch(tree, page,
+						       tree->page_max_fill - 1);
+	b->data.page = next_page;
 }
 
 struct rtree_page *
-get_next_reinsert_page(const struct rtree_page *page)
+get_next_reinsert_page(const struct rtree *tree, const struct rtree_page *page)
 {
-	return page->b[RTREE_MAX_FILL - 1].data.page;
+	struct rtree_page_branch *b = rtree_get_branch(tree, page,
+						       tree->page_max_fill - 1);
+	return b->data.page;
 }
 
 /* Calculate cover of all rectangles at page */
-static struct rtree_rect
-rtree_page_cover(const struct rtree_page *page)
+static void
+rtree_page_cover(const struct rtree *tree, const struct rtree_page *page,
+		 struct rtree_rect *res)
 {
-	struct rtree_rect res = page->b[0].rect;
-	for (int i = 1; i < page->n; i++)
-		rtree_rect_add(&res, &page->b[i].rect);
-	return res;
+	rtree_rect_copy(res, &rtree_get_branch(tree, page, 0)->rect,
+			tree->dimension);
+	for (int i = 1; i < page->n; i++) {
+		rtree_rect_add(res, &rtree_get_branch(tree, page, i)->rect,
+			       tree->dimension);
+	}
 }
 
 /* Create root page by first inserting record */
 static void
-rtree_page_init_with_record(struct rtree_page *page,
+rtree_page_init_with_record(const struct rtree *tree, struct rtree_page *page,
 			    struct rtree_rect *rect, record_t obj)
 {
+	struct rtree_page_branch *b = rtree_get_branch(tree, page, 0);
 	page->n = 1;
-	page->b[0].rect = *rect;
-	page->b[0].data.record = obj;
-}
-
-/* Create root page by branch */
-static void
-rtree_page_init_with_branch(struct rtree_page *page,
-			    const struct rtree_page_branch *br)
-{
-	page->n = 1;
-	page->b[0] = *br;
+	rtree_rect_copy(&b->rect, rect, tree->dimension);
+	b->data.record = obj;
 }
 
 /* Create new root page (root splitting) */
 static void
-rtree_page_init_with_pages(struct rtree_page *page,
-			   struct rtree_page *page1,
-			   struct rtree_page *page2)
+rtree_page_init_with_pages(const struct rtree *tree, struct rtree_page *page,
+			   struct rtree_page *page1, struct rtree_page *page2)
 {
 	page->n = 2;
-	page->b[0].rect = rtree_page_cover(page1);
-	page->b[0].data.page = page1;
-	page->b[1].rect = rtree_page_cover(page2);
-	page->b[1].data.page = page2;
+	struct rtree_page_branch *b = rtree_get_branch(tree, page, 0);
+	rtree_page_cover(tree, page1, &b->rect);
+	b->data.page = page1;
+	b = rtree_get_branch(tree, page, 1);
+	rtree_page_cover(tree, page2, &b->rect);
+	b->data.page = page2;
 }
 
 static struct rtree_page *
 rtree_split_page(struct rtree *tree, struct rtree_page *page,
 		 const struct rtree_page_branch *br)
 {
-	area_t rect_area[RTREE_MAX_FILL + 1];
-	rect_area[0] = rtree_rect_area(&br->rect);
-	for (int i = 0; i < RTREE_MAX_FILL; i++)
-		rect_area[i + 1] = rtree_rect_area(&page->b[i].rect);
-
-	/*
-	 * As the seeds for the two groups, find two rectangles
-	 * which waste the most area if covered by a single
-	 * rectangle.
-	 */
-	int seed[2] = {-1, -1};
-	coord_t worst_waste = 0;
-	bool worst_waste_set = false;
-
-	const struct rtree_page_branch *bp = br;
-	for (int i = 0; i < RTREE_MAX_FILL; i++) {
-		for (int j = i + 1; j <= RTREE_MAX_FILL; j++) {
-			struct rtree_rect cover =
-				rtree_rect_cover(&bp->rect,
-						 &page->b[j - 1].rect);
-			coord_t waste = rtree_rect_area(&cover) -
-				rect_area[i] - rect_area[j];
-			if (!worst_waste_set) {
-				worst_waste_set = true;
-				worst_waste = waste;
-				seed[0] = i;
-				seed[1] = j;
-			}else if (waste > worst_waste) {
-				worst_waste = waste;
-				seed[0] = i;
-				seed[1] = j;
-			}
-		}
-		bp = page->b + i;
+	assert(page->n == tree-> page_max_fill);
+	const struct rtree_rect *rects[RTREE_MAXIMUM_BRANCHES_IN_PAGE + 1];
+	unsigned ids[RTREE_MAXIMUM_BRANCHES_IN_PAGE + 1];
+	rects[0] = &br->rect;
+	ids[0] = 0;
+	for (unsigned i = 0; i < page->n; i++) {
+		struct rtree_page_branch *b = rtree_get_branch(tree, page, i);
+		rects[i + 1] = &b->rect;
+		ids[i + 1] = i + 1;
 	}
-	assert(seed[0] >= 0);
-
-	char taken[RTREE_MAX_FILL];
-	memset(taken, 0, sizeof(taken));
-	struct rtree_rect group_rect[2];
-	struct rtree_page *p = rtree_alloc_page(tree);
-	tree->n_pages++;
-
-	taken[seed[1] - 1] = 2;
-	group_rect[1] = page->b[seed[1] - 1].rect;
-
-	if (seed[0] == 0) {
-		group_rect[0] = br->rect;
-		rtree_page_init_with_branch(p, br);
-	} else {
-		group_rect[0] = page->b[seed[0] - 1].rect;
-		rtree_page_init_with_branch(p, &page->b[seed[0] - 1]);
-		page->b[seed[0] - 1] = *br;
-	}
-	area_t group_area[2] = {rect_area[seed[0]], rect_area[seed[1]]};
-	int group_card[2] = {1, 1};
-
-	/*
-	 * Split remaining rectangles between two groups.
-	 * The one chosen is the one with the greatest difference in area
-	 * expansion depending on which group - the rect most strongly
-	 * attracted to one group and repelled from the other.
-	 */
-	while (group_card[0] + group_card[1] < RTREE_MAX_FILL + 1
-	       && group_card[0] < RTREE_MAX_FILL + 1 - RTREE_MIN_FILL
-	       && group_card[1] < RTREE_MAX_FILL + 1 - RTREE_MIN_FILL)
-	{
-		int better_group = -1, chosen = -1;
-		area_t biggest_diff = -1;
-		for (int i = 0; i < RTREE_MAX_FILL; i++) {
-			if (taken[i])
-				continue;
-			struct rtree_rect cover0 =
-				rtree_rect_cover(&group_rect[0],
-						 &page->b[i].rect);
-			struct rtree_rect cover1 =
-				rtree_rect_cover(&group_rect[1],
-						 &page->b[i].rect);
-			area_t diff = rtree_rect_area(&cover0) - group_area[0]
-				- (rtree_rect_area(&cover1) - group_area[1]);
-			if (diff > biggest_diff || -diff > biggest_diff) {
-				chosen = i;
-				if (diff < 0) {
-					better_group = 0;
-					biggest_diff = -diff;
-				} else {
-					better_group = 1;
-					biggest_diff = diff;
+	const unsigned n = page->n + 1;
+	const unsigned k_max = n - 2 * tree->page_min_fill;
+	unsigned d = tree->dimension;
+	unsigned best_axis = 0;
+	coord_t best_s = 0;
+	for (unsigned a = 0; a < d; a++) {
+		for (unsigned i = 0; i < n - 1; i++) {
+			unsigned min_i = i;
+			coord_t min_l = rects[ids[i]]->coords[2 * a];
+			coord_t min_r = rects[ids[i]]->coords[2 * a + 1];
+			for (unsigned j = i + 1; j < n; j++) {
+				coord_t l = rects[ids[j]]->coords[2 * a];
+				coord_t r = rects[ids[j]]->coords[2 * a + 1];
+				if (l < min_l || (l == min_l && r < min_r)) {
+					min_i = j;
+					min_l = l;
+					min_r = r;
 				}
 			}
+			unsigned tmp = ids[i];
+			ids[i] = ids[min_i];
+			ids[min_i] = tmp;
 		}
-		assert(chosen >= 0);
-		group_card[better_group]++;
-		rtree_rect_add(&group_rect[better_group],
-			       &page->b[chosen].rect);
-		group_area[better_group] =
-			rtree_rect_area(&group_rect[better_group]);
-		taken[chosen] = better_group + 1;
-		if (better_group == 0)
-			p->b[group_card[0] - 1] = page->b[chosen];
-	}
-	/*
-	 * If one group gets too full, then remaining rectangle
-	 * are split between two groups in such way to balance
-	 * CARDs of two groups.
-	 */
-	if (group_card[0] + group_card[1] < RTREE_MAX_FILL + 1) {
-		for (int i = 0; i < RTREE_MAX_FILL; i++) {
-			if (taken[i])
-				continue;
-			if (group_card[0] >= group_card[1]) {
-				taken[i] = 2;
-				group_card[1] += 1;
-			} else {
-				taken[i] = 1;
-				p->b[group_card[0]++] = page->b[i];
+		struct rtree_rect test_rect;
+		coord_t dir_hm[RTREE_MAXIMUM_BRANCHES_IN_PAGE + 1];
+		coord_t rev_hm[RTREE_MAXIMUM_BRANCHES_IN_PAGE + 1];
+		dir_hm[0] = 0;
+		rtree_rect_copy(&test_rect, rects[ids[0]], d);
+		dir_hm[1] = rtree_rect_half_margin(&test_rect, d);
+		for (unsigned i = 1; i < n - tree->page_min_fill; i++) {
+			rtree_rect_add(&test_rect, rects[ids[i]], d);
+			dir_hm[i + 1] = rtree_rect_half_margin(&test_rect, d);
+		}
+		rev_hm[0] = 0;
+		rtree_rect_copy(&test_rect, rects[ids[n - 1]], d);
+		rev_hm[1] = rtree_rect_half_margin(&test_rect, d);
+		for (unsigned i = 1; i < n - tree->page_min_fill; i++) {
+			rtree_rect_add(&test_rect, rects[ids[n - i - 1]], d);
+			rev_hm[i + 1] = rtree_rect_half_margin(&test_rect, d);
+		}
+		coord_t s = 0;
+		for (unsigned k = 0; k < k_max; k++) {
+			unsigned k1 = tree->page_min_fill + k;
+			unsigned k2 = n - k1;
+			s += dir_hm[k1] + rev_hm[k2];
+		}
+		if (a == 0 || s < best_s) {
+			best_axis = a;
+			best_s = s;
+		}
+	}
+	unsigned a = best_axis;
+	for (unsigned i = 0; i < n - 1; i++) {
+		unsigned min_i = i;
+		coord_t min_l = rects[ids[i]]->coords[2 * a];
+		coord_t min_r = rects[ids[i]]->coords[2 * a + 1];
+		for (unsigned j = i + 1; j < n; j++) {
+			coord_t l = rects[ids[j]]->coords[2 * a];
+			coord_t r = rects[ids[j]]->coords[2 * a + 1];
+			if (l < min_l || (l == min_l && r < min_r)) {
+				min_i = j;
+				min_l = l;
+				min_r = r;
 			}
 		}
+		unsigned tmp = ids[i];
+		ids[i] = ids[min_i];
+		ids[min_i] = tmp;
+	}
+	area_t min_overlap = 0;
+	area_t min_area = 0;
+	unsigned min_k = 0;
+	for (unsigned k = 0; k < k_max; k++) {
+		unsigned k1 = tree->page_min_fill + k;
+		/* unsigned k2 = n - k1; */
+		struct rtree_rect rt1, rt2, over_rt;
+		rtree_rect_copy(&rt1, rects[ids[0]], d);
+		for (int i = 1; i < k1; i++) {
+			rtree_rect_add(&rt1, rects[ids[i]], d);
+		}
+		rtree_rect_copy(&rt2, rects[ids[k1]], d);
+		for (int i = k1 + 1; i < n; i++) {
+			rtree_rect_add(&rt2, rects[ids[i]], d);
+		}
+		rtree_rect_intersection(&rt1, &rt2, &over_rt, d);
+		area_t overlap = rtree_rect_area(&over_rt, d);
+		area_t area = rtree_rect_area(&rt1, d) +
+			rtree_rect_area(&rt2, d);
+		if (k == 0 || overlap < min_overlap ||
+			(overlap == min_overlap && area < min_area)) {
+			min_k = k;
+			min_overlap = overlap;
+			min_area = area;
+		}
+	}
+	unsigned k = min_k;
+	unsigned k1 = tree->page_min_fill + k;
+	unsigned k2 = n - k1;
+	struct rtree_page *new_page = rtree_alloc_page(tree);
+	tree->n_pages++;
+	char taken[RTREE_MAXIMUM_BRANCHES_IN_PAGE];
+	memset(taken, 0, sizeof(taken));
+	for (unsigned i = 0; i < k1; i++) {
+		struct rtree_page_branch *new_b =
+			rtree_get_branch(tree, new_page, i);
+		const struct rtree_page_branch *from_b = br;
+		if (ids[i]) {
+			from_b = rtree_get_branch(tree, page, ids[i] - 1);
+			taken[ids[i] - 1] = 1;
+		}
+		rtree_branch_copy(new_b, from_b, d);
+	}
+	unsigned moved = 0;
+	for (unsigned i = 0, j = 0; j < page->n; j++) {
+		if (taken[j] == 0) {
+			struct rtree_page_branch *to, *from;
+			to = rtree_get_branch(tree, page, i++);
+			from = rtree_get_branch(tree, page, j);
+			rtree_branch_copy(to, from, tree->dimension);
+			moved++;
+		}
 	}
-	p->n = group_card[0];
-	page->n = group_card[1];
-	for (int i = 0, j = 0; i < page->n; j++) {
-		if (taken[j] == 2)
-			page->b[i++] = page->b[j];
+	assert(moved == k2 || moved + 1 == k2);
+	if (moved + 1 == k2) {
+		struct rtree_page_branch *to;
+		to = rtree_get_branch(tree, page, moved);
+		rtree_branch_copy(to, br, tree->dimension);
 	}
-	return p;
+	new_page->n = k1;
+	page->n = k2;
+	return new_page;
 }
 
 static struct rtree_page*
 rtree_page_add_branch(struct rtree *tree, struct rtree_page *page,
 		      const struct rtree_page_branch *br)
 {
-	if (page->n < RTREE_MAX_FILL) {
-		page->b[page->n++] = *br;
+	if (page->n < tree->page_max_fill) {
+		struct rtree_page_branch *b;
+		b = rtree_get_branch(tree, page, page->n++);
+		rtree_branch_copy(b, br, tree->dimension);
 		return NULL;
 	} else {
 		return rtree_split_page(tree, page, br);
@@ -468,11 +572,15 @@ rtree_page_add_branch(struct rtree *tree, struct rtree_page *page,
 }
 
 static void
-rtree_page_remove_branch(struct rtree_page *page, int i)
+rtree_page_remove_branch(struct rtree *tree, struct rtree_page *page, int i)
 {
-	page->n -= 1;
-	memmove(page->b + i, page->b + i + 1,
-		(page->n - i) * sizeof(struct rtree_page_branch));
+	page->n--;
+	for (int j = i; j < page->n; j++) {
+		struct rtree_page_branch *to, *from;
+		to = rtree_get_branch(tree, page, j);
+		from = rtree_get_branch(tree, page, j + 1);
+		rtree_branch_copy(to, from, tree->dimension);
+	}
 }
 
 static struct rtree_page *
@@ -481,14 +589,20 @@ rtree_page_insert(struct rtree *tree, struct rtree_page *page,
 {
 	struct rtree_page_branch br;
 	if (--level != 0) {
-		/* not a leaf page */
+		/* not a leaf page, minize area increase */
 		int mini = -1;
-		area_t min_incr, best_area;
+		area_t min_incr = 0, best_area = 0;
 		for (int i = 0; i < page->n; i++) {
-			area_t r_area = rtree_rect_area(&page->b[i].rect);
-			struct rtree_rect cover =
-				rtree_rect_cover(&page->b[i].rect, rect);
-			area_t incr = rtree_rect_area(&cover) - r_area;
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(tree, page, i);
+			area_t r_area = rtree_rect_area(&b->rect,
+							tree->dimension);
+			struct rtree_rect cover;
+			rtree_rect_cover(&b->rect, rect,
+					 &cover, tree->dimension);
+			area_t incr = rtree_rect_area(&cover,
+						      tree->dimension);
+			incr -= r_area;
 			assert(incr >= 0);
 			if (i == 0) {
 				best_area = r_area;
@@ -498,29 +612,32 @@ rtree_page_insert(struct rtree *tree, struct rtree_page *page,
 				best_area = r_area;
 				min_incr = incr;
 				mini = i;
-			} else if (incr == min_incr && r_area < best_area) {
+			} else if (incr == min_incr &&
+				   r_area < best_area) {
 				best_area = r_area;
 				mini = i;
 			}
 		}
 		assert(mini >= 0);
-		struct rtree_page *p = page->b[mini].data.page;
+		struct rtree_page_branch *b;
+		b = rtree_get_branch(tree, page, mini);
+		struct rtree_page *p = b->data.page;
 		struct rtree_page *q = rtree_page_insert(tree, p,
 							 rect, obj, level);
 		if (q == NULL) {
 			/* child was not split */
-			rtree_rect_add(&page->b[mini].rect, rect);
+			rtree_rect_add(&b->rect, rect, tree->dimension);
 			return NULL;
 		} else {
 			/* child was split */
-			page->b[mini].rect = rtree_page_cover(p);
+			rtree_page_cover(tree, p, &b->rect);
 			br.data.page = q;
-			br.rect = rtree_page_cover(q);
+			rtree_page_cover(tree, q, &br.rect);
 			return rtree_page_add_branch(tree, page, &br);
 		}
 	} else {
 		br.data.record = obj;
-		br.rect = *rect;
+		rtree_rect_copy(&br.rect, rect, tree->dimension);
 		return rtree_page_add_branch(tree, page, &br);
 	}
 }
@@ -530,30 +647,35 @@ rtree_page_remove(struct rtree *tree, struct rtree_page *page,
 		  const struct rtree_rect *rect, record_t obj,
 		  int level, struct rtree_reinsert_list *rlist)
 {
+	unsigned d = tree->dimension;
 	if (--level != 0) {
-		for (int i = 0; i < page->n; i++) {
-			if (!rtree_rect_intersects_rect(&page->b[i].rect, rect))
+		for (unsigned i = 0; i < page->n; i++) {
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(tree, page, i);
+			if (!rtree_rect_intersects_rect(&b->rect, rect, d))
 				continue;
-			struct rtree_page *next_page = page->b[i].data.page;
+			struct rtree_page *next_page = b->data.page;
 			if (!rtree_page_remove(tree, next_page, rect,
 					       obj, level, rlist))
 				continue;
-			if (next_page->n >= RTREE_MIN_FILL) {
-				page->b[i].rect =
-					rtree_page_cover(next_page);
+			if (next_page->n >= tree->page_min_fill) {
+				rtree_page_cover(tree, next_page, &b->rect);
 			} else {
 				/* not enough entries in child */
-				set_next_reinsert_page(next_page, rlist->chain);
+				set_next_reinsert_page(tree, next_page,
+						       rlist->chain);
 				rlist->chain = next_page;
 				rlist->level = level - 1;
-				rtree_page_remove_branch(page, i);
+				rtree_page_remove_branch(tree, page, i);
 			}
 			return true;
 		}
 	} else {
-		for (int i = 0; i < page->n; i++) {
-			if (page->b[i].data.page == obj) {
-				rtree_page_remove_branch(page, i);
+		for (unsigned i = 0; i < page->n; i++) {
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(tree, page, i);
+			if (b->data.page == obj) {
+				rtree_page_remove_branch(tree, page, i);
 				return true;
 			}
 		}
@@ -565,8 +687,11 @@ static void
 rtree_page_purge(struct rtree *tree, struct rtree_page *page, int level)
 {
 	if (--level != 0) { /* this is an internal node in the tree */
-		for (int i = 0; i < page->n; i++)
-			rtree_page_purge(tree, page->b[i].data.page, level);
+		for (int i = 0; i < page->n; i++) {
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(tree, page, i);
+			rtree_page_purge(tree, b->data.page, level);
+		}
 	}
 	rtree_free_page(tree, page);
 }
@@ -576,11 +701,15 @@ rtree_page_purge(struct rtree *tree, struct rtree_page *page, int level)
 /*------------------------------------------------------------------------- */
 
 static bool
-rtree_iterator_goto_first(struct rtree_iterator *itr, int sp, struct rtree_page* pg)
+rtree_iterator_goto_first(struct rtree_iterator *itr, int sp,
+			  struct rtree_page* pg)
 {
+	unsigned d = itr->tree->dimension;
 	if (sp + 1 == itr->tree->height) {
 		for (int i = 0, n = pg->n; i < n; i++) {
-			if (itr->leaf_cmp(&itr->rect, &pg->b[i].rect)) {
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(itr->tree, pg, i);
+			if (itr->leaf_cmp(&itr->rect, &b->rect, d)) {
 				itr->stack[sp].page = pg;
 				itr->stack[sp].pos = i;
 				return true;
@@ -588,9 +717,11 @@ rtree_iterator_goto_first(struct rtree_iterator *itr, int sp, struct rtree_page*
 		}
 	} else {
 		for (int i = 0, n = pg->n; i < n; i++) {
-			if (itr->intr_cmp(&itr->rect, &pg->b[i].rect)
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(itr->tree, pg, i);
+			if (itr->intr_cmp(&itr->rect, &b->rect, d)
 			    && rtree_iterator_goto_first(itr, sp + 1,
-							 pg->b[i].data.page))
+							 b->data.page))
 			{
 				itr->stack[sp].page = pg;
 				itr->stack[sp].pos = i;
@@ -605,19 +736,24 @@ rtree_iterator_goto_first(struct rtree_iterator *itr, int sp, struct rtree_page*
 static bool
 rtree_iterator_goto_next(struct rtree_iterator *itr, int sp)
 {
+	unsigned d = itr->tree->dimension;
 	struct rtree_page *pg = itr->stack[sp].page;
 	if (sp + 1 == itr->tree->height) {
 		for (int i = itr->stack[sp].pos, n = pg->n; ++i < n;) {
-			if (itr->leaf_cmp(&itr->rect, &pg->b[i].rect)) {
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(itr->tree, pg, i);
+			if (itr->leaf_cmp(&itr->rect, &b->rect, d)) {
 				itr->stack[sp].pos = i;
 				return true;
 			}
 		}
 	} else {
 		for (int i = itr->stack[sp].pos, n = pg->n; ++i < n;) {
-			if (itr->intr_cmp(&itr->rect, &pg->b[i].rect)
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(itr->tree, pg, i);
+			if (itr->intr_cmp(&itr->rect, &b->rect, d)
 			    && rtree_iterator_goto_first(itr, sp + 1,
-							 pg->b[i].data.page))
+							 b->data.page))
 			{
 				itr->stack[sp].page = pg;
 				itr->stack[sp].pos = i;
@@ -638,26 +774,30 @@ rtree_iterator_destroy(struct rtree_iterator *itr)
 				(struct rtree_page *) curr);
 	}
 	itr->page_list = NULL;
-	itr->page_pos = RTREE_NEIGHBORS_IN_PAGE;
+	itr->page_pos = INT_MAX;
+}
+
+struct rtree_neighbor *
+rtree_iterator_reset_cb(rtnt_t *t, struct rtree_neighbor *n, void *d)
+{
+	(void) t;
+	struct rtree_iterator *itr = (struct rtree_iterator *)d;
+	n->next = itr->neigh_free_list;
+	itr->neigh_free_list = n;
+	return 0;
 }
 
 static void
 rtree_iterator_reset(struct rtree_iterator *itr)
 {
-	if (itr->neigh_list != NULL) {
-		struct rtree_neighbor **npp = &itr->neigh_free_list;
-		while (*npp != NULL) {
-			npp = &(*npp)->next;
-		}
-		*npp = itr->neigh_list;
-		itr->neigh_list = NULL;
-	}
+	rtnt_iter(&itr->neigh_tree, 0, rtree_iterator_reset_cb, (void *)itr);
+	rtnt_new(&itr->neigh_tree);
 }
 
 static struct rtree_neighbor *
 rtree_iterator_allocate_neighbour(struct rtree_iterator *itr)
 {
-	if (itr->page_pos >= RTREE_NEIGHBORS_IN_PAGE) {
+	if (itr->page_pos >= itr->tree->neighbours_in_page) {
 		struct rtree_neighbor_page *new_page =
 			(struct rtree_neighbor_page *)
 			rtree_alloc_page((struct rtree*)itr->tree);
@@ -680,7 +820,6 @@ rtree_iterator_new_neighbor(struct rtree_iterator *itr,
 	n->child = child;
 	n->distance = distance;
 	n->level = level;
-	n->next = NULL;
 	return n;
 }
 
@@ -696,29 +835,34 @@ void
 rtree_iterator_init(struct rtree_iterator *itr)
 {
 	itr->tree = 0;
-	itr->neigh_list = NULL;
+	rtnt_new(&itr->neigh_tree);
 	itr->neigh_free_list = NULL;
 	itr->page_list = NULL;
-	itr->page_pos = RTREE_NEIGHBORS_IN_PAGE;
+	itr->page_pos = INT_MAX;
 }
 
 static void
-rtree_iterator_insert_neighbor(struct rtree_iterator *itr,
-			       struct rtree_neighbor *node)
-{
-	struct rtree_neighbor *prev = NULL, *next = itr->neigh_list;
-	sq_coord_t distance = node->distance;
-	while (next != NULL && next->distance < distance) {
-		prev = next;
-		next = prev->next;
-	}
-	node->next = next;
-	if (prev == NULL)
-		itr->neigh_list = node;
-	else
-		prev->next = node;
+rtree_iterator_process_neigh(struct rtree_iterator *itr,
+			     struct rtree_neighbor *neighbor)
+{
+	unsigned d = itr->tree->dimension;
+	void *child = neighbor->child;
+	struct rtree_page *pg = (struct rtree_page *)child;
+	int level = neighbor->level;
+	rtree_iterator_free_neighbor(itr, neighbor);
+	for (int i = 0, n = pg->n; i < n; i++) {
+		struct rtree_page_branch *b;
+		b = rtree_get_branch(itr->tree, pg, i);
+		coord_t distance =
+			rtree_rect_neigh_distance2(&b->rect, &itr->rect, d);
+		struct rtree_neighbor *neigh =
+			rtree_iterator_new_neighbor(itr, b->data.page,
+						    distance, level - 1);
+		rtnt_insert(&itr->neigh_tree, neigh);
+	}
 }
 
+
 record_t
 rtree_iterator_next(struct rtree_iterator *itr)
 {
@@ -742,32 +886,27 @@ rtree_iterator_next(struct rtree_iterator *itr)
 		 *      page and insert them in sorted list
 		*/
 		while (true) {
-			struct rtree_neighbor *neighbor = itr->neigh_list;
+			struct rtree_neighbor *neighbor =
+				rtnt_first(&itr->neigh_tree);
 			if (neighbor == NULL)
 				return NULL;
-			void *child = neighbor->child;
-			int level = neighbor->level;
-			itr->neigh_list = neighbor->next;
-			rtree_iterator_free_neighbor(itr, neighbor);
-			if (level == 0)
+			rtnt_remove(&itr->neigh_tree, neighbor);
+			if (neighbor->level == 0) {
+				void *child = neighbor->child;
+				rtree_iterator_free_neighbor(itr, neighbor);
 				return (record_t)child;
-			struct rtree_page *pg = (struct rtree_page *)child;
-			for (int i = 0, n = pg->n; i < n; i++) {
-				struct rtree_page *pg =
-					(struct rtree_page *)child;
-				coord_t distance =
-					rtree_rect_point_distance2(&pg->b[i].rect,
-								   &itr->rect.lower_point);
-				struct rtree_neighbor *neigh =
-					rtree_iterator_new_neighbor(itr, pg->b[i].data.page,
-								    distance, level - 1);
-				rtree_iterator_insert_neighbor(itr, neigh);
+			} else {
+				rtree_iterator_process_neigh(itr, neighbor);
 			}
 		}
 	}
 	int sp = itr->tree->height - 1;
-	if (!itr->eof && rtree_iterator_goto_next(itr, sp))
-		return itr->stack[sp].page->b[itr->stack[sp].pos].data.record;
+	if (!itr->eof && rtree_iterator_goto_next(itr, sp)) {
+		struct rtree_page_branch *b;
+		b = rtree_get_branch(itr->tree,
+				     itr->stack[sp].page, itr->stack[sp].pos);
+		return b->data.record;
+	}
 	itr->eof = true;
 	return NULL;
 }
@@ -776,8 +915,8 @@ rtree_iterator_next(struct rtree_iterator *itr)
 /* R-tree methods */
 /*------------------------------------------------------------------------- */
 
-void
-rtree_init(struct rtree *tree, uint32_t extent_size,
+int
+rtree_init(struct rtree *tree, unsigned dimension, uint32_t extent_size,
 	   rtree_extent_alloc_t extent_alloc, rtree_extent_free_t extent_free)
 {
 	tree->n_records = 0;
@@ -785,9 +924,27 @@ rtree_init(struct rtree *tree, uint32_t extent_size,
 	tree->root = NULL;
 	tree->version = 0;
 	tree->n_pages = 0;
-	matras_create(&tree->mtab, extent_size, RTREE_PAGE_SIZE,
-		      extent_alloc, extent_free);
 	tree->free_pages = 0;
+
+	tree->dimension = dimension;
+	tree->page_branch_size =
+		(RTREE_BRANCH_DATA_SIZE + dimension * 2 * sizeof(coord_t));
+	tree->page_size = RTREE_OPTIMAL_BRANCHES_IN_PAGE *
+		tree->page_branch_size + sizeof(int);
+	/* round up to closest power of 2 */
+	int lz = __builtin_clz(tree->page_size - 1);
+	tree->page_size = 1u << (sizeof(int) * CHAR_BIT - lz);
+	assert(tree->page_size - sizeof(int) >=
+	       tree->page_branch_size * RTREE_OPTIMAL_BRANCHES_IN_PAGE);
+	tree->page_max_fill = (tree->page_size - sizeof(int)) /
+		tree->page_branch_size;
+	tree->page_min_fill = tree->page_max_fill * 2 / 5;
+	tree->neighbours_in_page = (tree->page_size - sizeof(void *))
+		/ sizeof(struct rtree_neighbor);
+
+	matras_create(&tree->mtab, extent_size, tree->page_size,
+		      extent_alloc, extent_free);
+	return 0;
 }
 
 void
@@ -802,7 +959,7 @@ rtree_insert(struct rtree *tree, struct rtree_rect *rect, record_t obj)
 {
 	if (tree->root == NULL) {
 		tree->root = rtree_alloc_page(tree);
-		rtree_page_init_with_record(tree->root, rect, obj);
+		rtree_page_init_with_record(tree, tree->root, rect, obj);
 		tree->height = 1;
 		tree->n_pages++;
 	} else {
@@ -811,7 +968,8 @@ rtree_insert(struct rtree *tree, struct rtree_rect *rect, record_t obj)
 		if (p != NULL) {
 			/* root splitted */
 			struct rtree_page *new_root = rtree_alloc_page(tree);
-			rtree_page_init_with_pages(new_root, tree->root, p);
+			rtree_page_init_with_pages(tree, new_root,
+						   tree->root, p);
 			tree->root = new_root;
 			tree->height++;
 			tree->n_pages++;
@@ -821,7 +979,6 @@ rtree_insert(struct rtree *tree, struct rtree_rect *rect, record_t obj)
 	tree->n_records++;
 }
 
-
 bool
 rtree_remove(struct rtree *tree, const struct rtree_rect *rect, record_t obj)
 {
@@ -835,16 +992,17 @@ rtree_remove(struct rtree *tree, const struct rtree_rect *rect, record_t obj)
 	int level = rlist.level;
 	while (pg != NULL) {
 		for (int i = 0, n = pg->n; i < n; i++) {
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(tree, pg, i);
 			struct rtree_page *p =
 				rtree_page_insert(tree, tree->root,
-						  &pg->b[i].rect,
-						  pg->b[i].data.record,
+						  &b->rect, b->data.record,
 						  tree->height - level);
 			if (p != NULL) {
 				/* root splitted */
 				struct rtree_page *new_root
 					= rtree_alloc_page(tree);
-				rtree_page_init_with_pages(new_root,
+				rtree_page_init_with_pages(tree, new_root,
 							   tree->root, p);
 				tree->root = new_root;
 				tree->height++;
@@ -852,13 +1010,15 @@ rtree_remove(struct rtree *tree, const struct rtree_rect *rect, record_t obj)
 			}
 		}
 		level--;
-		struct rtree_page *next = get_next_reinsert_page(pg);
+		struct rtree_page *next = get_next_reinsert_page(tree, pg);
 		rtree_free_page(tree, pg);
 		tree->n_pages--;
 		pg = next;
 	}
 	if (tree->root->n == 1 && tree->height > 1) {
-		struct rtree_page *new_root = tree->root->b[0].data.page;
+		struct rtree_page_branch *b;
+		b = rtree_get_branch(tree, tree->root, 0);
+		struct rtree_page *new_root = b->data.page;
 		rtree_free_page(tree, tree->root);
 		tree->root = new_root;
 		tree->height--;
@@ -877,7 +1037,7 @@ rtree_search(const struct rtree *tree, const struct rtree_rect *rect,
 	assert(itr->tree == 0 || itr->tree == tree);
 	itr->tree = tree;
 	itr->version = tree->version;
-	itr->rect = *rect;
+	rtree_rect_copy(&itr->rect, rect, tree->dimension);
 	itr->op = op;
 	assert(tree->height <= RTREE_MAX_HEIGHT);
 	switch (op) {
@@ -907,17 +1067,18 @@ rtree_search(const struct rtree *tree, const struct rtree_rect *rect,
 		break;
 	case SOP_NEIGHBOR:
 		if (tree->root) {
-			struct rtree_rect cover = rtree_page_cover(tree->root);
+			struct rtree_rect cover;
+			rtree_page_cover(tree, tree->root, &cover);
 			sq_coord_t distance =
-				rtree_rect_point_distance2(&cover,
-							   &rect->lower_point);
-			itr->neigh_list =
+				rtree_rect_neigh_distance2(&cover, rect,
+							   tree->dimension);
+			struct rtree_neighbor *n =
 				rtree_iterator_new_neighbor(itr, tree->root,
 							    distance,
 							    tree->height);
+			rtnt_insert(&itr->neigh_tree, n);
 			return true;
 		} else {
-			itr->neigh_list = NULL;
 			return false;
 		}
 	}
@@ -947,7 +1108,7 @@ rtree_purge(struct rtree *tree)
 size_t
 rtree_used_size(const struct rtree *tree)
 {
-	return tree->n_pages * RTREE_PAGE_SIZE;
+	return tree->n_pages * tree->page_size;
 }
 
 unsigned
@@ -956,32 +1117,32 @@ rtree_number_of_records(const struct rtree *tree) {
 }
 
 #if 0
+#include <stdio.h>
 void
-rtree_debug_print_page(const struct rtree_page *page, unsigned level, unsigned path)
+rtree_debug_print_page(const struct rtree *tree, const struct rtree_page *page,
+		       unsigned level, unsigned path)
 {
-	printf("%d:", path);
-	if (--level) {
-		for (int i = 0; i < page->n; i++) {
-			printf(" [");
-			for (int j = 0; j < RTREE_DIMENSION; j++)
-				printf("%lg ", (double)page->b[i].rect.lower_point.coords[j]);
-			for (int j = 0; j < RTREE_DIMENSION; j++)
-				printf("%lg ", (double)page->b[i].rect.upper_point.coords[j]);
-			printf("]");
+	printf("%d:\n", path);
+	unsigned d = tree->dimension;
+	for (int i = 0; i < page->n; i++) {
+		struct rtree_page_branch *b;
+		b = rtree_get_branch(tree, page, i);
+		double v = 1;
+		for (unsigned j = 0; j < d; j++) {
+			double d1 = b->rect.coords[j * 2];
+			double d2 = b->rect.coords[j * 2 + 1];
+			v *= (d2 - d1) / 100;
+			printf("[%04.1lf-%04.1lf:%04.1lf]", d2, d1, d2 - d1);
 		}
-		printf("\n");
-		for (int i = 0; i < page->n; i++)
-			rtree_debug_print_page(page->b[i].data.page, level, path * 100 + i);
-	} else {
+		printf("%d\n", (int)(v * 100));
+	}
+	if (--level > 1) {
 		for (int i = 0; i < page->n; i++) {
-			printf(" [");
-			for (int j = 0; j < RTREE_DIMENSION; j++)
-				printf("%lg ", (double)page->b[i].rect.lower_point.coords[j]);
-			for (int j = 0; j < RTREE_DIMENSION; j++)
-				printf("%lg ", (double)page->b[i].rect.upper_point.coords[j]);
-			printf(": %p]", (void *)page->b[i].data.record);
+			struct rtree_page_branch *b;
+			b = rtree_get_branch(tree, page, i);
+			rtree_debug_print_page(tree, b->data.page, level,
+					       path * 100 + i + 1);
 		}
-		printf("\n");
 	}
 }
 
@@ -989,7 +1150,7 @@ void
 rtree_debug_print(const struct rtree *tree)
 {
 	if (tree->root)
-		rtree_debug_print_page(tree->root, tree->height, 0);
+		rtree_debug_print_page(tree, tree->root, tree->height, 1);
 }
 #endif
 
diff --git a/src/lib/salad/rtree.h b/src/lib/salad/rtree.h
index 4d1f4c2e28..a1f5ba5025 100644
--- a/src/lib/salad/rtree.h
+++ b/src/lib/salad/rtree.h
@@ -32,6 +32,9 @@
 #include <stdbool.h>
 #include "small/matras.h"
 
+#define RB_COMPACT 1
+#include "third_party/rb.h"
+
 /**
  * In-memory Guttman's R-tree
  */
@@ -49,17 +52,21 @@ typedef double area_t;
 extern "C" {
 #endif /* defined(__cplusplus) */
 
+struct rtree_neighbor {
+	rb_node(struct rtree_neighbor) link;
+	struct rtree_neighbor *next;
+	void *child;
+	int level;
+	sq_coord_t distance;
+};
+
+typedef rb_tree(struct rtree_neighbor) rtnt_t;
+
 enum {
-	/** Number of dimensions of R-tree geometry */
-	RTREE_DIMENSION = 2,
 	/** Maximal possible R-tree height */
 	RTREE_MAX_HEIGHT = 16,
-	/**
-	 * R-Tree uses linear search for elements on a page,
-	 * so a larger page size can hurt performance.
-	 * must be power of 2
-	 */
-	RTREE_PAGE_SIZE = 1024
+	/** Maximal possible R-tree height */
+	RTREE_MAX_DIMENSION = 20
 };
 
 /**
@@ -93,31 +100,36 @@ enum spatial_search_op
 typedef void *(*rtree_extent_alloc_t)();
 typedef void (*rtree_extent_free_t)(void *);
 
-/* A point in RTREE_DIMENSION space */
-struct rtree_point
-{
-	/* coordinates of the point */
-	coord_t coords[RTREE_DIMENSION];
-};
-
 /* A box in RTREE_DIMENSION space */
 struct rtree_rect
 {
-	/* vertex with minimal coordinates */
-	struct rtree_point lower_point;
-	/* vertex with maximal coordinates (diagonal to lower_point) */
-	struct rtree_point upper_point;
+	/* coords: { low X, upper X, low Y, upper Y, etc } */
+	coord_t coords[RTREE_MAX_DIMENSION * 2];
 };
 
 /* Type of function, comparing two rectangles */
 typedef bool (*rtree_comparator_t)(const struct rtree_rect *rt1,
-				   const struct rtree_rect *rt2);
+				   const struct rtree_rect *rt2,
+				   unsigned dimension);
 
 /* Main rtree struct */
 struct rtree
 {
 	/* Root node (page) */
 	struct rtree_page *root;
+	/* R-tree dimension */
+	unsigned dimension;
+	/* Minimal number of branches in tree page */
+	unsigned page_min_fill;
+	/* Maximal number of branches in tree page */
+	unsigned page_max_fill;
+	/* Page size in bytes */
+	unsigned page_size;
+	/* Page branch size in bytes */
+	unsigned page_branch_size;
+	/* For iterator usage, pages are splitted into structs neighbours
+	 * Here is number of neighbours fit into one page */
+	unsigned neighbours_in_page;
 	/* Number of records in entire tree */
 	unsigned n_records;
 	/* Height of a tree */
@@ -146,13 +158,13 @@ struct rtree_iterator
 	/* A verion of a tree when the iterator was created */
 	int version;
 
-	/* Special single-linked list of closest neqighbors
+	/* Special rb tree of closest neqighbors
 	 * Used only for iteration with op = SOP_NEIGHBOR
 	 * For allocating list entries, page allocator of tree is used.
 	 * Allocated page is much bigger than list entry and thus
 	 * provides several list entries.
 	 */
-	struct rtree_neighbor *neigh_list;
+	rtnt_t neigh_tree;
 	/* List of unused (deleted) list entries */
 	struct rtree_neighbor *neigh_free_list;
 	/* List of tree pages, allocated for list entries */
@@ -184,7 +196,7 @@ struct rtree_iterator
  * @param rect - pointer to a rectangle
  */
 void
-rtree_rect_normalize(struct rtree_rect *rect);
+rtree_rect_normalize(struct rtree_rect *rect, unsigned dimension);
 
 /**
  * @brief Set up 2D rectangle by 4 coordinates
@@ -195,15 +207,24 @@ void
 rtree_set2d(struct rtree_rect *rect,
 	    coord_t left, coord_t bottom, coord_t right, coord_t top);
 
+/**
+ * @brief Set up 2D rectangle by 2 coordinates (set to point)
+ * @param rect - pointer to a rectangle
+ * @params x, y - corresponding coordinates
+ */
+void
+rtree_set2dp(struct rtree_rect *rect, coord_t x, coord_t y);
+
 /**
  * @brief Initialize a tree
  * @param tree - pointer to a tree
  * @param extent_size - size of extents allocated by extent_alloc (see next)
  * @param extent_alloc - extent allocation function
  * @param extent_free - extent deallocation function
+ * @return 0 on success, -1 on error
  */
-void
-rtree_init(struct rtree *tree, uint32_t extent_size,
+int
+rtree_init(struct rtree *tree, unsigned dimension, uint32_t extent_size,
 	   rtree_extent_alloc_t extent_alloc, rtree_extent_free_t extent_free);
 
 /**
diff --git a/test/box/misc.result b/test/box/misc.result
index 8ff8f3bc9e..7a3030305d 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -257,6 +257,7 @@ t;
   - 'box.error.DROP_USER : 44'
   - 'box.error.CROSS_ENGINE_TRANSACTION : 81'
   - 'box.error.injection : table: <address>
+  - 'box.error.RTREE_RECT_ERROR : 101'
   - 'box.error.FUNCTION_LANGUAGE : 100'
   - 'box.error.MODIFY_INDEX : 14'
   - 'box.error.TUPLE_FOUND : 3'
diff --git a/test/box/rtree_array.result b/test/box/rtree_array.result
index 659b7905c4..18925aa6f7 100644
--- a/test/box/rtree_array.result
+++ b/test/box/rtree_array.result
@@ -76,16 +76,191 @@ s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
 -- select neighbors of point (5,5)
 s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
 ---
-- - [6, [10, 10]]
-  - [4, [10, 0]]
+- - [1, [0, 0]]
   - [2, [0, 10]]
-  - [1, [0, 0]]
-  - [8, [50, 10]]
-  - [7, [10, 50]]
-  - [5, [50, 0]]
+  - [4, [10, 0]]
+  - [6, [10, 10]]
   - [3, [0, 50]]
+  - [5, [50, 0]]
+  - [7, [10, 50]]
+  - [8, [50, 10]]
   - [9, [50, 50]]
 ...
 s:drop()
 ---
 ...
+s = box.schema.space.create('spatial')
+---
+...
+_ = s:create_index('primary')
+---
+...
+spatial = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}, dimension = 8})
+---
+...
+spatial.type
+---
+- RTREE
+...
+s:insert{ 1,{0, 0, 0, 0, 0, 0, 0, 0}}
+---
+- [1, [0, 0, 0, 0, 0, 0, 0, 0]]
+...
+s:insert{ 2,{10, 0, 0, 0, 0, 0, 0, 0}}
+---
+- [2, [10, 0, 0, 0, 0, 0, 0, 0]]
+...
+s:insert{ 3,{0, 10, 0, 0, 0, 0, 0, 0}}
+---
+- [3, [0, 10, 0, 0, 0, 0, 0, 0]]
+...
+s:insert{ 4,{0, 0, 10, 0, 0, 0, 0, 0}}
+---
+- [4, [0, 0, 10, 0, 0, 0, 0, 0]]
+...
+s:insert{ 5,{0, 0, 0, 10, 0, 0, 0, 0}}
+---
+- [5, [0, 0, 0, 10, 0, 0, 0, 0]]
+...
+s:insert{ 6,{0, 0, 0, 0, 10, 0, 0, 0}}
+---
+- [6, [0, 0, 0, 0, 10, 0, 0, 0]]
+...
+s:insert{ 7,{0, 0, 0, 0, 0, 10, 0, 0}}
+---
+- [7, [0, 0, 0, 0, 0, 10, 0, 0]]
+...
+s:insert{ 8,{0, 0, 0, 0, 0, 0, 10, 0}}
+---
+- [8, [0, 0, 0, 0, 0, 0, 10, 0]]
+...
+s:insert{ 9,{0, 0, 0, 0, 0, 0, 0, 10}}
+---
+- [9, [0, 0, 0, 0, 0, 0, 0, 10]]
+...
+s:insert{10,{50, 0, 0, 0, 0, 0, 0, 0}}
+---
+- [10, [50, 0, 0, 0, 0, 0, 0, 0]]
+...
+s:insert{11,{0, 50, 0, 0, 0, 0, 0, 0}}
+---
+- [11, [0, 50, 0, 0, 0, 0, 0, 0]]
+...
+s:insert{12,{0, 0, 50, 0, 0, 0, 0, 0}}
+---
+- [12, [0, 0, 50, 0, 0, 0, 0, 0]]
+...
+s:insert{13,{0, 0, 0, 50, 0, 0, 0, 0}}
+---
+- [13, [0, 0, 0, 50, 0, 0, 0, 0]]
+...
+s:insert{14,{0, 0, 0, 0, 50, 0, 0, 0}}
+---
+- [14, [0, 0, 0, 0, 50, 0, 0, 0]]
+...
+s:insert{15,{0, 0, 0, 0, 0, 50, 0, 0}}
+---
+- [15, [0, 0, 0, 0, 0, 50, 0, 0]]
+...
+s:insert{16,{0, 0, 0, 0, 0, 0, 50, 0}}
+---
+- [16, [0, 0, 0, 0, 0, 0, 50, 0]]
+...
+s:insert{17,{0, 0, 0, 0, 0, 0, 0, 50}}
+---
+- [17, [0, 0, 0, 0, 0, 0, 0, 50]]
+...
+s:insert{18,{10, 10, 10, 10, 10, 10, 10, 10}}
+---
+- [18, [10, 10, 10, 10, 10, 10, 10, 10]]
+...
+s:insert{19,{10, 50, 10, 50, 10, 50, 10, 50}}
+---
+- [19, [10, 50, 10, 50, 10, 50, 10, 50]]
+...
+s:insert{20,{0, 10, 50, 0, 10, 50, 0, 10}}
+---
+- [20, [0, 10, 50, 0, 10, 50, 0, 10]]
+...
+p0 = {0, 0, 0, 0, 0, 0, 0, 0}
+---
+...
+p5 = {5, 5, 5, 5, 5, 5, 5, 5}
+---
+...
+p10 = {10, 10, 10, 10, 10, 10, 10, 10 }
+---
+...
+rt0_10 = {0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10 }
+---
+...
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+---
+- - [1, [0, 0, 0, 0, 0, 0, 0, 0]]
+  - [2, [10, 0, 0, 0, 0, 0, 0, 0]]
+  - [3, [0, 10, 0, 0, 0, 0, 0, 0]]
+  - [4, [0, 0, 10, 0, 0, 0, 0, 0]]
+  - [5, [0, 0, 0, 10, 0, 0, 0, 0]]
+  - [6, [0, 0, 0, 0, 10, 0, 0, 0]]
+  - [7, [0, 0, 0, 0, 0, 10, 0, 0]]
+  - [8, [0, 0, 0, 0, 0, 0, 10, 0]]
+  - [9, [0, 0, 0, 0, 0, 0, 0, 10]]
+  - [10, [50, 0, 0, 0, 0, 0, 0, 0]]
+  - [11, [0, 50, 0, 0, 0, 0, 0, 0]]
+  - [12, [0, 0, 50, 0, 0, 0, 0, 0]]
+  - [13, [0, 0, 0, 50, 0, 0, 0, 0]]
+  - [14, [0, 0, 0, 0, 50, 0, 0, 0]]
+  - [15, [0, 0, 0, 0, 0, 50, 0, 0]]
+  - [16, [0, 0, 0, 0, 0, 0, 50, 0]]
+  - [17, [0, 0, 0, 0, 0, 0, 0, 50]]
+  - [18, [10, 10, 10, 10, 10, 10, 10, 10]]
+  - [19, [10, 50, 10, 50, 10, 50, 10, 50]]
+  - [20, [0, 10, 50, 0, 10, 50, 0, 10]]
+...
+-- select records belonging to rectangle (0,0,..10,10,..)
+s.index.spatial:select(rt0_10, {iterator = 'LE'})
+---
+- - [1, [0, 0, 0, 0, 0, 0, 0, 0]]
+  - [2, [10, 0, 0, 0, 0, 0, 0, 0]]
+  - [3, [0, 10, 0, 0, 0, 0, 0, 0]]
+  - [4, [0, 0, 10, 0, 0, 0, 0, 0]]
+  - [5, [0, 0, 0, 10, 0, 0, 0, 0]]
+  - [6, [0, 0, 0, 0, 10, 0, 0, 0]]
+  - [7, [0, 0, 0, 0, 0, 10, 0, 0]]
+  - [8, [0, 0, 0, 0, 0, 0, 10, 0]]
+  - [9, [0, 0, 0, 0, 0, 0, 0, 10]]
+  - [18, [10, 10, 10, 10, 10, 10, 10, 10]]
+...
+-- select records with coordinates (10,10)
+s.index.spatial:select(p10, {iterator = 'EQ'})
+---
+- - [18, [10, 10, 10, 10, 10, 10, 10, 10]]
+...
+-- select neighbors of point (5,5)
+s.index.spatial:select(p5, {iterator = 'NEIGHBOR'})
+---
+- - [1, [0, 0, 0, 0, 0, 0, 0, 0]]
+  - [2, [10, 0, 0, 0, 0, 0, 0, 0]]
+  - [3, [0, 10, 0, 0, 0, 0, 0, 0]]
+  - [4, [0, 0, 10, 0, 0, 0, 0, 0]]
+  - [5, [0, 0, 0, 10, 0, 0, 0, 0]]
+  - [6, [0, 0, 0, 0, 10, 0, 0, 0]]
+  - [7, [0, 0, 0, 0, 0, 10, 0, 0]]
+  - [8, [0, 0, 0, 0, 0, 0, 10, 0]]
+  - [9, [0, 0, 0, 0, 0, 0, 0, 10]]
+  - [18, [10, 10, 10, 10, 10, 10, 10, 10]]
+  - [10, [50, 0, 0, 0, 0, 0, 0, 0]]
+  - [11, [0, 50, 0, 0, 0, 0, 0, 0]]
+  - [12, [0, 0, 50, 0, 0, 0, 0, 0]]
+  - [13, [0, 0, 0, 50, 0, 0, 0, 0]]
+  - [14, [0, 0, 0, 0, 50, 0, 0, 0]]
+  - [15, [0, 0, 0, 0, 0, 50, 0, 0]]
+  - [16, [0, 0, 0, 0, 0, 0, 50, 0]]
+  - [17, [0, 0, 0, 0, 0, 0, 0, 50]]
+  - [20, [0, 10, 50, 0, 10, 50, 0, 10]]
+  - [19, [10, 50, 10, 50, 10, 50, 10, 50]]
+...
+s:drop()
+---
+...
diff --git a/test/box/rtree_array.test.lua b/test/box/rtree_array.test.lua
index 19897b1d3a..f6a0785baf 100644
--- a/test/box/rtree_array.test.lua
+++ b/test/box/rtree_array.test.lua
@@ -24,3 +24,46 @@ s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
 s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
 
 s:drop()
+
+s = box.schema.space.create('spatial')
+_ = s:create_index('primary')
+spatial = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}, dimension = 8})
+
+spatial.type
+
+s:insert{ 1,{0, 0, 0, 0, 0, 0, 0, 0}}
+s:insert{ 2,{10, 0, 0, 0, 0, 0, 0, 0}}
+s:insert{ 3,{0, 10, 0, 0, 0, 0, 0, 0}}
+s:insert{ 4,{0, 0, 10, 0, 0, 0, 0, 0}}
+s:insert{ 5,{0, 0, 0, 10, 0, 0, 0, 0}}
+s:insert{ 6,{0, 0, 0, 0, 10, 0, 0, 0}}
+s:insert{ 7,{0, 0, 0, 0, 0, 10, 0, 0}}
+s:insert{ 8,{0, 0, 0, 0, 0, 0, 10, 0}}
+s:insert{ 9,{0, 0, 0, 0, 0, 0, 0, 10}}
+s:insert{10,{50, 0, 0, 0, 0, 0, 0, 0}}
+s:insert{11,{0, 50, 0, 0, 0, 0, 0, 0}}
+s:insert{12,{0, 0, 50, 0, 0, 0, 0, 0}}
+s:insert{13,{0, 0, 0, 50, 0, 0, 0, 0}}
+s:insert{14,{0, 0, 0, 0, 50, 0, 0, 0}}
+s:insert{15,{0, 0, 0, 0, 0, 50, 0, 0}}
+s:insert{16,{0, 0, 0, 0, 0, 0, 50, 0}}
+s:insert{17,{0, 0, 0, 0, 0, 0, 0, 50}}
+s:insert{18,{10, 10, 10, 10, 10, 10, 10, 10}}
+s:insert{19,{10, 50, 10, 50, 10, 50, 10, 50}}
+s:insert{20,{0, 10, 50, 0, 10, 50, 0, 10}}
+
+p0 = {0, 0, 0, 0, 0, 0, 0, 0}
+p5 = {5, 5, 5, 5, 5, 5, 5, 5}
+p10 = {10, 10, 10, 10, 10, 10, 10, 10 }
+rt0_10 = {0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 10, 10, 10, 10, 10 }
+
+-- select all records
+s.index.spatial:select({iterator = 'ALL'})
+-- select records belonging to rectangle (0,0,..10,10,..)
+s.index.spatial:select(rt0_10, {iterator = 'LE'})
+-- select records with coordinates (10,10)
+s.index.spatial:select(p10, {iterator = 'EQ'})
+-- select neighbors of point (5,5)
+s.index.spatial:select(p5, {iterator = 'NEIGHBOR'})
+
+s:drop()
diff --git a/test/box/rtree_misc.result b/test/box/rtree_misc.result
index f356dac885..9b5183d5fd 100644
--- a/test/box/rtree_misc.result
+++ b/test/box/rtree_misc.result
@@ -93,8 +93,8 @@ s:insert{1, 2, nil, 3}
 ...
 s:insert{1, 2, {}}
 ---
-- error: R-Tree index does not support Key should contain 2 (point) or 4 (rectangle)
-    coordinates
+- error: 'RTree: Field must be an array with 2 (point) or 4 (rectangle/box) numeric
+    coordinates'
 ...
 s:insert{1, 2, {"3", "4", "5", "6"}}
 ---
@@ -118,13 +118,13 @@ s:insert{1, 2, {3, 4, 5, "6"}}
 ...
 s:insert{1, 2, {3}}
 ---
-- error: R-Tree index does not support Field should be array with size 2 (point) or
-    4 (rectangle)
+- error: 'RTree: Field must be an array with 2 (point) or 4 (rectangle/box) numeric
+    coordinates'
 ...
 s:insert{1, 2, {3, 4, 5}}
 ---
-- error: R-Tree index does not support Key should contain 2 (point) or 4 (rectangle)
-    coordinates
+- error: 'RTree: Field must be an array with 2 (point) or 4 (rectangle/box) numeric
+    coordinates'
 ...
 -- inserting good value
 s:insert{1, 2, {3, 4, 5, 6}}
diff --git a/test/box/rtree_point.result b/test/box/rtree_point.result
index 69c8214de1..6591342f32 100644
--- a/test/box/rtree_point.result
+++ b/test/box/rtree_point.result
@@ -72,14 +72,14 @@ s.index.spatial:select({10,10}, {iterator = 'EQ'})
 -- select neighbors of point (5,5)
 s.index.spatial:select({5,5}, {iterator = 'NEIGHBOR'})
 ---
-- - [6, [10, 10]]
-  - [4, [10, 0]]
+- - [1, [0, 0]]
   - [2, [0, 10]]
-  - [1, [0, 0]]
-  - [8, [50, 10]]
-  - [7, [10, 50]]
-  - [5, [50, 0]]
+  - [4, [10, 0]]
+  - [6, [10, 10]]
   - [3, [0, 50]]
+  - [5, [50, 0]]
+  - [7, [10, 50]]
+  - [8, [50, 10]]
   - [9, [50, 50]]
 ...
 s:drop()
diff --git a/test/box/rtree_point_r2.result b/test/box/rtree_point_r2.result
index 238d0d9cd0..ce89fac9fd 100644
--- a/test/box/rtree_point_r2.result
+++ b/test/box/rtree_point_r2.result
@@ -72,14 +72,14 @@ s.index.spatial:select({10.0,10.0}, {iterator = 'EQ'})
 -- select neighbors of point (5,5)
 s.index.spatial:select({5.0,5.0}, {iterator = 'NEIGHBOR'})
 ---
-- - [6, [10, 10]]
-  - [4, [10, 0]]
+- - [1, [0, 0]]
   - [2, [0, 10]]
-  - [1, [0, 0]]
-  - [8, [50, 10]]
-  - [7, [10, 50]]
-  - [5, [50, 0]]
+  - [4, [10, 0]]
+  - [6, [10, 10]]
   - [3, [0, 50]]
+  - [5, [50, 0]]
+  - [7, [10, 50]]
+  - [8, [50, 10]]
   - [9, [50, 50]]
 ...
 s:drop()
diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt
index 6136e80c21..b09b15f253 100644
--- a/test/unit/CMakeLists.txt
+++ b/test/unit/CMakeLists.txt
@@ -43,14 +43,16 @@ add_executable(slab_arena.test slab_arena.c)
 target_link_libraries(slab_arena.test small)
 add_executable(arena_mt.test arena_mt.c)
 target_link_libraries(arena_mt.test small pthread)
-add_executable(bps_tree.test bps_tree.cc ${CMAKE_SOURCE_DIR}/third_party/qsort_arg.c)
-target_link_libraries(bps_tree.test small)
-add_executable(bps_tree_itr.test bps_tree_itr.cc ${CMAKE_SOURCE_DIR}/third_party/qsort_arg.c)
-target_link_libraries(bps_tree_itr.test small)
-add_executable(rtree.test rtree.cc ${CMAKE_SOURCE_DIR}/src/lib/salad/rtree.c)
-target_link_libraries(rtree.test small)
-add_executable(rtree_itr.test rtree_itr.cc ${CMAKE_SOURCE_DIR}/src/lib/salad/rtree.c)
-target_link_libraries(rtree_itr.test small)
+add_executable(bps_tree.test bps_tree.cc)
+target_link_libraries(bps_tree.test small misc)
+add_executable(bps_tree_itr.test bps_tree_itr.cc)
+target_link_libraries(bps_tree_itr.test small misc)
+add_executable(rtree.test rtree.cc)
+target_link_libraries(rtree.test salad small)
+add_executable(rtree_itr.test rtree_itr.cc)
+target_link_libraries(rtree_itr.test salad small)
+add_executable(rtree_multidim.test rtree_multidim.cc)
+target_link_libraries(rtree_multidim.test salad small)
 add_executable(matras.test matras.cc)
 target_link_libraries(matras.test small)
 add_executable(light.test light.cc)
diff --git a/test/unit/rtree.cc b/test/unit/rtree.cc
index e8836deb9d..3469fe2d1e 100644
--- a/test/unit/rtree.cc
+++ b/test/unit/rtree.cc
@@ -9,7 +9,7 @@
 
 static int page_count = 0;
 
-const uint32_t extent_size = RTREE_PAGE_SIZE * 8;
+const uint32_t extent_size = 1024 * 8;
 
 static void *
 extent_alloc()
@@ -36,16 +36,13 @@ simple_check()
 	header();
 
 	struct rtree tree;
-	rtree_init(&tree, extent_size, extent_alloc, extent_free);
+	rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
 
 	printf("Insert 1..X, remove 1..X\n");
 	for (size_t i = 1; i <= rounds; i++) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
 			fail("element already in tree (1)", "true");
@@ -58,10 +55,7 @@ simple_check()
 	for (size_t i = 1; i <= rounds; i++) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (!rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
 			fail("element in tree (1)", "false");
@@ -87,10 +81,7 @@ simple_check()
 	for (size_t i = 1; i <= rounds; i++) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (rtree_search(&tree, &rect, SOP_EQUALS, &iterator)) {
 			fail("element already in tree (2)", "true");
@@ -103,10 +94,7 @@ simple_check()
 	for (size_t i = rounds; i != 0; i--) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (!rtree_search(&tree, &rect, SOP_OVERLAPS, &iterator)) {
 			fail("element in tree (2)", "false");
@@ -133,10 +121,7 @@ simple_check()
 	for (size_t i = rounds; i != 0; i--) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
 			fail("element already in tree (3)", "true");
@@ -149,10 +134,7 @@ simple_check()
 	for (size_t i = 1; i <= rounds; i++) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (!rtree_search(&tree, &rect, SOP_BELONGS, &iterator)) {
 			fail("element in tree (3)", "false");
@@ -179,10 +161,7 @@ simple_check()
 	for (size_t i = rounds; i != 0; i--) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
 			fail("element already in tree (4)", "true");
@@ -195,10 +174,7 @@ simple_check()
 	for (size_t i = rounds; i != 0; i--) {
 		record_t rec = (record_t)i;
 
-		rect.lower_point.coords[0] = i;
-		rect.lower_point.coords[1] = i;
-		rect.upper_point.coords[0] = i + 0.5;
-		rect.upper_point.coords[1] = i + 0.5;
+		rtree_set2d(&rect, i, i, i + 0.5, i + 0.5);
 
 		if (!rtree_search(&tree, &rect, SOP_CONTAINS, &iterator)) {
 			fail("element in tree (4)", "false");
@@ -247,15 +223,12 @@ neighbor_test()
 	static struct rtree_rect basis;
 
 	for (size_t i = 0; i < test_count; i++) {
-		arr[i].lower_point.coords[0] = i;
-		arr[i].lower_point.coords[1] = i;
-		arr[i].upper_point.coords[0] = i + 1;
-		arr[i].upper_point.coords[1] = i + 1;
+		rtree_set2d(&arr[i], i, i, i + 1, i + 1);
 	}
 
 	for (size_t i = 0; i <= test_count; i++) {
 		struct rtree tree;
-		rtree_init(&tree, extent_size, extent_alloc, extent_free);
+		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
 
 		rtree_test_build(&tree, arr, i);
 
diff --git a/test/unit/rtree_itr.cc b/test/unit/rtree_itr.cc
index 08bc959aec..088edd657e 100644
--- a/test/unit/rtree_itr.cc
+++ b/test/unit/rtree_itr.cc
@@ -8,7 +8,7 @@
 
 static int extent_count = 0;
 
-const uint32_t extent_size = RTREE_PAGE_SIZE * 8;
+const uint32_t extent_size = 1024 * 8;
 
 static void *
 extent_alloc()
@@ -30,7 +30,7 @@ itr_check()
 	header();
 
 	struct rtree tree;
-	rtree_init(&tree, extent_size, extent_alloc, extent_free);
+	rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
 
 	/* Filling tree */
 	const size_t count1 = 10000;
@@ -161,7 +161,7 @@ itr_check()
 		for (size_t j = 0; j < count2; j++) {
 			coord_t coord = i * 2 * count2;
 			rtree_set2d(&rect, coord + 0.1, coord + 0.1, coord + j, coord + j);
-			rtree_rect_normalize(&rect);
+			rtree_rect_normalize(&rect, 2);
 			if (!rtree_search(&tree, &rect, SOP_STRICT_CONTAINS, &iterator) && j != 0 && j != count2 - 1) {
 				fail("Integrity check failed (11)", "false");
 			}
@@ -211,7 +211,7 @@ itr_invalidate_check()
 			del_cnt = test_size - del_pos;
 		}
 		struct rtree tree;
-		rtree_init(&tree, extent_size, extent_alloc, extent_free);
+		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
 		struct rtree_iterator iterators[test_size];
 		for (size_t i = 0; i < test_size; i++)
 			rtree_iterator_init(iterators + i);
@@ -255,7 +255,7 @@ itr_invalidate_check()
 		size_t ins_cnt = rand() % max_insert_count + 1;
 
 		struct rtree tree;
-		rtree_init(&tree, extent_size, extent_alloc, extent_free);
+		rtree_init(&tree, 2, extent_size, extent_alloc, extent_free);
 		struct rtree_iterator iterators[test_size];
 		for (size_t i = 0; i < test_size; i++)
 			rtree_iterator_init(iterators + i);
diff --git a/test/unit/rtree_itr.result b/test/unit/rtree_itr.result
index 4a3762cf11..14196b6b38 100644
--- a/test/unit/rtree_itr.result
+++ b/test/unit/rtree_itr.result
@@ -1,6 +1,6 @@
 	*** itr_check ***
 Test tree size: 50000
---> 0x5 0x4 0x3 0x2 0x1 0xa 0x9 
+--> 0x1 0x2 0x3 0x4 0x5 0x6 0x7 
 <-- 0xc34c 0xc34d 0xc34e 0xc34f 0xc350 0xc34b 0xc34a 
 	*** itr_check: done ***
  	*** itr_invalidate_check ***
diff --git a/test/unit/rtree_multidim.cc b/test/unit/rtree_multidim.cc
new file mode 100644
index 0000000000..4d67179e47
--- /dev/null
+++ b/test/unit/rtree_multidim.cc
@@ -0,0 +1,436 @@
+#include <algorithm>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <time.h>
+
+#include "unit.h"
+#include "salad/rtree.h"
+#include "../../src/lib/salad/rtree.h"
+
+#include <vector>
+#include <set>
+using namespace std;
+
+const uint32_t extent_size = 1024 * 16;
+
+const coord_t SPACE_LIMIT = 100;
+const coord_t BOX_LIMIT = 10;
+const unsigned BOX_POINT_CHANCE_PERCENT = 5;
+const unsigned NEIGH_COUNT = 5;
+const unsigned AVERAGE_COUNT = 500;
+const unsigned TEST_ROUNDS = 1000;
+
+static int page_count = 0;
+
+static void *
+extent_alloc()
+{
+	page_count++;
+	return malloc(extent_size);
+}
+
+static void
+extent_free(void *page)
+{
+	page_count--;
+	free(page);
+}
+
+struct CCoordPair {
+	coord_t a, b;
+};
+
+coord_t
+rand(coord_t lim)
+{
+	return rand() % 1024 * lim / 1024;
+}
+
+template<unsigned DIMENSION>
+struct CBox {
+	CCoordPair pairs[DIMENSION];
+	void RandomPoint()
+	{
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			pairs[i].b = pairs[i].a = rand(SPACE_LIMIT);
+		}
+	}
+	void Randomize()
+	{
+		coord_t widths[DIMENSION] = {0};
+		if (rand() % 100 >= BOX_POINT_CHANCE_PERCENT)
+			for (unsigned i = 0; i < DIMENSION; i++)
+				widths[i] = rand(BOX_LIMIT);
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			pairs[i].a = rand(SPACE_LIMIT - widths[i]);
+			pairs[i].b = pairs[i].a + widths[i];
+		}
+	}
+	void RandomizeBig()
+	{
+		coord_t widths[DIMENSION] = {0};
+		if (DIMENSION == 1)
+			for (unsigned i = 0; i < DIMENSION; i++)
+				widths[i] = rand(SPACE_LIMIT / 4);
+		else if (DIMENSION == 2)
+			for (unsigned i = 0; i < DIMENSION; i++)
+				widths[i] = rand(SPACE_LIMIT / 3);
+		else if (DIMENSION == 2)
+			for (unsigned i = 0; i < DIMENSION; i++)
+				widths[i] = rand(SPACE_LIMIT / 2);
+		else
+			for (unsigned i = 0; i < DIMENSION; i++)
+				widths[i] = rand(SPACE_LIMIT);
+
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			pairs[i].a = rand(SPACE_LIMIT - widths[i]);
+			pairs[i].b = pairs[i].a + widths[i];
+		}
+	}
+	void FillRTreeRect(struct rtree_rect *rt)
+	{
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			rt->coords[2 * i] = pairs[i].a;
+			rt->coords[2 * i + 1] = pairs[i].b;
+		}
+	}
+	bool operator== (const struct rtree_rect *rt) const
+	{
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			if (rt->coords[2 * i] != pairs[i].a ||
+			    rt->coords[2 * i + 1] != pairs[i].b)
+				return false;
+		}
+		return true;
+	}
+	bool In(const CBox<DIMENSION> &another) const
+	{
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			if (pairs[i].a < another.pairs[i].a ||
+			    pairs[i].b > another.pairs[i].b)
+				return false;
+		}
+		return true;
+	}
+	bool InStrictly(const CBox<DIMENSION> &another) const
+	{
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			if (pairs[i].a <= another.pairs[i].a ||
+			    pairs[i].b >= another.pairs[i].b)
+				return false;
+		}
+		return true;
+	}
+	coord_t Distance2(const CBox<DIMENSION> &point) const
+	{
+		coord_t res = 0;
+		for (unsigned i = 0; i < DIMENSION; i++) {
+			if (point.pairs[i].a < pairs[i].a) {
+				coord_t d = pairs[i].a - point.pairs[i].a;
+				res += d * d;
+			} else if (point.pairs[i].a > pairs[i].b) {
+				coord_t d = point.pairs[i].a - pairs[i].b;
+				res += d * d;
+			}
+		}
+		return res;
+	}
+};
+
+template<unsigned DIMENSION>
+struct CBoxSetEntry {
+	CBox<DIMENSION> box;
+	size_t id;
+	size_t next;
+	bool used;
+	bool operator<(const CBoxSetEntry<DIMENSION> &a) const
+	{
+		return id < a.id;
+	}
+};
+
+template<unsigned DIMENSION>
+struct CBoxSet {
+	vector<CBoxSetEntry<DIMENSION> > entries;
+	size_t boxCount;
+	size_t free;
+	CBoxSet() : boxCount(0), free(SIZE_MAX) {}
+	size_t getNewID()
+	{
+		size_t res;
+		if (free != SIZE_MAX) {
+			res = free;
+			free = entries[free].next;
+		} else {
+			res = entries.size();
+			entries.resize(res + 1);
+		}
+		return res;
+	}
+	size_t AddBox(const CBox<DIMENSION> &box)
+	{
+		size_t id = getNewID();
+		entries[id].box = box;
+		entries[id].id = id;
+		entries[id].next = SIZE_MAX;
+		entries[id].used = true;
+		boxCount++;
+		return id;
+	}
+	size_t RandUsedID() const
+	{
+		assert(boxCount);
+		size_t res = rand() % entries.size();
+		while (!entries[res].used)
+			if (++res >= entries.size())
+				res = 0;
+		return res;
+	}
+	void DeleteBox(size_t id)
+	{
+		entries[id].used = false;
+		entries[id].next = free;
+		free = id;
+		boxCount--;
+	}
+	void SelectIn(const CBox<DIMENSION> &box,
+		      vector<CBoxSetEntry<DIMENSION> > &result) const
+	{
+		result.clear();
+		for (size_t i = 0; i < entries.size(); i++)
+			if (entries[i].used && entries[i].box.In(box))
+				result.push_back(entries[i]);
+	}
+	void SelectInStrictly(const CBox<DIMENSION> &box,
+			      vector<CBoxSetEntry<DIMENSION> > &result) const
+	{
+		result.clear();
+		for (size_t i = 0; i < entries.size(); i++)
+			if (entries[i].used && entries[i].box.InStrictly(box))
+				result.push_back(entries[i]);
+	}
+	void SelectNeigh(const CBox<DIMENSION> &point,
+			 vector<CBoxSetEntry<DIMENSION> > &result) const;
+};
+
+template<unsigned DIMENSION>
+struct CEntryByDistance {
+	const CBox<DIMENSION> &point;
+	CEntryByDistance(const CBox<DIMENSION> &point_) : point(point_) {}
+	bool operator()(const CBoxSetEntry<DIMENSION> &a,
+			const CBoxSetEntry<DIMENSION> &b) const
+	{
+		coord_t da = a.box.Distance2(point);
+		coord_t db = b.box.Distance2(point);
+		return da < db ? true : da > db ? false : a.id < b.id;
+	}
+};
+
+template<unsigned DIMENSION>
+void CBoxSet<DIMENSION>::SelectNeigh(const CBox<DIMENSION> &point,
+	vector<CBoxSetEntry<DIMENSION> > &result) const
+{
+	result.clear();
+	CEntryByDistance<DIMENSION> comp(point);
+	set<CBoxSetEntry<DIMENSION>, CEntryByDistance<DIMENSION> > set(comp);
+	size_t i = 0;
+	for (; i < entries.size() && set.size() < NEIGH_COUNT; i++) {
+		if (!entries[i].used)
+			continue;
+		set.insert(entries[i]);
+	}
+	coord_t max_d = set.rbegin()->box.Distance2(point);
+	for (; i < entries.size(); i++) {
+		if (!entries[i].used)
+			continue;
+		coord_t d = entries[i].box.Distance2(point);
+		if (d < max_d) {
+			auto itr = set.end();
+			--itr;
+			set.erase(itr);
+			set.insert(entries[i]);
+			max_d = set.rbegin()->box.Distance2(point);
+		}
+	}
+	for (auto itr : set)
+		result.push_back(itr);
+}
+
+template<unsigned DIMENSION>
+static void
+test_select_neigh(const CBoxSet<DIMENSION> &set, const struct rtree *tree)
+{
+	CBox<DIMENSION> box;
+	box.RandomizeBig();
+	vector<CBoxSetEntry<DIMENSION> > res1;
+	set.SelectNeigh(box, res1);
+
+	struct rtree_rect rt;
+	box.FillRTreeRect(&rt);
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+	vector<CBoxSetEntry<DIMENSION> > res2;
+	if (rtree_search(tree, &rt, SOP_NEIGHBOR, &iterator)) {
+		void *record;
+		while((record = rtree_iterator_next(&iterator))) {
+			CBoxSetEntry<DIMENSION> entry;
+			entry.id = ((unsigned)(uintptr_t)record) - 1;
+			entry.box = set.entries[entry.id].box;
+			res2.push_back(entry);
+			if (res2.size() == NEIGH_COUNT)
+				break;
+		}
+	}
+	if (res1.size() != res2.size()) {
+		printf("%s result size differ %d %d\n", __func__,
+		       (int)res1.size(), (int)res2.size());
+	} else {
+		for (size_t i = 0; i < res1.size(); i++)
+			if (res1[i].id != res2[i].id &&
+				res1[i].box.Distance2(box) !=
+				res2[i].box.Distance2(box))
+				printf("%s result differ!\n", __func__);
+	}
+	rtree_iterator_destroy(&iterator);
+
+}
+
+template<unsigned DIMENSION>
+static void
+test_select_in(const CBoxSet<DIMENSION> &set, const struct rtree *tree)
+{
+	CBox<DIMENSION> box;
+	box.RandomizeBig();
+	vector<CBoxSetEntry<DIMENSION> > res1;
+	set.SelectIn(box, res1);
+
+	struct rtree_rect rt;
+	box.FillRTreeRect(&rt);
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+	vector<CBoxSetEntry<DIMENSION> > res2;
+	if (rtree_search(tree, &rt, SOP_BELONGS, &iterator)) {
+		void *record;
+		while((record = rtree_iterator_next(&iterator))) {
+			CBoxSetEntry<DIMENSION> entry;
+			entry.id = ((unsigned)(uintptr_t)record) - 1;
+			entry.box = set.entries[entry.id].box;
+			res2.push_back(entry);
+		}
+	}
+	sort(res1.begin(), res1.end());
+	sort(res2.begin(), res2.end());
+	if (res1.size() != res2.size()) {
+		printf("%s result size differ %d %d\n", __func__,
+		       (int)res1.size(), (int)res2.size());
+	} else {
+		for (size_t i = 0; i < res1.size(); i++)
+			if (res1[i].id != res2[i].id)
+				printf("%s result differ!\n", __func__);
+	}
+	rtree_iterator_destroy(&iterator);
+
+}
+
+template<unsigned DIMENSION>
+static void
+test_select_strict_in(const CBoxSet<DIMENSION> &set, const struct rtree *tree)
+{
+	CBox<DIMENSION> box;
+	box.RandomizeBig();
+	vector<CBoxSetEntry<DIMENSION> > res1;
+	set.SelectInStrictly(box, res1);
+
+	struct rtree_rect rt;
+	box.FillRTreeRect(&rt);
+	struct rtree_iterator iterator;
+	rtree_iterator_init(&iterator);
+	vector<CBoxSetEntry<DIMENSION> > res2;
+	if (rtree_search(tree, &rt, SOP_STRICT_BELONGS, &iterator)) {
+		void *record;
+		while((record = rtree_iterator_next(&iterator))) {
+			CBoxSetEntry<DIMENSION> entry;
+			entry.id = ((unsigned)(uintptr_t)record) - 1;
+			entry.box = set.entries[entry.id].box;
+			res2.push_back(entry);
+		}
+	}
+	sort(res1.begin(), res1.end());
+	sort(res2.begin(), res2.end());
+	if (res1.size() != res2.size()) {
+		printf("%s result size differ %d %d\n", __func__,
+		       (int)res1.size(), (int)res2.size());
+	} else {
+		for (size_t i = 0; i < res1.size(); i++)
+			if (res1[i].id != res2[i].id)
+				printf("%s result differ!\n", __func__);
+	}
+	rtree_iterator_destroy(&iterator);
+
+}
+
+template<unsigned DIMENSION>
+static void
+rand_test()
+{
+	header();
+
+	CBoxSet<DIMENSION> set;
+
+	struct rtree tree;
+	rtree_init(&tree, DIMENSION, extent_size, extent_alloc, extent_free);
+
+	printf("\tDIMENSION: %u, page size: %u, max fill: %u\n",
+	       DIMENSION, tree.page_size, tree.page_max_fill);
+
+	for (unsigned i = 0; i < TEST_ROUNDS; i++) {
+		bool insert;
+		if (set.boxCount == 0) {
+			insert = true;
+		} else if (set.boxCount == AVERAGE_COUNT) {
+			insert = false;
+		} else {
+			insert = rand() % (AVERAGE_COUNT * 2) > set.boxCount;
+		}
+		if (insert) {
+			CBox<DIMENSION> box;
+			box.Randomize();
+			size_t id = set.AddBox(box);
+			struct rtree_rect rt;
+			box.FillRTreeRect(&rt);
+			rtree_insert(&tree, &rt, (void *)(id + 1));
+		} else {
+			size_t id = set.RandUsedID();
+			struct rtree_rect rt;
+			set.entries[id].box.FillRTreeRect(&rt);
+			rtree_remove(&tree, &rt, (void *)(id + 1));
+			set.DeleteBox(id);
+		}
+		assert(set.boxCount == tree.n_records);
+		test_select_neigh<DIMENSION>(set, &tree);
+		test_select_in<DIMENSION>(set, &tree);
+		test_select_strict_in<DIMENSION>(set, &tree);
+	}
+
+	rtree_destroy(&tree);
+
+	footer();
+}
+
+int
+main(void)
+{
+	srand(time(0));
+	rand_test<1>();
+	rand_test<2>();
+	rand_test<3>();
+	rand_test<8>();
+	rand_test<16>();
+	if (page_count != 0) {
+		fail("memory leak!", "true");
+	}
+}
diff --git a/test/unit/rtree_multidim.result b/test/unit/rtree_multidim.result
new file mode 100644
index 0000000000..f22a472274
--- /dev/null
+++ b/test/unit/rtree_multidim.result
@@ -0,0 +1,16 @@
+	*** rand_test ***
+	DIMENSION: 1, page size: 512, max fill: 21
+	*** rand_test: done ***
+ 	*** rand_test ***
+	DIMENSION: 2, page size: 1024, max fill: 25
+	*** rand_test: done ***
+ 	*** rand_test ***
+	DIMENSION: 3, page size: 1024, max fill: 18
+	*** rand_test: done ***
+ 	*** rand_test ***
+	DIMENSION: 8, page size: 4096, max fill: 30
+	*** rand_test: done ***
+ 	*** rand_test ***
+	DIMENSION: 16, page size: 8192, max fill: 31
+	*** rand_test: done ***
+ 
\ No newline at end of file
diff --git a/test/wal_off/rtree_benchmark.result b/test/wal_off/rtree_benchmark.result
index c4b4c4071f..16c486e4dc 100644
--- a/test/wal_off/rtree_benchmark.result
+++ b/test/wal_off/rtree_benchmark.result
@@ -1,25 +1,32 @@
-s = box.schema.space.create('rtreebench')
+n_records = 10000
 ---
 ...
-_ = s:create_index('primary')
+n_iterations = 10000
 ---
 ...
-_ = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+n_neighbors = 10
 ---
 ...
-n_records = 20000
+file = io.open("rtree_benchmark.res", "w")
 ---
 ...
-n_iterations = 10000
+s = box.schema.space.create('rtreebench')
 ---
 ...
-n_neighbors = 10
+_ = s:create_index('primary')
 ---
 ...
-file = io.open("rtree_benchmark.res", "w")
+_ = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
+---
+...
+file:write(" *** 2D *** \n")
+---
+- true
+...
+rect_width = 180 / math.pow(n_records, 1 / 2)
 ---
 ...
-start = os.clock()
+start = os.time()
 ---
 ...
 --# setopt delimiter ';'
@@ -28,47 +35,47 @@ for i = 1, n_records do
 end;
 ---
 ...
-file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.clock() - start));
+file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.time() - start));
 ---
 - true
 ...
-start = os.clock();
+start = os.time();
 ---
 ...
 n = 0;
 ---
 ...
 for i = 1, n_iterations do
-   x = 180*math.random()
-   y = 180*math.random()
-   for k,v in s.index.spatial:pairs({x,y,x+1,y+1}, {iterator = 'LE'}) do
+   x = (180 - rect_width) * math.random()
+   y = (180 - rect_width) * math.random()
+   for k,v in s.index.spatial:pairs({x,y,x+rect_width,y+rect_width}, {iterator = 'LE'}) do
        n = n + 1
    end
 end;
 ---
 ...
-file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.clock() - start));
+file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.time() - start));
 ---
 - true
 ...
-start = os.clock();
+start = os.time();
 ---
 ...
 n = 0
 for i = 1, n_iterations do
-   x = 180*math.random()
-   y = 180*math.random()
+   x = 180 * math.random()
+   y = 180 * math.random()
    for k,v in pairs(s.index.spatial:select({x,y }, {limit = n_neighbors, iterator = 'NEIGHBOR'})) do
       n = n + 1
    end
 end;
 ---
 ...
-file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.clock() - start));
+file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.time() - start));
 ---
 - true
 ...
-start = os.clock();
+start = os.time();
 ---
 ...
 for i = 1, n_records do
@@ -76,14 +83,104 @@ for i = 1, n_records do
 end;
 ---
 ...
-file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.clock() - start));
+file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.time() - start));
 ---
 - true
 ...
-file:close();
+s:drop();
+---
+...
+dimension = 8;
+---
+...
+s = box.schema.space.create('rtreebench');
+---
+...
+_ = s:create_index('primary');
+---
+...
+_ = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}, dimension = dimension});
+---
+...
+file:write(" *** 8D *** \n")
+rect_width = 180 / math.pow(n_records, 1 / dimension)
+
+start = os.time();
+---
+...
+for i = 1, n_records do
+   local record = {}
+   for j = 1, dimension do
+      table.insert(record, 180*math.random())
+   end
+   s:insert{i,record}
+end;
+---
+...
+file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.time() - start));
+---
+- true
+...
+start = os.time();
+---
+...
+n = 0;
+---
+...
+for i = 1, n_iterations do
+   local rect = {}
+   for j = 1, dimension do
+      table.insert(rect, (180 - rect_width) * math.random())
+   end
+   for j = 1, dimension do
+      table.insert(rect, rect[j] + rect_width)
+   end
+   for k,v in s.index.spatial:pairs(rect, {iterator = 'LE'}) do
+       n = n + 1
+   end
+end;
+---
+...
+file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.time() - start));
+---
+- true
+...
+start = os.time();
+---
+...
+n = 0
+for i = 1, 0 do
+   local rect = {}
+   for j = 1, dimension do
+      table.insert(rect, 180*math.random())
+   end
+   for k,v in pairs(s.index.spatial:select(rect, {limit = n_neighbors, iterator = 'NEIGHBOR'})) do
+      n = n + 1
+   end
+end;
+---
+...
+file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.time() - start));
+---
+- true
+...
+start = os.time();
+---
+...
+for i = 1, n_records do
+    s:delete{i}
+end;
+---
+...
+file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.time() - start));
 ---
 - true
 ...
 s:drop();
 ---
 ...
+file:close();
+---
+- true
+...
+--# setopt delimiter ''
diff --git a/test/wal_off/rtree_benchmark.test.lua b/test/wal_off/rtree_benchmark.test.lua
index 37dec0fda2..fa072eeb7b 100644
--- a/test/wal_off/rtree_benchmark.test.lua
+++ b/test/wal_off/rtree_benchmark.test.lua
@@ -1,49 +1,115 @@
+n_records = 10000
+n_iterations = 10000
+n_neighbors = 10
+
+file = io.open("rtree_benchmark.res", "w")
+
 s = box.schema.space.create('rtreebench')
 _ = s:create_index('primary')
 _ = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}})
 
-n_records = 20000
-n_iterations = 10000
-n_neighbors = 10
+file:write(" *** 2D *** \n")
+rect_width = 180 / math.pow(n_records, 1 / 2)
 
-file = io.open("rtree_benchmark.res", "w")
-start = os.clock()
+start = os.time()
 
 --# setopt delimiter ';'
 for i = 1, n_records do
    s:insert{i,{180*math.random(),180*math.random()}}
 end;
 
-file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.clock() - start));
+file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.time() - start));
 
-start = os.clock();
+start = os.time();
 n = 0;
 for i = 1, n_iterations do
-   x = 180*math.random()
-   y = 180*math.random()
-   for k,v in s.index.spatial:pairs({x,y,x+1,y+1}, {iterator = 'LE'}) do
+   x = (180 - rect_width) * math.random()
+   y = (180 - rect_width) * math.random()
+   for k,v in s.index.spatial:pairs({x,y,x+rect_width,y+rect_width}, {iterator = 'LE'}) do
        n = n + 1
    end
 end;
-file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.clock() - start));
+file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.time() - start));
 
-start = os.clock();
+start = os.time();
 n = 0
 for i = 1, n_iterations do
-   x = 180*math.random()
-   y = 180*math.random()
+   x = 180 * math.random()
+   y = 180 * math.random()
    for k,v in pairs(s.index.spatial:select({x,y }, {limit = n_neighbors, iterator = 'NEIGHBOR'})) do
       n = n + 1
    end
 end;
-file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.clock() - start));
+file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.time() - start));
 
-start = os.clock();
+start = os.time();
 for i = 1, n_records do
     s:delete{i}
 end;
-file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.clock() - start));
+file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.time() - start));
+
+s:drop();
+
+dimension = 8;
+
+s = box.schema.space.create('rtreebench');
+_ = s:create_index('primary');
+_ = s:create_index('spatial', { type = 'rtree', unique = false, parts = {2, 'array'}, dimension = dimension});
+
+file:write(" *** 8D *** \n")
+rect_width = 180 / math.pow(n_records, 1 / dimension)
+
+start = os.time();
+
+for i = 1, n_records do
+   local record = {}
+   for j = 1, dimension do
+      table.insert(record, 180*math.random())
+   end
+   s:insert{i,record}
+end;
+
+file:write(string.format("Elapsed time for inserting %d records: %d\n", n_records, os.time() - start));
+
+start = os.time();
+n = 0;
+for i = 1, n_iterations do
+   local rect = {}
+   for j = 1, dimension do
+      table.insert(rect, (180 - rect_width) * math.random())
+   end
+   for j = 1, dimension do
+      table.insert(rect, rect[j] + rect_width)
+   end
+   for k,v in s.index.spatial:pairs(rect, {iterator = 'LE'}) do
+       n = n + 1
+   end
+end;
+file:write(string.format("Elapsed time for %d belongs searches selecting %d records: %d\n", n_iterations, n, os.time() - start));
+
+start = os.time();
+n = 0
+for i = 1, 0 do
+   local rect = {}
+   for j = 1, dimension do
+      table.insert(rect, 180*math.random())
+   end
+   for k,v in pairs(s.index.spatial:select(rect, {limit = n_neighbors, iterator = 'NEIGHBOR'})) do
+      n = n + 1
+   end
+end;
+file:write(string.format("Elapsed time for %d nearest %d neighbors searches selecting %d records: %d\n", n_iterations, n_neighbors, n, os.time() - start));
+
+start = os.time();
+for i = 1, n_records do
+    s:delete{i}
+end;
+file:write(string.format("Elapsed time for deleting  %d records: %d\n", n_records, os.time() - start));
 
-file:close();
 s:drop();
 
+file:close();
+
+--# setopt delimiter ''
+
+
-- 
GitLab