From cc58d7d8ee103807c533b758112b4254e9a05b6d Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 27 Dec 2020 18:45:10 +0100 Subject: [PATCH] WIP: performance improvement: copy-on-write for flat edge pairs --- src/db/db/dbFlatEdgePairs.cc | 54 ++--- src/db/db/dbFlatEdgePairs.h | 11 +- src/tl/tl/tl.pro | 2 + src/tl/tl/tlCopyOnWrite.cc | 31 +++ src/tl/tl/tlCopyOnWrite.h | 264 ++++++++++++++++++++++++ src/tl/unit_tests/tlCopyOnWriteTests.cc | 213 +++++++++++++++++++ src/tl/unit_tests/unit_tests.pro | 1 + 7 files changed, 547 insertions(+), 29 deletions(-) create mode 100644 src/tl/tl/tlCopyOnWrite.cc create mode 100644 src/tl/tl/tlCopyOnWrite.h create mode 100644 src/tl/unit_tests/tlCopyOnWriteTests.cc diff --git a/src/db/db/dbFlatEdgePairs.cc b/src/db/db/dbFlatEdgePairs.cc index 18e3b4e32..1890613e0 100644 --- a/src/db/db/dbFlatEdgePairs.cc +++ b/src/db/db/dbFlatEdgePairs.cc @@ -32,7 +32,7 @@ namespace db // FlatEdgePairs implementation FlatEdgePairs::FlatEdgePairs () - : AsIfFlatEdgePairs (), m_edge_pairs (false) + : AsIfFlatEdgePairs (), mp_edge_pairs (new db::Shapes (false)) { // .. nothing yet .. } @@ -43,13 +43,13 @@ FlatEdgePairs::~FlatEdgePairs () } FlatEdgePairs::FlatEdgePairs (const FlatEdgePairs &other) - : AsIfFlatEdgePairs (other), m_edge_pairs (false) + : AsIfFlatEdgePairs (other), mp_edge_pairs (other.mp_edge_pairs) { - m_edge_pairs = other.m_edge_pairs; + // .. nothing yet .. } FlatEdgePairs::FlatEdgePairs (const db::Shapes &edge_pairs) - : AsIfFlatEdgePairs (), m_edge_pairs (edge_pairs) + : AsIfFlatEdgePairs (), mp_edge_pairs (new db::Shapes (edge_pairs)) { // .. nothing yet .. } @@ -61,56 +61,58 @@ void FlatEdgePairs::invalidate_cache () void FlatEdgePairs::reserve (size_t n) { - m_edge_pairs.reserve (db::EdgePair::tag (), n); + mp_edge_pairs->reserve (db::EdgePair::tag (), n); } EdgePairsIteratorDelegate *FlatEdgePairs::begin () const { - return new FlatEdgePairsIterator (&m_edge_pairs); + return new FlatEdgePairsIterator (mp_edge_pairs.get_const ()); } std::pair FlatEdgePairs::begin_iter () const { - return std::make_pair (db::RecursiveShapeIterator (m_edge_pairs), db::ICplxTrans ()); + return std::make_pair (db::RecursiveShapeIterator (*mp_edge_pairs), db::ICplxTrans ()); } bool FlatEdgePairs::empty () const { - return m_edge_pairs.empty (); + return mp_edge_pairs->empty (); } size_t FlatEdgePairs::count () const { - return m_edge_pairs.size (); + return mp_edge_pairs->size (); } size_t FlatEdgePairs::hier_count () const { - return m_edge_pairs.size (); + return mp_edge_pairs->size (); } Box FlatEdgePairs::compute_bbox () const { - m_edge_pairs.update_bbox (); - return m_edge_pairs.bbox (); + mp_edge_pairs->update_bbox (); + return mp_edge_pairs->bbox (); } EdgePairsDelegate * FlatEdgePairs::filter_in_place (const EdgePairFilterBase &filter) { - edge_pair_iterator_type pw = m_edge_pairs.get_layer ().begin (); + db::Shapes &ep = *mp_edge_pairs; + + edge_pair_iterator_type pw = ep.get_layer ().begin (); for (EdgePairsIterator p (begin ()); ! p.at_end (); ++p) { if (filter.selected (*p)) { - if (pw == m_edge_pairs.get_layer ().end ()) { - m_edge_pairs.get_layer ().insert (*p); - pw = m_edge_pairs.get_layer ().end (); + if (pw == ep.get_layer ().end ()) { + ep.get_layer ().insert (*p); + pw = ep.get_layer ().end (); } else { - m_edge_pairs.get_layer ().replace (pw++, *p); + ep.get_layer ().replace (pw++, *p); } } } - m_edge_pairs.get_layer ().erase (pw, m_edge_pairs.get_layer ().end ()); + ep.get_layer ().erase (pw, ep.get_layer ().end ()); return this; } @@ -147,22 +149,24 @@ EdgePairsDelegate *FlatEdgePairs::add_in_place (const EdgePairs &other) { invalidate_cache (); + db::Shapes &ep = *mp_edge_pairs; + FlatEdgePairs *other_flat = dynamic_cast (other.delegate ()); if (other_flat) { - m_edge_pairs.insert (other_flat->raw_edge_pairs ().get_layer ().begin (), other_flat->raw_edge_pairs ().get_layer ().end ()); + ep.insert (other_flat->raw_edge_pairs ().get_layer ().begin (), other_flat->raw_edge_pairs ().get_layer ().end ()); } else { - size_t n = m_edge_pairs.size (); + size_t n = ep.size (); for (EdgePairsIterator p (other.begin ()); ! p.at_end (); ++p) { ++n; } - m_edge_pairs.reserve (db::EdgePair::tag (), n); + ep.reserve (db::EdgePair::tag (), n); for (EdgePairsIterator p (other.begin ()); ! p.at_end (); ++p) { - m_edge_pairs.insert (*p); + ep.insert (*p); } } @@ -172,7 +176,7 @@ EdgePairsDelegate *FlatEdgePairs::add_in_place (const EdgePairs &other) const db::EdgePair *FlatEdgePairs::nth (size_t n) const { - return n < m_edge_pairs.size () ? &m_edge_pairs.get_layer ().begin () [n] : 0; + return n < mp_edge_pairs->size () ? &mp_edge_pairs->get_layer ().begin () [n] : 0; } bool FlatEdgePairs::has_valid_edge_pairs () const @@ -197,13 +201,13 @@ FlatEdgePairs::insert_into_as_polygons (Layout *layout, db::cell_index_type into void FlatEdgePairs::insert_into (Layout *layout, db::cell_index_type into_cell, unsigned int into_layer) const { - layout->cell (into_cell).shapes (into_layer).insert (m_edge_pairs); + layout->cell (into_cell).shapes (into_layer).insert (*mp_edge_pairs); } void FlatEdgePairs::insert (const db::EdgePair &ep) { - m_edge_pairs.insert (ep); + mp_edge_pairs->insert (ep); invalidate_cache (); } diff --git a/src/db/db/dbFlatEdgePairs.h b/src/db/db/dbFlatEdgePairs.h index 2cee47b80..eee455ed7 100644 --- a/src/db/db/dbFlatEdgePairs.h +++ b/src/db/db/dbFlatEdgePairs.h @@ -29,6 +29,7 @@ #include "dbAsIfFlatEdgePairs.h" #include "dbShapes.h" #include "dbGenericShapeIterator.h" +#include "tlCopyOnWrite.h" namespace db { @@ -109,14 +110,16 @@ public: void transform (const Trans &trans) { if (! trans.is_unity ()) { - for (edge_pair_iterator_type p = m_edge_pairs.template get_layer ().begin (); p != m_edge_pairs.template get_layer ().end (); ++p) { - m_edge_pairs.get_layer ().replace (p, p->transformed (trans)); + db::Shapes &ep = *mp_edge_pairs; + for (edge_pair_iterator_type p = ep.template get_layer ().begin (); p != ep.template get_layer ().end (); ++p) { + ep.get_layer ().replace (p, p->transformed (trans)); } invalidate_cache (); } } - db::Shapes &raw_edge_pairs () { return m_edge_pairs; } + db::Shapes &raw_edge_pairs () { return *mp_edge_pairs; } + const db::Shapes &raw_edge_pairs () const { return *mp_edge_pairs; } protected: virtual Box compute_bbox () const; @@ -127,7 +130,7 @@ private: FlatEdgePairs &operator= (const FlatEdgePairs &other); - mutable db::Shapes m_edge_pairs; + mutable tl::copy_on_write_ptr mp_edge_pairs; }; } diff --git a/src/tl/tl/tl.pro b/src/tl/tl/tl.pro index fde7e7494..9e003fe27 100644 --- a/src/tl/tl/tl.pro +++ b/src/tl/tl/tl.pro @@ -11,6 +11,7 @@ FORMS = SOURCES = \ tlAssert.cc \ tlClassRegistry.cc \ + tlCopyOnWrite.cc \ tlDataMapping.cc \ tlDeflate.cc \ tlException.cc \ @@ -54,6 +55,7 @@ HEADERS = \ tlAlgorithm.h \ tlAssert.h \ tlClassRegistry.h \ + tlCopyOnWrite.h \ tlDataMapping.h \ tlDeflate.h \ tlException.h \ diff --git a/src/tl/tl/tlCopyOnWrite.cc b/src/tl/tl/tlCopyOnWrite.cc new file mode 100644 index 000000000..9944f1f63 --- /dev/null +++ b/src/tl/tl/tlCopyOnWrite.cc @@ -0,0 +1,31 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#include "tlCopyOnWrite.h" + +namespace tl +{ + +tl::Mutex tl::CopyOnWritePtrBase::ms_lock; + +} diff --git a/src/tl/tl/tlCopyOnWrite.h b/src/tl/tl/tlCopyOnWrite.h new file mode 100644 index 000000000..0f81b462f --- /dev/null +++ b/src/tl/tl/tlCopyOnWrite.h @@ -0,0 +1,264 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + + +#ifndef HDR_tlCopyOnWrite +#define HDR_tlCopyOnWrite + +#include "tlCommon.h" +#include "tlThreads.h" + +namespace tl +{ + +/** + * @brief A copy duplicator (see copy_on_write_ptr below) + */ +template +struct copy_duplicator +{ + X *operator() (const X &o) + { + return new X (o); + } +}; + +/** + * @brief A clone duplicator (see copy_on_write_ptr below) + */ +template +struct clone_duplicator +{ + X *operator() (const X &o) + { + return o.clone (); + } +}; + +/** + * @brief A base class for the copy-on-write shared pointers + */ +class TL_PUBLIC CopyOnWritePtrBase +{ +protected: + static tl::Mutex ms_lock; +}; + +/** + * @brief The holder object: keeps the actual reference of the object + */ +template +class copy_on_write_holder +{ +public: + copy_on_write_holder (X *x) + : m_ref_count (1), mp_x (x) + { } + + ~copy_on_write_holder () + { + delete mp_x; + mp_x = 0; + } + + X *x () + { + return mp_x; + } + + int ref_count () const { return m_ref_count; }; + int dec_ref () { return --m_ref_count; } + void inc_ref () { ++m_ref_count; } + +private: + int m_ref_count; + X *mp_x; +}; + +/** + * @brief Supplies a copy-on-write shared pointer scheme + * + * The idea is to provide a smart, "unique" pointer providing a copy constructor and assignment, but + * sharing the object as long as the object is not written. + * + * Write access is assumed as soon as the non-const pointer is retrieved. + * + * In order to duplicate the object, a "duplicator" needs to be defined. By default, the + * object is duplicated using the copy constructor ("copy_duplicator"). An alternative implementation + * is provided through the "clone_duplicator", which assumes a "clone" method to supply the duplicated + * object. + */ +template > +class copy_on_write_ptr + : public CopyOnWritePtrBase +{ +public: + typedef X value_type; + + copy_on_write_ptr () + : mp_holder (0) + { } + + copy_on_write_ptr (X *x) + : mp_holder (x ? new copy_on_write_holder (x) : 0) + { } + + explicit copy_on_write_ptr (const copy_on_write_ptr &other) + : mp_holder (other.mp_holder) + { + aquire (); + } + + copy_on_write_ptr &operator= (const copy_on_write_ptr &other) + { + if (this != &other) { + release (); + mp_holder = other.mp_holder; + aquire (); + } + return *this; + } + + ~copy_on_write_ptr () + { + release (); + } + + /** + * @brief Gets a writable object + * This is when we will create a new copy if the object is shared. + */ + X *get_non_const () + { + if (mp_holder) { + tl::MutexLocker locker (&ms_lock); + if (mp_holder->ref_count () > 1) { + X *x = mp_holder->x (); + mp_holder->dec_ref (); + mp_holder = new copy_on_write_holder (Dup () (*x)); + } + return mp_holder->x (); + } else { + return 0; + } + } + + /** + * @brief Gets the const pointer + * No copy will be created. + */ + const X *get_const () const + { + if (mp_holder) { + return mp_holder->x (); + } else { + return 0; + } + } + + /** + * @brief Sets the pointer + */ + void reset (X *x) + { + release (); + if (x) { + mp_holder = new copy_on_write_holder (x); + } + } + + /** + * @brief Gets the non-const pointer + * This is when we will create a new copy if the object is shared. + */ + X *operator-> () + { + return get_non_const (); + } + + /** + * @brief Gets the const pointer + * No copy will be created. + */ + const X *operator-> () const + { + return get_const (); + } + + /** + * @brief Gets the non-const reference + * This is when we will create a new copy if the object is shared. + */ + X &operator* () + { + return *get_non_const (); + } + + /** + * @brief Gets the const reference + * No copy will be created. + */ + const X &operator* () const + { + return *get_const (); + } + + /** + * @brief Debugging and testing: gets the reference count + */ + int ref_count () const + { + int rc = 0; + if (mp_holder) { + tl::MutexLocker locker (&ms_lock); + rc = mp_holder->ref_count (); + } + return rc; + } + +private: + X *mp_x; + copy_on_write_holder *mp_holder; + + void release () + { + if (mp_holder) { + tl::MutexLocker locker (&ms_lock); + if (mp_holder->dec_ref () <= 0) { + delete mp_holder; + } + mp_holder = 0; + } + } + + void aquire () + { + if (mp_holder) { + tl::MutexLocker locker (&ms_lock); + mp_holder->inc_ref (); + } + } +}; + +} + +#endif diff --git a/src/tl/unit_tests/tlCopyOnWriteTests.cc b/src/tl/unit_tests/tlCopyOnWriteTests.cc new file mode 100644 index 000000000..805b8d076 --- /dev/null +++ b/src/tl/unit_tests/tlCopyOnWriteTests.cc @@ -0,0 +1,213 @@ + +/* + + KLayout Layout Viewer + Copyright (C) 2006-2020 Matthias Koefferlein + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*/ + +#include "tlCopyOnWrite.h" +#include "tlUnitTest.h" + +// This namespace separates the test structs from other objects +namespace copy_on_write_tests +{ + +static int x_instances = 0; + +class X +{ +public: + X () { ++x_instances; } + virtual ~X () { --x_instances; } + virtual const char *name () const = 0; + virtual X *clone () const = 0; +}; + +class Y : public X +{ +public: + virtual const char *name () const { return "Y"; } + virtual X *clone () const { return new Y (); } +}; + +class Z : public X +{ +public: + virtual const char *name () const { return "Z"; } + virtual X *clone () const { return new Z (); } +}; + +static int a_instances = 0; + +class A +{ +public: + A () { ++a_instances; } + A (const A &) { ++a_instances; } + ~A () { --a_instances; } + const char *name () const { return "A"; } +}; + +} + +TEST(1) +{ + tl::copy_on_write_ptr ptr1, ptr2; + + EXPECT_EQ (copy_on_write_tests::a_instances, 0); + + ptr1.reset (new copy_on_write_tests::A ()); + EXPECT_EQ (copy_on_write_tests::a_instances, 1); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + EXPECT_EQ (ptr1.get_non_const ()->name (), "A"); + EXPECT_EQ (ptr1.ref_count (), 1); + + ptr2 = ptr1; + + EXPECT_EQ (ptr1.ref_count (), 2); + EXPECT_EQ (ptr2.ref_count (), 2); + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), true); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + + EXPECT_EQ (ptr1.get_non_const ()->name (), "A"); + EXPECT_EQ (copy_on_write_tests::a_instances, 2); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr2.ref_count (), 1); + + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), false); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + EXPECT_EQ (ptr2.get_const ()->name (), "A"); + + ptr1.reset (0); + EXPECT_EQ (copy_on_write_tests::a_instances, 1); + ptr2.reset (0); + EXPECT_EQ (copy_on_write_tests::a_instances, 0); +} + +TEST(2) +{ + tl::copy_on_write_ptr ptr1; + + EXPECT_EQ (copy_on_write_tests::a_instances, 0); + + ptr1.reset (new copy_on_write_tests::A ()); + EXPECT_EQ (copy_on_write_tests::a_instances, 1); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + EXPECT_EQ (ptr1.get_non_const ()->name (), "A"); + EXPECT_EQ (ptr1.ref_count (), 1); + + tl::copy_on_write_ptr ptr2 (ptr1); + + EXPECT_EQ (ptr1.ref_count (), 2); + EXPECT_EQ (ptr2.ref_count (), 2); + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), true); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + + EXPECT_EQ (ptr1.get_non_const ()->name (), "A"); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr2.ref_count (), 1); + + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), false); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + EXPECT_EQ (ptr2.get_const ()->name (), "A"); +} + +TEST(3) +{ + tl::copy_on_write_ptr ptr1; + + EXPECT_EQ (copy_on_write_tests::a_instances, 0); + + ptr1.reset (new copy_on_write_tests::A ()); + EXPECT_EQ (copy_on_write_tests::a_instances, 1); + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + EXPECT_EQ (ptr1.get_non_const ()->name (), "A"); + EXPECT_EQ (ptr1.ref_count (), 1); + + tl::copy_on_write_ptr ptr2 (ptr1); + + EXPECT_EQ (ptr1.ref_count (), 2); + EXPECT_EQ (ptr2.ref_count (), 2); + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), true); + EXPECT_EQ (ptr1.get_const ()->name (), "A"); + + ptr2.reset (0); + EXPECT_EQ (copy_on_write_tests::a_instances, 1); + + EXPECT_EQ (ptr1.get_non_const ()->name (), "A"); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr2.ref_count (), 0); + + EXPECT_EQ (ptr1.get_const () == 0, false); + EXPECT_EQ (ptr2.get_const () == 0, true); + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), false); + + ptr1.reset (0); + EXPECT_EQ (ptr1.ref_count (), 0); + EXPECT_EQ (copy_on_write_tests::a_instances, 0); +} + +TEST(4) +{ + tl::copy_on_write_ptr > ptr1, ptr2; + + EXPECT_EQ (copy_on_write_tests::x_instances, 0); + + ptr1.reset (new copy_on_write_tests::Y ()); + EXPECT_EQ (copy_on_write_tests::x_instances, 1); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr1.get_const ()->name (), "Y"); + EXPECT_EQ (ptr1.get_non_const ()->name (), "Y"); + EXPECT_EQ (ptr1.ref_count (), 1); + + ptr2 = ptr1; + + EXPECT_EQ (ptr1.ref_count (), 2); + EXPECT_EQ (ptr2.ref_count (), 2); + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), true); + EXPECT_EQ (ptr1.get_const ()->name (), "Y"); + + EXPECT_EQ (ptr1.get_non_const ()->name (), "Y"); + EXPECT_EQ (copy_on_write_tests::x_instances, 2); + + EXPECT_EQ (ptr1.ref_count (), 1); + EXPECT_EQ (ptr2.ref_count (), 1); + + EXPECT_EQ (ptr1.get_const () == ptr2.get_const (), false); + EXPECT_EQ (ptr1.get_const ()->name (), "Y"); + EXPECT_EQ (ptr2.get_const ()->name (), "Y"); + + ptr1.reset (0); + EXPECT_EQ (copy_on_write_tests::x_instances, 1); + ptr2.reset (new copy_on_write_tests::Z ()); + EXPECT_EQ (copy_on_write_tests::x_instances, 1); + EXPECT_EQ (ptr2.get_const ()->name (), "Z"); + + ptr2.reset (0); + EXPECT_EQ (copy_on_write_tests::x_instances, 0); +} + diff --git a/src/tl/unit_tests/unit_tests.pro b/src/tl/unit_tests/unit_tests.pro index 51ba34af4..43ca72765 100644 --- a/src/tl/unit_tests/unit_tests.pro +++ b/src/tl/unit_tests/unit_tests.pro @@ -10,6 +10,7 @@ SOURCES = \ tlAlgorithm.cc \ tlClassRegistry.cc \ tlCommandLineParser.cc \ + tlCopyOnWriteTests.cc \ tlDataMapping.cc \ tlDeflate.cc \ tlEvents.cc \