From 034c12d2442389d6d89bccf81b0319e4ef683e63 Mon Sep 17 00:00:00 2001 From: Peter Dimov Date: Sat, 12 Jul 2008 11:57:45 +0000 Subject: [PATCH] Merged 44636, 44640, 45094 (atomic access) from trunk to release. [SVN r47349] --- include/boost/memory_order.hpp | 35 +++++ include/boost/shared_ptr.hpp | 64 +++++++++ test/Jamfile.v2 | 1 + test/sp_atomic_mt2_test.cpp | 247 +++++++++++++++++++++++++++++++++ test/sp_atomic_mt_test.cpp | 191 +++++++++++++++++++++++++ test/sp_atomic_test.cpp | 87 ++++++++++++ 6 files changed, 625 insertions(+) create mode 100644 include/boost/memory_order.hpp create mode 100644 test/sp_atomic_mt2_test.cpp create mode 100644 test/sp_atomic_mt_test.cpp create mode 100644 test/sp_atomic_test.cpp diff --git a/include/boost/memory_order.hpp b/include/boost/memory_order.hpp new file mode 100644 index 0000000..be5a9ce --- /dev/null +++ b/include/boost/memory_order.hpp @@ -0,0 +1,35 @@ +#ifndef BOOST_MEMORY_ORDER_HPP_INCLUDED +#define BOOST_MEMORY_ORDER_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// boost/memory_order.hpp +// +// Defines enum boost::memory_order per the C++0x working draft +// +// Copyright (c) 2008 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + + +namespace boost +{ + +enum memory_order +{ + memory_order_relaxed = 0, + memory_order_acquire = 1, + memory_order_release = 2, + memory_order_acq_rel = 3, // acquire | release + memory_order_seq_cst = 7, // acq_rel | 4 +}; + +} // namespace boost + +#endif // #ifndef BOOST_MEMORY_ORDER_HPP_INCLUDED diff --git a/include/boost/shared_ptr.hpp b/include/boost/shared_ptr.hpp index 5a172b9..394e30c 100644 --- a/include/boost/shared_ptr.hpp +++ b/include/boost/shared_ptr.hpp @@ -33,6 +33,11 @@ #include #include +#if !defined(BOOST_SP_NO_ATOMIC_ACCESS) +#include +#include +#endif + #include // for std::swap #include // for std::less #include // for std::bad_cast @@ -498,6 +503,65 @@ public: return pn.get_deleter( ti ); } + // atomic access + +#if !defined(BOOST_SP_NO_ATOMIC_ACCESS) + + shared_ptr atomic_load( memory_order /*mo*/ = memory_order_seq_cst ) const + { + boost::detail::spinlock_pool<2>::scoped_lock lock( this ); + return *this; + } + + void atomic_store( shared_ptr r, memory_order /*mo*/ = memory_order_seq_cst ) + { + boost::detail::spinlock_pool<2>::scoped_lock lock( this ); + swap( r ); + } + + shared_ptr atomic_swap( shared_ptr r, memory_order /*mo*/ = memory_order_seq_cst ) + { + boost::detail::spinlock & sp = boost::detail::spinlock_pool<2>::spinlock_for( this ); + + sp.lock(); + swap( r ); + sp.unlock(); + + return r; // return std::move(r) + } + + bool atomic_compare_swap( shared_ptr & v, shared_ptr w ) + { + boost::detail::spinlock & sp = boost::detail::spinlock_pool<2>::spinlock_for( this ); + + sp.lock(); + + if( px == v.px && pn == v.pn ) + { + swap( w ); + + sp.unlock(); + + return true; + } + else + { + shared_ptr tmp( *this ); + + sp.unlock(); + + tmp.swap( v ); + return false; + } + } + + inline bool atomic_compare_swap( shared_ptr & v, shared_ptr w, memory_order /*success*/, memory_order /*failure*/ ) + { + return atomic_compare_swap( v, w ); // std::move( w ) + } + +#endif + // Tasteless as this may seem, making all members public allows member templates // to work in the absence of member template friends. (Matthew Langston) diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 955b282..5f68e20 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -47,5 +47,6 @@ import testing ; [ run wp_convertible_test.cpp ] [ run ip_convertible_test.cpp ] [ run allocate_shared_test.cpp ] + [ run sp_atomic_test.cpp ] ; } diff --git a/test/sp_atomic_mt2_test.cpp b/test/sp_atomic_mt2_test.cpp new file mode 100644 index 0000000..c865359 --- /dev/null +++ b/test/sp_atomic_mt2_test.cpp @@ -0,0 +1,247 @@ + +// Copyright (c) 2008 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +// + +static void next_value( unsigned & v ) +{ + v = v % 2? 3 * v + 1: v / 2; +} + +struct X +{ + std::vector v_; + + explicit X( std::size_t n ): v_( n ) + { + for( std::size_t i = 0; i < n; ++i ) + { + v_[ i ] = i; + } + } + + unsigned get() const + { + return std::accumulate( v_.begin(), v_.end(), 0 ); + } + + void set() + { + std::for_each( v_.begin(), v_.end(), next_value ); + } +}; + +static boost::shared_ptr ps; + +static boost::detail::lightweight_mutex lm; +static boost::shared_mutex rw; + +enum prim_type +{ + pt_mutex, + pt_rwlock, + pt_atomics +}; + +int read_access( prim_type pt ) +{ + switch( pt ) + { + case pt_mutex: + { + boost::detail::lightweight_mutex::scoped_lock lock( lm ); + return ps->get(); + } + + case pt_rwlock: + { + boost::shared_lock lock( rw ); + return ps->get(); + } + + case pt_atomics: + { + boost::shared_ptr p2 = ps.atomic_load(); + return p2->get(); + } + } +} + +void write_access( prim_type pt ) +{ + switch( pt ) + { + case pt_mutex: + { + boost::detail::lightweight_mutex::scoped_lock lock( lm ); + ps->set(); + } + break; + + case pt_rwlock: + { + boost::unique_lock lock( rw ); + ps->set(); + } + break; + + case pt_atomics: + { + boost::shared_ptr p1 = ps.atomic_load(); + + for( ;; ) + { + boost::shared_ptr p2( new X( *p1 ) ); + p2->set(); + + if( ps.atomic_compare_swap( p1, p2 ) ) break; + } + } + break; + } +} + +void worker( int k, prim_type pt, int n, int r ) +{ + ++r; + + unsigned s = 0, nr = 0, nw = 0; + + for( int i = 0; i < n; ++i ) + { + if( i % r ) + { + s += read_access( pt ); + ++nr; + } + else + { + write_access( pt ); + ++s; + ++nw; + } + } + + printf( "Worker %2d: %u:%u, %10u\n", k, nr, nw, s ); +} + +#if defined( BOOST_HAS_PTHREADS ) + char const * thmodel = "POSIX"; +#else + char const * thmodel = "Windows"; +#endif + +char const * pt_to_string( prim_type pt ) +{ + switch( pt ) + { + case pt_mutex: + + return "mutex"; + + case pt_rwlock: + + return "rwlock"; + + case pt_atomics: + + return "atomics"; + } +} + +static void handle_pt_option( std::string const & opt, prim_type & pt, prim_type pt2 ) +{ + if( opt == pt_to_string( pt2 ) ) + { + pt = pt2; + } +} + +static void handle_int_option( std::string const & opt, std::string const & prefix, int & k, int kmin, int kmax ) +{ + if( opt.substr( 0, prefix.size() ) == prefix ) + { + int v = atoi( opt.substr( prefix.size() ).c_str() ); + + if( v >= kmin && v <= kmax ) + { + k = v; + } + } +} + +int main( int ac, char const * av[] ) +{ + using namespace std; // printf, clock_t, clock + + int m = 4; // threads + int n = 10000; // vector size + int k = 1000000; // iterations + int r = 100; // read/write ratio, r:1 + + prim_type pt = pt_atomics; + + for( int i = 1; i < ac; ++i ) + { + handle_pt_option( av[i], pt, pt_mutex ); + handle_pt_option( av[i], pt, pt_rwlock ); + handle_pt_option( av[i], pt, pt_atomics ); + + handle_int_option( av[i], "n=", n, 1, INT_MAX ); + handle_int_option( av[i], "size=", n, 1, INT_MAX ); + + handle_int_option( av[i], "k=", k, 1, INT_MAX ); + handle_int_option( av[i], "iterations=", k, 1, INT_MAX ); + + handle_int_option( av[i], "m=", m, 1, INT_MAX ); + handle_int_option( av[i], "threads=", m, 1, INT_MAX ); + + handle_int_option( av[i], "r=", r, 1, INT_MAX ); + handle_int_option( av[i], "ratio=", r, 1, INT_MAX ); + } + + printf( "%s: threads=%d size=%d iterations=%d ratio=%d %s\n\n", thmodel, m, n, k, r, pt_to_string( pt ) ); + + ps.reset( new X( n ) ); + + clock_t t = clock(); + + std::vector a( m ); + + for( int i = 0; i < m; ++i ) + { + boost::detail::lw_thread_create( a[ i ], boost::bind( worker, i, pt, k, r ) ); + } + + for( int j = 0; j < m; ++j ) + { + pthread_join( a[ j ], 0 ); + } + + t = clock() - t; + + double ts = static_cast( t ) / CLOCKS_PER_SEC; + printf( "%.3f seconds, %.3f accesses per microsecond.\n", ts, m * k / ts / 1e+6 ); +} diff --git a/test/sp_atomic_mt_test.cpp b/test/sp_atomic_mt_test.cpp new file mode 100644 index 0000000..143df98 --- /dev/null +++ b/test/sp_atomic_mt_test.cpp @@ -0,0 +1,191 @@ + +// Copyright (c) 2008 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +//#define USE_MUTEX +//#define USE_RWLOCK + +#include + +#include +#include + +#if defined( USE_RWLOCK ) +#include +#include +#endif + +#include +#include +#include + +#include +#include + +// + +int const n = 1024 * 1024; + +struct X +{ + int v_; // version + + unsigned a_; + unsigned b_; + + X(): v_( 0 ), a_( 1 ), b_( 1 ) + { + } + + int get() const + { + return a_ * 7 + b_ * 11; + } + + void set() + { + int tmp = get(); + + b_ = a_; + a_ = tmp; + + ++v_; + } +}; + +static boost::shared_ptr ps( new X ); + +static boost::detail::lightweight_mutex lm; + +#if defined( USE_RWLOCK ) +static boost::shared_mutex rw; +#endif + +static int tr = 0; + +void reader( int r ) +{ + int k = 0; + unsigned s = 0; + + for( int i = 0; i < n; ++k ) + { +#if defined( USE_MUTEX ) + + boost::detail::lightweight_mutex::scoped_lock lock( lm ); + + s += ps->get(); + + BOOST_TEST( ps->v_ >= i ); + i = ps->v_; + +#elif defined( USE_RWLOCK ) + + boost::shared_lock lock( rw ); + + s += ps->get(); + + BOOST_TEST( ps->v_ >= i ); + i = ps->v_; + +#else + + boost::shared_ptr p2 = ps.atomic_load(); + + s += p2->get(); + + BOOST_TEST( p2->v_ >= i ); + i = p2->v_; + +#endif + } + + printf( "Reader %d: %9d iterations (%6.3fx), %u\n", r, k, (double)k / n, s ); + + boost::detail::lightweight_mutex::scoped_lock lock( lm ); + tr += k; +} + +void writer() +{ + for( int i = 0; i < n; ++i ) + { +#if defined( USE_MUTEX ) + + boost::detail::lightweight_mutex::scoped_lock lock( lm ); + + BOOST_TEST( ps->v_ == i ); + ps->set(); + +#elif defined( USE_RWLOCK ) + + boost::unique_lock lock( rw ); + + BOOST_TEST( ps->v_ == i ); + ps->set(); + +#else + + boost::shared_ptr p2( new X( *ps ) ); + + BOOST_TEST( p2->v_ == i ); + p2->set(); + + ps.atomic_store( p2 ); + +#endif + } +} + +#if defined( BOOST_HAS_PTHREADS ) + char const * thmodel = "POSIX"; +#else + char const * thmodel = "Windows"; +#endif + +int const mr = 8; // reader threads +int const mw = 1; // writer thread + +#if defined( USE_MUTEX ) + char const * prim = "mutex"; +#elif defined( USE_RWLOCK ) + char const * prim = "rwlock"; +#else + char const * prim = "atomics"; +#endif + +int main() +{ + using namespace std; // printf, clock_t, clock + + printf( "Using %s threads: %dR + %dW threads, %d iterations, %s\n\n", thmodel, mr, mw, n, prim ); + + clock_t t = clock(); + + pthread_t a[ mr+mw ]; + + for( int i = 0; i < mr; ++i ) + { + boost::detail::lw_thread_create( a[ i ], boost::bind( reader, i ) ); + } + + for( int i = mr; i < mr+mw; ++i ) + { + boost::detail::lw_thread_create( a[ i ], writer ); + } + + for( int j = 0; j < mr+mw; ++j ) + { + pthread_join( a[ j ], 0 ); + } + + t = clock() - t; + + double ts = static_cast( t ) / CLOCKS_PER_SEC; + printf( "%.3f seconds, %.3f reads per microsecond.\n", ts, tr / ts / 1e+6 ); + + return boost::report_errors(); +} diff --git a/test/sp_atomic_test.cpp b/test/sp_atomic_test.cpp new file mode 100644 index 0000000..c1113da --- /dev/null +++ b/test/sp_atomic_test.cpp @@ -0,0 +1,87 @@ +#include + +// sp_atomic_test.cpp +// +// Copyright (c) 2008 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + + +#include +#include + +// + +struct X +{ +}; + +#define BOOST_TEST_SP_EQ( p, q ) BOOST_TEST( p == q && !( p < q ) && !( q < p ) ) + +int main() +{ + boost::shared_ptr px( new X ); + + { + boost::shared_ptr p2 = px.atomic_load(); + BOOST_TEST_SP_EQ( p2, px ); + + boost::shared_ptr px2( new X ); + px.atomic_store( px2 ); + BOOST_TEST_SP_EQ( px, px2 ); + + p2 = px.atomic_load(); + BOOST_TEST_SP_EQ( p2, px ); + BOOST_TEST_SP_EQ( p2, px2 ); + + boost::shared_ptr px3( new X ); + boost::shared_ptr p3 = px.atomic_swap( px3 ); + BOOST_TEST_SP_EQ( p3, px2 ); + BOOST_TEST_SP_EQ( px, px3 ); + + boost::shared_ptr px4( new X ); + boost::shared_ptr cmp; + + bool r = px.atomic_compare_swap( cmp, px4 ); + BOOST_TEST( !r ); + BOOST_TEST_SP_EQ( px, px3 ); + BOOST_TEST_SP_EQ( cmp, px3 ); + + r = px.atomic_compare_swap( cmp, px4 ); + BOOST_TEST( r ); + BOOST_TEST_SP_EQ( px, px4 ); + } + + // + + px.reset(); + + { + boost::shared_ptr p2 = px.atomic_load( boost::memory_order_acquire ); + BOOST_TEST_SP_EQ( p2, px ); + + boost::shared_ptr px2( new X ); + px.atomic_store( px2, boost::memory_order_release ); + BOOST_TEST_SP_EQ( px, px2 ); + + boost::shared_ptr p3 = px.atomic_swap( boost::shared_ptr(), boost::memory_order_acq_rel ); + BOOST_TEST_SP_EQ( p3, px2 ); + BOOST_TEST_SP_EQ( px, p2 ); + + boost::shared_ptr px4( new X ); + boost::shared_ptr cmp( px2 ); + + bool r = px.atomic_compare_swap( cmp, px4, boost::memory_order_acquire, boost::memory_order_relaxed ); + BOOST_TEST( !r ); + BOOST_TEST_SP_EQ( px, p2 ); + BOOST_TEST_SP_EQ( cmp, p2 ); + + r = px.atomic_compare_swap( cmp, px4, boost::memory_order_release, boost::memory_order_acquire ); + BOOST_TEST( r ); + BOOST_TEST_SP_EQ( px, px4 ); + } + + return boost::report_errors(); +}