From cdf4d08fd377b9a6b32e354b52022a51730c3215 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Mon, 1 Jun 2020 23:28:04 +0200 Subject: [PATCH] WIP * Maybe fixed a performance issue on box-trees: the iterator wasn't going down to the very bottom of the tree on initialization * Added array quad skipping in display of shape arrays --- src/db/db/dbArray.h | 77 ++++++++++++++- src/db/db/dbBoxTree.h | 56 ++++++++--- src/db/db/dbInstances.cc | 5 +- src/db/db/dbInstances.h | 1 - src/db/db/dbShapeIterator.cc | 97 ++++++++++++++++++- src/db/db/dbShapes.h | 33 +++++++ src/db/unit_tests/dbBoxTreeTests.cc | 88 +++++++++++++++++ .../laybasic/layRedrawThreadWorker.cc | 49 ++++++++-- 8 files changed, 374 insertions(+), 32 deletions(-) diff --git a/src/db/db/dbArray.h b/src/db/db/dbArray.h index 8b55c6c35..7def9fe86 100644 --- a/src/db/db/dbArray.h +++ b/src/db/db/dbArray.h @@ -70,7 +70,11 @@ struct basic_array_iterator virtual long index_a () const { return -1; } virtual long index_b () const { return -1; } - + + virtual size_t quad_id () const { return 0; } + virtual box_type quad_box () const { return box_type::world (); } + virtual void skip_quad () { } + virtual disp_type get () const = 0; virtual basic_array_iterator *clone () const = 0; @@ -806,6 +810,21 @@ struct iterated_array_iterator return new iterated_array_iterator (*this); } + virtual size_t quad_id () const + { + return m_t.quad_id (); + } + + virtual box_type quad_box () const + { + return m_t.quad_box (); + } + + virtual void skip_quad () + { + m_t.skip_quad (); + } + private: box_tree_const_iterator m_b, m_e; box_tree_touching_iterator m_t; @@ -1420,6 +1439,36 @@ struct array_iterator return mp_base ? mp_base->index_b () : -1; } + /** + * @brief For iterators supporting quads (iterated arrays), this method will return the quad ID + */ + size_t quad_id () const + { + return mp_base ? mp_base->quad_id () : 0; + } + + /** + * @brief For iterators supporting quads (iterated arrays), this method will return the quad bounding box + * + * Note that this method will only return a valid quad box is the quad_id is non-null. + * + * This method will return the bounding box of all array offsets in the quad. + */ + db::box quad_box () const + { + return mp_base ? mp_base->quad_box () : db::box::world (); + } + + /** + * @brief For iterators supporting quads (iterated arrays), this method will skip the current quad + */ + void skip_quad () + { + if (mp_base) { + mp_base->skip_quad (); + } + } + private: trans_type m_trans; basic_array_iterator *mp_base; @@ -1835,6 +1884,32 @@ struct array } } + /** + * @brief Gets the bounding box from the iterator's current quad + * + * The bounding box is that of all objects in the current quad and + * is confined to the array's total bounding box. + */ + template + box_type quad_box (const Iter &iter, const BoxConv &bc) const + { + box_type bb; + if (mp_base) { + bb = mp_base->bbox (box_type (0, 0, 0, 0)); + } + bb &= iter.quad_box (); + + if (mp_base) { + if (mp_base->is_complex ()) { + return bb * box_type (mp_base->complex_trans (simple_trans_type (m_trans)) * bc (m_obj)); + } else { + return bb * (m_trans * bc (m_obj)); + } + } else { + return bb * (m_trans * bc (m_obj)); + } + } + /** * @brief The number of single instances in the array */ diff --git a/src/db/db/dbBoxTree.h b/src/db/db/dbBoxTree.h index 0e3a8ae67..7217d2159 100644 --- a/src/db/db/dbBoxTree.h +++ b/src/db/db/dbBoxTree.h @@ -577,12 +577,16 @@ private: return m_quad < 4; } - // down one level + // down as many levels as required for the next non-empty quad // returns true if this is possible bool down () { - box_tree_node *c = mp_node->child (m_quad); - if (c) { + while (true) { + + box_tree_node *c = mp_node->child (m_quad); + if (! c) { + return false; + } mp_node = c; m_quad = -1; @@ -595,12 +599,11 @@ private: // nothing to visit: up again up (); return false; - } else { + } else if (m_quad < 0) { + // stay in main chunk return true; } - } else { - return false; } } @@ -670,7 +673,7 @@ private: * whose box overlaps or touches a specified test box. */ -template +template class box_tree { public: @@ -1175,7 +1178,16 @@ private: // the bins are: overall, ur, ul, ll, lr, empty element_iterator qloc [6] = { from, from, from, from, from, from }; - point_type center (bbox.center ()); + point_type center; + if (bbox.width () < thin_aspect * bbox.height ()) { + // separate by height only + center = point_type (bbox.left (), bbox.bottom () + bbox.height () / 2); + } else if (bbox.height () < thin_aspect * bbox.width ()) { + // separate by width only + center = point_type (bbox.left () + bbox.width () / 2, bbox.bottom ()); + } else { + center = bbox.center (); + } for (element_iterator e = from; e != to; ++e) { @@ -1578,12 +1590,16 @@ private: return m_quad < 4; } - // down one level + // down as many levels as required for the next non-empty quad // returns true if this is possible bool down () { - box_tree_node *c = mp_node->child (m_quad); - if (c) { + while (true) { + + box_tree_node *c = mp_node->child (m_quad); + if (! c) { + return false; + } mp_node = c; m_quad = -1; @@ -1596,12 +1612,11 @@ private: // nothing to visit: up again up (); return false; - } else { + } else if (m_quad < 0) { + // stay in main chunk return true; } - } else { - return false; } } @@ -1638,7 +1653,7 @@ private: * is sorted. */ -template +template class unstable_box_tree { public: @@ -2103,7 +2118,16 @@ private: } obj_iterator qloc [5] = { from, from, from, from, from }; - point_type center (bbox.center ()); + point_type center; + if (bbox.width () < thin_aspect * bbox.height ()) { + // separate by height only + center = point_type (bbox.left (), bbox.bottom () + bbox.height () / 2); + } else if (bbox.height () < thin_aspect * bbox.width ()) { + // separate by width only + center = point_type (bbox.left () + bbox.width () / 2, bbox.bottom ()); + } else { + center = bbox.center (); + } for (obj_iterator e = from; e != to; ++e) { diff --git a/src/db/db/dbInstances.cc b/src/db/db/dbInstances.cc index 096d515de..488b86a92 100644 --- a/src/db/db/dbInstances.cc +++ b/src/db/db/dbInstances.cc @@ -640,13 +640,12 @@ OverlappingInstanceIteratorTraits::init (instance_iteratorbegin_sorted_insts ()), - m_end (insts->end_sorted_insts ()), - mp_insts (insts) + m_end (insts->end_sorted_insts ()) { } cell_index_type diff --git a/src/db/db/dbInstances.h b/src/db/db/dbInstances.h index e754392db..fd2e13a08 100644 --- a/src/db/db/dbInstances.h +++ b/src/db/db/dbInstances.h @@ -945,7 +945,6 @@ public: private: inst_iterator_type m_iter, m_end; - const instances_type *mp_insts; }; /** diff --git a/src/db/db/dbShapeIterator.cc b/src/db/db/dbShapeIterator.cc index a0e9db1e1..f4433255b 100644 --- a/src/db/db/dbShapeIterator.cc +++ b/src/db/db/dbShapeIterator.cc @@ -546,9 +546,13 @@ ShapeIterator::advance_aref (int &mode) if (mode && m_array_iterator_valid) { - if (mode > 0) { + if (mode == 1) { array_iterator *arr_iter = (array_iterator *) m_ad.iter; ++*arr_iter; + } else if (mode == 2) { + // skip array quad -> skip rest of array quad and move to shape in the next quad or to end + do_skip_array_quad (); + mode = 1; } else { // skip quad -> skip rest of array and move to next shape array skip_array (); // sets m_array_iterator_valid = false @@ -810,9 +814,100 @@ ShapeIterator::quad_box () const return quad_box_generic (); } } + return db::Box (); } +template +void +ShapeIterator::do_skip_array_quad_iter () +{ + Iter *arr_iter = (Iter *) m_ad.iter; + arr_iter->skip_quad (); +} + +void +ShapeIterator::do_skip_array_quad () +{ + if (m_array_iterator_valid) { + if (m_type == PolygonPtrArray) { + do_skip_array_quad_iter (); + } else if (m_type == SimplePolygonPtrArray) { + do_skip_array_quad_iter (); + } else if (m_type == PathPtrArray) { + do_skip_array_quad_iter (); + } else if (m_type == TextPtrArray) { + do_skip_array_quad_iter (); + } else if (m_type == BoxArray) { + do_skip_array_quad_iter (); + } else if (m_type == ShortBoxArray) { + do_skip_array_quad_iter (); + } + } +} + +template +size_t +ShapeIterator::get_array_quad_id () const +{ + Iter *arr_iter = (Iter *) m_ad.iter; + return arr_iter->quad_id (); +} + +size_t +ShapeIterator::array_quad_id () const +{ + if (m_array_iterator_valid) { + if (m_type == PolygonPtrArray) { + return get_array_quad_id (); + } else if (m_type == SimplePolygonPtrArray) { + return get_array_quad_id (); + } else if (m_type == PathPtrArray) { + return get_array_quad_id (); + } else if (m_type == TextPtrArray) { + return get_array_quad_id (); + } else if (m_type == BoxArray) { + return get_array_quad_id (); + } else if (m_type == ShortBoxArray) { + return get_array_quad_id (); + } + } + + return 0; +} + +template +db::Box +ShapeIterator::get_array_quad_box () const +{ + const Array *arr = m_array.basic_ptr (typename Array::tag ()); + Iter *arr_iter = (Iter *) m_ad.iter; + db::box_convert bc; + return arr->quad_box (*arr_iter, bc); +} + +db::Box +ShapeIterator::array_quad_box () const +{ + if (m_array_iterator_valid) { + if (m_type == PolygonPtrArray) { + return get_array_quad_box (); + } else if (m_type == SimplePolygonPtrArray) { + return get_array_quad_box (); + } else if (m_type == PathPtrArray) { + return get_array_quad_box (); + } else if (m_type == TextPtrArray) { + return get_array_quad_box (); + } else if (m_type == BoxArray) { + return get_array_quad_box (); + } else if (m_type == ShortBoxArray) { + return get_array_quad_box (); + } + } + + return db::Box::world (); +} + void ShapeIterator::cleanup () { diff --git a/src/db/db/dbShapes.h b/src/db/db/dbShapes.h index ce8368518..1942906a0 100644 --- a/src/db/db/dbShapes.h +++ b/src/db/db/dbShapes.h @@ -319,6 +319,33 @@ public: advance (-1); } + /** + * @brief Gets the arrays quad ID + * + * The arrays quad ID is a unique identifier for the current quad for iterated arrays. This can be used to + * detect whether the iterator entered a new quad and optimize the search in that case. + */ + size_t array_quad_id () const; + + /** + * @brief Gets the quad box + * + * Gets the box the current quad uses. This box may be larger than the actual shape containers + * bounding box. Specifically if there is no quad tree at all, this box is the world box. + */ + db::Box array_quad_box () const; + + /** + * @brief Skips the current quad + * + * Moves to the next quad. This method can be used to shortcut searching if we are inside + * a quad that is not relevant for the search. + */ + void skip_array_quad () + { + advance (2); + } + private: // a helper union for the iter_size union // (basically computing the size required for all iterators for a certain shape/array type) @@ -418,6 +445,12 @@ private: template db::Box quad_box_by_shape (OverlappingRegionTag) const; template db::Box quad_box_generic () const; + template db::Box get_array_quad_box () const; + template size_t get_array_quad_id () const; + + template void do_skip_array_quad_iter (); + void do_skip_array_quad (); + template bool advance_shape (int &mode); template void init_array_iter (NoRegionTag); template void init_array_iter (TouchingRegionTag); diff --git a/src/db/unit_tests/dbBoxTreeTests.cc b/src/db/unit_tests/dbBoxTreeTests.cc index c2cbe01ed..9b7e3feb5 100644 --- a/src/db/unit_tests/dbBoxTreeTests.cc +++ b/src/db/unit_tests/dbBoxTreeTests.cc @@ -1031,4 +1031,92 @@ TEST(6U) } +TEST(7) +{ + Box2Box conv; + TestTree t; + int n = 200000; + + for (int i = n - 1; i >= 0; --i) { + t.insert (db::Box (i * 10, 0, i * 10 + 5, 5)); + } + t.sort (conv); + + { + tl::SelfTimer timer ("test 7 lookup"); + size_t n = 0; + for (unsigned int i = 0; i < 2000; ++i) { + db::Coord sx = 0, sy = 0; + TestTree::touching_iterator it = t.begin_touching (db::Box (db::Point (2000, 0), db::Point (3000, 0)), conv); + while (!it.at_end ()) { + sx += abs (it->left ()); + sy += abs (it->bottom ()); + ++it; + ++n; + } + EXPECT_EQ (sx, 252500); + EXPECT_EQ (sy, 0); + } + EXPECT_EQ (n, size_t (101 * 2000)); + } + + { + tl::SelfTimer timer ("test 7 traverse"); + db::Coord m = std::numeric_limits::max (); + size_t n = 0; + for (unsigned int i = 0; i < 10; ++i) { + TestTree::touching_iterator it = t.begin_touching (db::Box (db::Point (-m,-m), db::Point (m, m)), conv); + while (!it.at_end ()) { + ++it; + ++n; + } + } + EXPECT_EQ (n, t.size () * 10); + } +} + +TEST(7U) +{ + Box2Box conv; + UnstableTestTree t; + + int n = 200000; + + for (int i = n - 1; i >= 0; --i) { + t.insert (db::Box (i * 10, 0, i * 10 + 5, 5)); + } + t.sort (conv); + + { + tl::SelfTimer timer ("test 7U lookup"); + size_t n = 0; + for (unsigned int i = 0; i < 2000; ++i) { + db::Coord sx = 0, sy = 0; + UnstableTestTree::touching_iterator it = t.begin_touching (db::Box (db::Point (2000, 0), db::Point (3000, 0)), conv); + while (!it.at_end ()) { + sx += abs (it->left ()); + sy += abs (it->bottom ()); + ++it; + ++n; + } + EXPECT_EQ (sx, 252500); + EXPECT_EQ (sy, 0); + } + EXPECT_EQ (n, size_t (101 * 2000)); + } + + { + tl::SelfTimer timer ("test 7U traverse"); + db::Coord m = std::numeric_limits::max (); + size_t n = 0; + for (unsigned int i = 0; i < 10; ++i) { + UnstableTestTree::touching_iterator it = t.begin_touching (db::Box (db::Point (-m,-m), db::Point (m, m)), conv); + while (!it.at_end ()) { + ++it; + ++n; + } + } + EXPECT_EQ (n, t.size () * 10); + } +} diff --git a/src/laybasic/laybasic/layRedrawThreadWorker.cc b/src/laybasic/laybasic/layRedrawThreadWorker.cc index d48752965..87dedc4ce 100644 --- a/src/laybasic/laybasic/layRedrawThreadWorker.cc +++ b/src/laybasic/laybasic/layRedrawThreadWorker.cc @@ -1582,6 +1582,8 @@ RedrawThreadWorker::draw_layer_wo_cache (int from_level, int to_level, db::cell_ db::Shape last_array; size_t current_quad_id = 0; + size_t current_array_quad_id = 0; + db::ShapeIterator shape (shapes.begin_touching (*v, db::ShapeIterator::Boxes | db::ShapeIterator::Polygons | db::ShapeIterator::Edges | db::ShapeIterator::Paths, mp_prop_sel, m_inv_prop_sel)); while (! shape.at_end ()) { @@ -1596,16 +1598,18 @@ RedrawThreadWorker::draw_layer_wo_cache (int from_level, int to_level, db::cell_ } if (skip) { - shape.skip_quad (); + continue; + } - } else { + if (shape.in_array ()) { - bool simplified = false; - - if (shape.in_array () && last_array != shape.array ()) { + if (last_array != shape.array ()) { last_array = shape.array (); + current_array_quad_id = 0; + + bool simplified = false; if (last_array.type () == db::Shape::PolygonPtrArray) { simplified = draw_array_simplified (mp_renderer.get (), last_array, frame, vertex, trans); @@ -1619,17 +1623,42 @@ RedrawThreadWorker::draw_layer_wo_cache (int from_level, int to_level, db::cell_ simplified = draw_array_simplified (mp_renderer.get (), last_array, frame, vertex, trans); } + if (simplified) { + shape.finish_array (); + // continue with the next shape, array or quad + continue; + } + } - if (simplified) { - shape.finish_array (); - } else { - mp_renderer->draw (*shape, trans, fill, frame, vertex, text); - ++shape; + } else { + current_array_quad_id = 0; + } + + // try whether the array quad can be simplified + + size_t aqid = shape.array_quad_id (); + if (aqid != 0 && aqid != current_array_quad_id) { + + current_array_quad_id = aqid; + + db::DBox qbbox = trans * shape.array_quad_box (); + if (qbbox.width () < 1.5 && qbbox.height () < 1.5) { + + // draw a single box instead of the quad + mp_renderer->draw (qbbox, fill, frame, vertex, text); + shape.skip_array_quad (); + + // continue with the next shape, array or quad + continue; + } } + mp_renderer->draw (*shape, trans, fill, frame, vertex, text); + ++shape; + } }