From patchwork Wed Sep 23 16:18:41 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonathan Wakely X-Patchwork-Id: 521762 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id C36401400A0 for ; Thu, 24 Sep 2015 02:18:54 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=gcc.gnu.org header.i=@gcc.gnu.org header.b=Sr8BMkHr; dkim-atps=neutral DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:date :from:to:subject:message-id:mime-version:content-type; q=dns; s= default; b=YN8bAEbZHi2RNh6+r3CUJUrA1JYPi0buggSjFArPZ5emLE16x5FHh q/cXRqNfiPKnc1+sYTY7ZX5zNzoWllvwnwISYghMaXO6OtYkvOfvdCRsUCapMM8f NYxfmgYlzckt4x8OSF6sCED1SFYR+itUP7unP811O625otw/GjN8c4= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender:date :from:to:subject:message-id:mime-version:content-type; s= default; bh=IfMqJlmsgzMTT+b318XHKqVX/Tg=; b=Sr8BMkHrlOf81fV2HiNa gNKXrlMqwA1/rpg3gKPKwF0An9vAauV5iOTIOcOJ8qmZS3W2K5OdCXh7NBqivFFk HBwfpSe7QNechYgHRlJIVT41vt6s8QIuapDTqiwusyqL+eHxXO5JVU/nmffukkDZ +xEIXNz2FslTOrXq/M3cBZo= Received: (qmail 16051 invoked by alias); 23 Sep 2015 16:18:45 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 16034 invoked by uid 89); 23 Sep 2015 16:18:45 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=0.1 required=5.0 tests=AWL, BAYES_50, KAM_LAZY_DOMAIN_SECURITY, RP_MATCHES_RCVD, SPF_HELO_PASS autolearn=no version=3.3.2 X-Spam-User: qpsmtpd, 2 recipients X-HELO: mx1.redhat.com Received: from mx1.redhat.com (HELO mx1.redhat.com) (209.132.183.28) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES256-GCM-SHA384 encrypted) ESMTPS; Wed, 23 Sep 2015 16:18:43 +0000 Received: from int-mx10.intmail.prod.int.phx2.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.23]) by mx1.redhat.com (Postfix) with ESMTPS id 3AB2B92486; Wed, 23 Sep 2015 16:18:42 +0000 (UTC) Received: from localhost (ovpn-116-78.ams2.redhat.com [10.36.116.78]) by int-mx10.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id t8NGIf0T012977; Wed, 23 Sep 2015 12:18:41 -0400 Date: Wed, 23 Sep 2015 17:18:41 +0100 From: Jonathan Wakely To: libstdc++@gcc.gnu.org, gcc-patches@gcc.gnu.org Subject: [patch] Reduce space and time overhead of std::thread Message-ID: <20150923161841.GC2969@redhat.com> MIME-Version: 1.0 Content-Disposition: inline X-Clacks-Overhead: GNU Terry Pratchett User-Agent: Mutt/1.5.23 (2014-03-12) For PR 65393 I avoided some unnecessary shared_ptr copies while launching a std::thread. This goes further and avoids shared_ptr entirely, using unique_ptr instead. This reduces the memory overhead of a std::thread by 32 bytes (on 64-bit) and avoids any reference-count updates. The downside is it exports some new symbols, and we have to keep the old code for backwards compatibility, but I think it's worth doing. Does anybody disagree? commit 2d7e89aae8ac12dd7a6b2083e5169679c1200cc5 Author: Jonathan Wakely Date: Thu Mar 12 13:23:23 2015 +0000 Reduce space and time overhead of std::thread PR libstdc++/65393 * config/abi/pre/gnu.ver: Export new symbols. * include/std/thread (thread::_State, thread::_State_impl): New types. (thread::_M_start_thread): Add overload taking unique_ptr<_State>. (thread::_M_make_routine): Remove. (thread::_S_make_state): Add. (thread::_Impl_base, thread::_Impl, thread::_M_start_thread) [_GLIBCXX_THREAD_ABI_COMPAT] Only declare conditionally. * src/c++11/thread.cc (execute_native_thread_routine): Rename to execute_native_thread_routine_compat and re-define to use _State. (thread::_State::~_State()): Define. (thread::_M_make_thread): Define new overload. (thread::_M_make_thread) [_GLIBCXX_THREAD_ABI_COMPAT]: Only define old overloads conditionally. diff --git a/libstdc++-v3/config/abi/pre/gnu.ver b/libstdc++-v3/config/abi/pre/gnu.ver index d42cd37..08d9bc6 100644 --- a/libstdc++-v3/config/abi/pre/gnu.ver +++ b/libstdc++-v3/config/abi/pre/gnu.ver @@ -1870,6 +1870,11 @@ GLIBCXX_3.4.22 { # std::uncaught_exceptions() _ZSt19uncaught_exceptionsv; + # std::thread::_State::~_State() + _ZT[ISV]NSt6thread6_StateE; + _ZNSt6thread6_StateD[012]Ev; + _ZNSt6thread15_M_start_threadESt10unique_ptrINS_6_StateESt14default_deleteIS1_EEPFvvE; + } GLIBCXX_3.4.21; # Symbols in the support library (libsupc++) have their own tag. diff --git a/libstdc++-v3/include/std/thread b/libstdc++-v3/include/std/thread index ebbda62..c67ec46 100644 --- a/libstdc++-v3/include/std/thread +++ b/libstdc++-v3/include/std/thread @@ -60,9 +60,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class thread { public: + // Abstract base class for types that wrap arbitrary functors to be + // invoked in the new thread of execution. + struct _State + { + virtual ~_State(); + virtual void _M_run() = 0; + }; + using _State_ptr = unique_ptr<_State>; + typedef __gthread_t native_handle_type; - struct _Impl_base; - typedef shared_ptr<_Impl_base> __shared_base_type; /// thread::id class id @@ -92,29 +99,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION operator<<(basic_ostream<_CharT, _Traits>& __out, thread::id __id); }; - // Simple base type that the templatized, derived class containing - // an arbitrary functor can be converted to and called. - struct _Impl_base - { - __shared_base_type _M_this_ptr; - - inline virtual ~_Impl_base(); - - virtual void _M_run() = 0; - }; - - template - struct _Impl : public _Impl_base - { - _Callable _M_func; - - _Impl(_Callable&& __f) : _M_func(std::forward<_Callable>(__f)) - { } - - void - _M_run() { _M_func(); } - }; - private: id _M_id; @@ -133,16 +117,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION thread(_Callable&& __f, _Args&&... __args) { #ifdef GTHR_ACTIVE_PROXY - // Create a reference to pthread_create, not just the gthr weak symbol - _M_start_thread(_M_make_routine(std::__bind_simple( - std::forward<_Callable>(__f), - std::forward<_Args>(__args)...)), - reinterpret_cast(&pthread_create)); + // Create a reference to pthread_create, not just the gthr weak symbol. + auto __depend = reinterpret_cast(&pthread_create); #else - _M_start_thread(_M_make_routine(std::__bind_simple( - std::forward<_Callable>(__f), - std::forward<_Args>(__args)...))); + auto __depend = nullptr; #endif + _M_start_thread(_S_make_state( + std::__bind_simple(std::forward<_Callable>(__f), + std::forward<_Args>(__args)...)), + __depend); } ~thread() @@ -190,23 +173,48 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION hardware_concurrency() noexcept; private: + template + struct _State_impl : public _State + { + _Callable _M_func; + + _State_impl(_Callable&& __f) : _M_func(std::forward<_Callable>(__f)) + { } + + void + _M_run() { _M_func(); } + }; + + void + _M_start_thread(_State_ptr, void (*)()); + + template + static _State_ptr + _S_make_state(_Callable&& __f) + { + using _Impl = _State_impl<_Callable>; + return _State_ptr{new _Impl{std::forward<_Callable>(__f)}}; + } +#if _GLIBCXX_THREAD_ABI_COMPAT + public: + struct _Impl_base; + typedef shared_ptr<_Impl_base> __shared_base_type; + struct _Impl_base + { + __shared_base_type _M_this_ptr; + virtual ~_Impl_base() = default; + virtual void _M_run() = 0; + }; + + private: void _M_start_thread(__shared_base_type, void (*)()); void _M_start_thread(__shared_base_type); - - template - shared_ptr<_Impl<_Callable>> - _M_make_routine(_Callable&& __f) - { - // Create and allocate full data structure, not base. - return std::make_shared<_Impl<_Callable>>(std::forward<_Callable>(__f)); - } +#endif }; - inline thread::_Impl_base::~_Impl_base() = default; - inline void swap(thread& __x, thread& __y) noexcept { __x.swap(__y); } diff --git a/libstdc++-v3/src/c++11/thread.cc b/libstdc++-v3/src/c++11/thread.cc index 906cafa..e116afa 100644 --- a/libstdc++-v3/src/c++11/thread.cc +++ b/libstdc++-v3/src/c++11/thread.cc @@ -23,6 +23,7 @@ // . +#define _GLIBCXX_THREAD_ABI_COMPAT 1 #include #include #include @@ -75,8 +76,33 @@ namespace std _GLIBCXX_VISIBILITY(default) extern "C" void* execute_native_thread_routine(void* __p) { + thread::_State_ptr __t{ static_cast(__p) }; + + __try + { + __t->_M_run(); + } + __catch(const __cxxabiv1::__forced_unwind&) + { + __throw_exception_again; + } + __catch(...) + { + std::terminate(); + } + + return nullptr; + } + +#if _GLIBCXX_THREAD_ABI_COMPAT + extern "C" void* + execute_native_thread_routine_compat(void* __p) + { thread::_Impl_base* __t = static_cast(__p); thread::__shared_base_type __local; + // Now that a new thread has been created we can transfer ownership of + // the thread state to a local object, breaking the reference cycle + // created in thread::_M_start_thread. __local.swap(__t->_M_this_ptr); __try @@ -94,10 +120,13 @@ namespace std _GLIBCXX_VISIBILITY(default) return nullptr; } +#endif } _GLIBCXX_BEGIN_NAMESPACE_VERSION + thread::_State::~_State() = default; + void thread::join() { @@ -127,6 +156,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } void + thread::_M_start_thread(_State_ptr state, void (*)()) + { + const int err = __gthread_create(&_M_id._M_thread, + &execute_native_thread_routine, + state.get()); + if (err) + __throw_system_error(err); + state.release(); + } + +#if _GLIBCXX_THREAD_ABI_COMPAT + void thread::_M_start_thread(__shared_base_type __b) { if (!__gthread_active_p()) @@ -144,15 +185,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION thread::_M_start_thread(__shared_base_type __b, void (*)()) { auto ptr = __b.get(); + // Create a reference cycle that will be broken in the new thread. ptr->_M_this_ptr = std::move(__b); int __e = __gthread_create(&_M_id._M_thread, - &execute_native_thread_routine, ptr); + &execute_native_thread_routine_compat, ptr); if (__e) { - ptr->_M_this_ptr.reset(); + ptr->_M_this_ptr.reset(); // break reference cycle, destroying *ptr. __throw_system_error(__e); } } +#endif unsigned int thread::hardware_concurrency() noexcept