// bslmt_meteredmutex.h -*-C++-*- // ---------------------------------------------------------------------------- // NOTICE // // This component is not up to date with current BDE coding standards, and // should not be used as an example for new development. // ---------------------------------------------------------------------------- #ifndef INCLUDED_BSLMT_METEREDMUTEX #define INCLUDED_BSLMT_METEREDMUTEX #include <bsls_ident.h> BSLS_IDENT("$Id: $") //@PURPOSE: Provide a mutex capable of keeping track of wait and hold time. // //@CLASSES: // bslmt::MeteredMutex: mutex capable of keeping track of wait and hold time // //@SEE_ALSO: // //@DESCRIPTION: This component provides a class, 'bslmt::MeteredMutex', that // functions as a mutex and has additional capability to keep track of wait // time and hold time. This class can be used, for example, in evaluating the // performance of an application, based on its lock contention behavior. // ///Precise Definitions of Wait and Hold Time ///----------------------------------------- // Wait time is defined as the sum of the time intervals between each call to // 'lock' (or 'tryLock') on the underlying mutex and the return of that call. // Note that if one or more threads are waiting for the lock at the point when // 'waitTime' is called, those waiting time intervals are *not* included in the // returned wait time. Hold time is defined as the sum of the time intervals // between return from each call to 'lock' (or a successful call to 'tryLock') // on the underlying mutex and the subsequent call to 'unlock'. Note that if a // thread is holding the lock at the point when 'holdTime' is called, then that // holding time is *not* included in the returned hold time. // ///Performance ///----------- // It should be noted that the overhead in keeping track of wait and hold time // is very small. We do not use additional mutexes to manipulate these times, // instead, we use atomic data types (which have very small overhead compared // to a mutex) to update these times atomically. // ///Inaccuracy of 'waitTime' and 'holdTime' ///--------------------------------------- // Times reported by 'waitTime' and 'holdTime' are (close) approximate times // and *not* 100% accurate. This inaccuracy can sometime cause surprising // behavior. For example, one can incorrectly assume 'lock()' and // 'while (tryLock() != 0);' to be effectively the same (both disallowing the // thread to advance until the lock is acquired) but the wait time reported in // the first case can be much more accurate than that of the second because the // 'lock' is called only once (and thus computation error is introduced only // once) in the first case. // ///Usage ///----- // In the following example, we have 'NUM_THREADS' threads (that are // sequentially numbered from '0' to 'NUM_THREADS-1') and two counters // 'evenCount' and 'oddCount'. 'evenCount' is incremented by the even numbered // threads and 'oddCount' is incremented by the odd ones. We considers two // strategies to increment these counters. In the first strategy (strategy1), // we use two mutexes (one for each counter) and in the second strategy // (strategy2), we use a single mutex for both counters. //.. // int oddCount = 0; // int evenCount = 0; // // typedef bslmt::MeteredMutex Obj; // Obj oddMutex; // Obj evenMutex; // Obj globalMutex; // // enum { k_USAGE_NUM_THREADS = 4, k_USAGE_SLEEP_TIME = 100000 }; // bslmt::Barrier usageBarrier(k_USAGE_NUM_THREADS); // // void executeInParallel(int numThreads, // bslmt::ThreadUtil::ThreadFunction function) // // Create the specified 'numThreads', each executing the specified // // 'function'. Number each thread (sequentially from 0 to // // 'numThreads - 1') by passing i to i'th thread. Finally join all the // // threads. // { // bslmt::ThreadUtil::Handle *threads = // new bslmt::ThreadUtil::Handle[numThreads]; // assert(threads); // // for (int i = 0; i < numThreads; ++i) { // bslmt::ThreadUtil::create(&threads[i], function, (void*)i); // } // for (int i = 0; i < numThreads; ++i) { // bslmt::ThreadUtil::join(threads[i]); // } // // delete [] threads; // } // // extern "C" { // void *strategy1(void *arg) // { // usageBarrier.wait(); // int remainder = (int)(bsls::Types::IntPtr)arg % 2; // if (remainder == 1) { // oddMutex.lock(); // ++oddCount; // bslmt::ThreadUtil::microSleep(k_USAGE_SLEEP_TIME); // oddMutex.unlock(); // } // else { // evenMutex.lock(); // ++evenCount; // bslmt::ThreadUtil::microSleep(k_USAGE_SLEEP_TIME); // evenMutex.unlock(); // } // return NULL; // } // } // extern "C" // // extern "C" { // void *strategy2(void *arg) // { // usageBarrier.wait(); // int remainder = (int)(bsls::Types::IntPtr)arg % 2; // if (remainder == 1) { // globalMutex.lock(); // ++oddCount; // bslmt::ThreadUtil::microSleep(k_USAGE_SLEEP_TIME); // globalMutex.unlock(); // } // else { // globalMutex.lock(); // ++evenCount; // bslmt::ThreadUtil::microSleep(k_USAGE_SLEEP_TIME); // globalMutex.unlock(); // } // return NULL; // } // } // extern "C" //.. // Then in the application 'main': //.. // executeInParallel(k_USAGE_NUM_THREADS, strategy1); // bsls::Types::Int64 waitTimeForStrategy1 = // oddMutex.waitTime() + evenMutex.waitTime(); // // executeInParallel(k_USAGE_NUM_THREADS, strategy2); // bsls::Types::Int64 waitTimeForStrategy2 = globalMutex.waitTime(); // // assert(waitTimeForStrategy2 > waitTimeForStrategy1); // if (veryVerbose) { // P(waitTimeForStrategy1); // P(waitTimeForStrategy2); // } //.. // We measured the wait times for each strategy. Intuitively, the wait time // for the second strategy should be greater than that of the first. The // output was consistent with our expectation. //.. // waitTimeForStrategy1 = 400787000 // waitTimeForStrategy2 = 880765000 //.. #include <bslscm_version.h> #include <bslmt_mutex.h> #include <bsls_atomic.h> #include <bsls_timeutil.h> #include <bsls_types.h> namespace BloombergLP { namespace bslmt { // ================== // class MeteredMutex // ================== class MeteredMutex { // This class implements a mutex, that has the additional capability to // keep track of hold time and wait time. The hold time is defined as the // cumulative duration for which the mutex was in the locked state. The // wait time is defined as the duration for which threads waited for the // mutex. // DATA Mutex d_mutex; // underlying mutex bsls::AtomicInt64 d_waitTime; // wait time bsls::AtomicInt64 d_holdTime; // hold time bsls::Types::Int64 d_startHoldTime; // starting point of hold time bsls::AtomicInt64 d_lastResetTime; // last reset time // NOT IMPLEMENTED MeteredMutex(const MeteredMutex&); MeteredMutex& operator=(const MeteredMutex&); public: // CREATORS MeteredMutex(); // Create a metered mutex in the unlocked state. ~MeteredMutex(); // Destroy this metered mutex. // MANIPULATORS void lock(); // Acquire the lock on this metered mutex. If this mutex is currently // locked, suspend the execution of the current thread until the lock // can be acquired. Update the wait and hold time appropriately. The // behavior is undefined if the calling thread already owns the lock. void resetMetrics(); // Reset the wait and hold time to zero and record the current time. // All subsequent calls (that are made before a subsequent call to // 'resetMetrics') to 'waitTime' (or 'holdTime') will return the wait // (or hold) time, accumulated since this call. Also, all subsequent // calls (that are made before a subsequent call to 'resetMetrics') to // 'lastResetTime' will return the time of this call. int tryLock(); // Attempt to acquire the lock on this metered mutex. Return 0 on // success, and a non-zero value if this mutex is already locked, or if // an error occurs. Update the wait and hold time appropriately. The // behavior is undefined if the calling thread already owns the lock. void unlock(); // Release the lock on this mutex that was previously acquired through // a successful call to 'lock' or 'tryLock'. Update the hold time // appropriately. The behavior is undefined unless the calling thread // currently owns the lock. // ACCESSORS bsls::Types::Int64 holdTime() const; // Return the hold time (in nanoseconds) accumulated since the most // recent call to 'resetMetrics' (or 'MeteredMutex' if 'resetMetrics' // was never called). bsls::Types::Int64 lastResetTime() const; // Return the time in nanoseconds (referenced to an arbitrary but fixed // origin) of the most recent invocation to 'resetMetrics' (or creation // time if 'resetMetrics' was never invoked). User can calculate the // difference (in nanoseconds) between the current time and the last // reset time by expression // 'bsls::TimeUtil::getTimer() - clientMutex.lastResetTime()'. bsls::Types::Int64 waitTime() const; // Return the wait time (in nanoseconds), accumulated since the most // recent call to 'resetMetrics' (or 'MeteredMutex' if 'resetMetrics' // was never called). }; } // close package namespace // ============================================================================ // INLINE DEFINITIONS // ============================================================================ // ------------ // MeteredMutex // ------------ // CREATORS inline bslmt::MeteredMutex::MeteredMutex() : d_lastResetTime(bsls::TimeUtil::getTimer()) { } inline bslmt::MeteredMutex::~MeteredMutex() { } // MANIPULATORS inline void bslmt::MeteredMutex::lock() { bsls::Types::Int64 t1 = bsls::TimeUtil::getTimer(); d_mutex.lock(); d_startHoldTime = bsls::TimeUtil::getTimer(); d_waitTime += (d_startHoldTime - t1); } inline int bslmt::MeteredMutex::tryLock() { bsls::Types::Int64 t1 = bsls::TimeUtil::getTimer(); int returnStatus = d_mutex.tryLock(); bsls::Types::Int64 t2 = bsls::TimeUtil::getTimer(); d_waitTime += t2 - t1; if (returnStatus == 0) { d_startHoldTime = t2; } return returnStatus; } inline void bslmt::MeteredMutex::unlock() { d_holdTime += (bsls::TimeUtil::getTimer() - d_startHoldTime); d_mutex.unlock(); } // ACCESSORS inline bsls::Types::Int64 bslmt::MeteredMutex::holdTime() const { return d_holdTime; } inline bsls::Types::Int64 bslmt::MeteredMutex::lastResetTime() const { return d_lastResetTime; } inline bsls::Types::Int64 bslmt::MeteredMutex::waitTime() const { return d_waitTime; } } // close enterprise namespace #endif // ---------------------------------------------------------------------------- // Copyright 2015 Bloomberg Finance L.P. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ----------------------------- END-OF-FILE ----------------------------------