mirror of https://github.com/opencv/opencv.git
- move TLS & instrumentation code out of core/utility.hpp - (*) TLSData lost .gather() method (to dispose thread data on thread termination) - use TLSDataAccumulator for reliable collecting of thread data - prefer using of .detachData() + .cleanupDetachedData() instead of .gather() method (*) API is broken: replace TLSData => TLSDataAccumulator if gather required (objects disposal on threads termination is not available in accumulator mode)pull/15692/head
parent
dd4f591d54
commit
17e2bf5717
14 changed files with 650 additions and 239 deletions
@ -0,0 +1,125 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef OPENCV_UTILS_INSTR_HPP |
||||
#define OPENCV_UTILS_INSTR_HPP |
||||
|
||||
#include <opencv2/core/utility.hpp> |
||||
#include <opencv2/core/utils/tls.hpp> |
||||
|
||||
namespace cv { |
||||
|
||||
//! @addtogroup core_utils
|
||||
//! @{
|
||||
|
||||
#ifdef CV_COLLECT_IMPL_DATA |
||||
CV_EXPORTS void setImpl(int flags); // set implementation flags and reset storage arrays
|
||||
CV_EXPORTS void addImpl(int flag, const char* func = 0); // add implementation and function name to storage arrays
|
||||
// Get stored implementation flags and functions names arrays
|
||||
// Each implementation entry correspond to function name entry, so you can find which implementation was executed in which function
|
||||
CV_EXPORTS int getImpl(std::vector<int> &impl, std::vector<String> &funName); |
||||
|
||||
CV_EXPORTS bool useCollection(); // return implementation collection state
|
||||
CV_EXPORTS void setUseCollection(bool flag); // set implementation collection state
|
||||
|
||||
#define CV_IMPL_PLAIN 0x01 // native CPU OpenCV implementation
|
||||
#define CV_IMPL_OCL 0x02 // OpenCL implementation
|
||||
#define CV_IMPL_IPP 0x04 // IPP implementation
|
||||
#define CV_IMPL_MT 0x10 // multithreaded implementation
|
||||
|
||||
#undef CV_IMPL_ADD |
||||
#define CV_IMPL_ADD(impl) \ |
||||
if(cv::useCollection()) \
|
||||
{ \
|
||||
cv::addImpl(impl, CV_Func); \
|
||||
} |
||||
#endif |
||||
|
||||
// Instrumentation external interface
|
||||
namespace instr |
||||
{ |
||||
|
||||
#if !defined OPENCV_ABI_CHECK |
||||
|
||||
enum TYPE |
||||
{ |
||||
TYPE_GENERAL = 0, // OpenCV API function, e.g. exported function
|
||||
TYPE_MARKER, // Information marker
|
||||
TYPE_WRAPPER, // Wrapper function for implementation
|
||||
TYPE_FUN, // Simple function call
|
||||
}; |
||||
|
||||
enum IMPL |
||||
{ |
||||
IMPL_PLAIN = 0, |
||||
IMPL_IPP, |
||||
IMPL_OPENCL, |
||||
}; |
||||
|
||||
struct NodeDataTls |
||||
{ |
||||
NodeDataTls() |
||||
{ |
||||
m_ticksTotal = 0; |
||||
} |
||||
uint64 m_ticksTotal; |
||||
}; |
||||
|
||||
class CV_EXPORTS NodeData |
||||
{ |
||||
public: |
||||
NodeData(const char* funName = 0, const char* fileName = NULL, int lineNum = 0, void* retAddress = NULL, bool alwaysExpand = false, cv::instr::TYPE instrType = TYPE_GENERAL, cv::instr::IMPL implType = IMPL_PLAIN); |
||||
NodeData(NodeData &ref); |
||||
~NodeData(); |
||||
NodeData& operator=(const NodeData&); |
||||
|
||||
cv::String m_funName; |
||||
cv::instr::TYPE m_instrType; |
||||
cv::instr::IMPL m_implType; |
||||
const char* m_fileName; |
||||
int m_lineNum; |
||||
void* m_retAddress; |
||||
bool m_alwaysExpand; |
||||
bool m_funError; |
||||
|
||||
volatile int m_counter; |
||||
volatile uint64 m_ticksTotal; |
||||
TLSDataAccumulator<NodeDataTls> m_tls; |
||||
int m_threads; |
||||
|
||||
// No synchronization
|
||||
double getTotalMs() const { return ((double)m_ticksTotal / cv::getTickFrequency()) * 1000; } |
||||
double getMeanMs() const { return (((double)m_ticksTotal/m_counter) / cv::getTickFrequency()) * 1000; } |
||||
}; |
||||
bool operator==(const NodeData& lhs, const NodeData& rhs); |
||||
|
||||
typedef Node<NodeData> InstrNode; |
||||
|
||||
CV_EXPORTS InstrNode* getTrace(); |
||||
|
||||
#endif // !defined OPENCV_ABI_CHECK
|
||||
|
||||
|
||||
CV_EXPORTS bool useInstrumentation(); |
||||
CV_EXPORTS void setUseInstrumentation(bool flag); |
||||
CV_EXPORTS void resetTrace(); |
||||
|
||||
enum FLAGS |
||||
{ |
||||
FLAGS_NONE = 0, |
||||
FLAGS_MAPPING = 0x01, |
||||
FLAGS_EXPAND_SAME_NAMES = 0x02, |
||||
}; |
||||
|
||||
CV_EXPORTS void setFlags(FLAGS modeFlags); |
||||
static inline void setFlags(int modeFlags) { setFlags((FLAGS)modeFlags); } |
||||
CV_EXPORTS FLAGS getFlags(); |
||||
|
||||
} // namespace instr
|
||||
|
||||
//! @}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // OPENCV_UTILS_TLS_HPP
|
@ -0,0 +1,237 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
#ifndef OPENCV_UTILS_TLS_HPP |
||||
#define OPENCV_UTILS_TLS_HPP |
||||
|
||||
#include <opencv2/core/utility.hpp> |
||||
|
||||
namespace cv { |
||||
|
||||
//! @addtogroup core_utils
|
||||
//! @{
|
||||
|
||||
namespace details { class TlsStorage; } |
||||
|
||||
/** TLS container base implementation
|
||||
* |
||||
* Don't use directly. |
||||
* |
||||
* @sa TLSData, TLSDataAccumulator templates |
||||
*/ |
||||
class CV_EXPORTS TLSDataContainer |
||||
{ |
||||
protected: |
||||
TLSDataContainer(); |
||||
virtual ~TLSDataContainer(); |
||||
|
||||
/// @deprecated use detachData() instead
|
||||
void gatherData(std::vector<void*> &data) const; |
||||
/// get TLS data and detach all data from threads (similar to cleanup() call)
|
||||
void detachData(std::vector<void*>& data); |
||||
|
||||
void* getData() const; |
||||
void release(); |
||||
|
||||
protected: |
||||
virtual void* createDataInstance() const = 0; |
||||
virtual void deleteDataInstance(void* pData) const = 0; |
||||
|
||||
#if OPENCV_ABI_COMPATIBILITY > 300 |
||||
private: |
||||
#else |
||||
public: |
||||
#endif |
||||
int key_; |
||||
|
||||
friend class cv::details::TlsStorage; // core/src/system.cpp
|
||||
|
||||
public: |
||||
void cleanup(); //!< Release created TLS data container objects. It is similar to release() call, but it keeps TLS container valid.
|
||||
|
||||
private: |
||||
// Disable copy/assign (noncopyable pattern)
|
||||
TLSDataContainer(TLSDataContainer &); |
||||
TLSDataContainer& operator =(const TLSDataContainer &); |
||||
}; |
||||
|
||||
|
||||
/** @brief Simple TLS data class
|
||||
* |
||||
* @sa TLSDataAccumulator |
||||
*/ |
||||
template <typename T> |
||||
class TLSData : protected TLSDataContainer |
||||
{ |
||||
public: |
||||
inline TLSData() {} |
||||
inline ~TLSData() { release(); } |
||||
|
||||
inline T* get() const { return (T*)getData(); } //!< Get data associated with key
|
||||
inline T& getRef() const { T* ptr = (T*)getData(); CV_DbgAssert(ptr); return *ptr; } //!< Get data associated with key
|
||||
|
||||
/// Release associated thread data
|
||||
inline void cleanup() |
||||
{ |
||||
TLSDataContainer::cleanup(); |
||||
} |
||||
|
||||
protected: |
||||
/// Wrapper to allocate data by template
|
||||
virtual void* createDataInstance() const CV_OVERRIDE { return new T; } |
||||
/// Wrapper to release data by template
|
||||
virtual void deleteDataInstance(void* pData) const CV_OVERRIDE { delete (T*)pData; } |
||||
}; |
||||
|
||||
|
||||
/// TLS data accumulator with gathering methods
|
||||
template <typename T> |
||||
class TLSDataAccumulator : public TLSData<T> |
||||
{ |
||||
mutable cv::Mutex mutex; |
||||
mutable std::vector<T*> dataFromTerminatedThreads; |
||||
std::vector<T*> detachedData; |
||||
bool cleanupMode; |
||||
public: |
||||
TLSDataAccumulator() : cleanupMode(false) {} |
||||
~TLSDataAccumulator() |
||||
{ |
||||
release(); |
||||
} |
||||
|
||||
/** @brief Get data from all threads
|
||||
* @deprecated replaced by detachData() |
||||
* |
||||
* Lifetime of vector data is valid until next detachData()/cleanup()/release() calls |
||||
* |
||||
* @param[out] data result buffer (should be empty) |
||||
*/ |
||||
void gather(std::vector<T*> &data) const |
||||
{ |
||||
CV_Assert(cleanupMode == false); // state is not valid
|
||||
CV_Assert(data.empty()); |
||||
{ |
||||
std::vector<void*> &dataVoid = reinterpret_cast<std::vector<void*>&>(data); |
||||
TLSDataContainer::gatherData(dataVoid); |
||||
} |
||||
{ |
||||
AutoLock lock(mutex); |
||||
data.reserve(data.size() + dataFromTerminatedThreads.size()); |
||||
for (typename std::vector<T*>::const_iterator i = dataFromTerminatedThreads.begin(); i != dataFromTerminatedThreads.end(); ++i) |
||||
{ |
||||
data.push_back((T*)*i); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** @brief Get and detach data from all threads
|
||||
* |
||||
* Call cleanupDetachedData() when returned vector is not needed anymore. |
||||
* |
||||
* @return Vector with associated data. Content is preserved (including lifetime of attached data pointers) until next detachData()/cleanupDetachedData()/cleanup()/release() calls |
||||
*/ |
||||
std::vector<T*>& detachData() |
||||
{ |
||||
CV_Assert(cleanupMode == false); // state is not valid
|
||||
std::vector<void*> dataVoid; |
||||
{ |
||||
TLSDataContainer::detachData(dataVoid); |
||||
} |
||||
{ |
||||
AutoLock lock(mutex); |
||||
detachedData.reserve(dataVoid.size() + dataFromTerminatedThreads.size()); |
||||
for (typename std::vector<T*>::const_iterator i = dataFromTerminatedThreads.begin(); i != dataFromTerminatedThreads.end(); ++i) |
||||
{ |
||||
detachedData.push_back((T*)*i); |
||||
} |
||||
dataFromTerminatedThreads.clear(); |
||||
for (typename std::vector<void*>::const_iterator i = dataVoid.begin(); i != dataVoid.end(); ++i) |
||||
{ |
||||
detachedData.push_back((T*)(void*)*i); |
||||
} |
||||
} |
||||
dataVoid.clear(); |
||||
return detachedData; |
||||
} |
||||
|
||||
/// Release associated thread data returned by detachData() call
|
||||
void cleanupDetachedData() |
||||
{ |
||||
AutoLock lock(mutex); |
||||
cleanupMode = true; |
||||
_cleanupDetachedData(); |
||||
cleanupMode = false; |
||||
} |
||||
|
||||
/// Release associated thread data
|
||||
void cleanup() |
||||
{ |
||||
cleanupMode = true; |
||||
TLSDataContainer::cleanup(); |
||||
|
||||
AutoLock lock(mutex); |
||||
_cleanupDetachedData(); |
||||
_cleanupTerminatedData(); |
||||
cleanupMode = false; |
||||
} |
||||
|
||||
/// Release associated thread data and free TLS key
|
||||
void release() |
||||
{ |
||||
cleanupMode = true; |
||||
TLSDataContainer::release(); |
||||
{ |
||||
AutoLock lock(mutex); |
||||
_cleanupDetachedData(); |
||||
_cleanupTerminatedData(); |
||||
} |
||||
} |
||||
|
||||
protected: |
||||
// synchronized
|
||||
void _cleanupDetachedData() |
||||
{ |
||||
for (typename std::vector<T*>::iterator i = detachedData.begin(); i != detachedData.end(); ++i) |
||||
{ |
||||
deleteDataInstance((T*)*i); |
||||
} |
||||
detachedData.clear(); |
||||
} |
||||
|
||||
// synchronized
|
||||
void _cleanupTerminatedData() |
||||
{ |
||||
for (typename std::vector<T*>::iterator i = dataFromTerminatedThreads.begin(); i != dataFromTerminatedThreads.end(); ++i) |
||||
{ |
||||
deleteDataInstance((T*)*i); |
||||
} |
||||
dataFromTerminatedThreads.clear(); |
||||
} |
||||
|
||||
protected: |
||||
virtual void* createDataInstance() const CV_OVERRIDE |
||||
{ |
||||
// Note: we can collect all allocated data here, but this would require raced mutex locks
|
||||
return new T; |
||||
} |
||||
virtual void deleteDataInstance(void* pData) const CV_OVERRIDE |
||||
{ |
||||
if (cleanupMode) |
||||
{ |
||||
delete (T*)pData; |
||||
} |
||||
else |
||||
{ |
||||
AutoLock lock(mutex); |
||||
dataFromTerminatedThreads.push_back((T*)pData); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
|
||||
//! @}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // OPENCV_UTILS_TLS_HPP
|
@ -0,0 +1,134 @@ |
||||
// This file is part of OpenCV project.
|
||||
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
||||
// of this distribution and at http://opencv.org/license.html.
|
||||
|
||||
// This is .hpp file included from test_utils.cpp
|
||||
|
||||
#ifdef CV_CXX11 |
||||
#include <thread> // std::thread |
||||
#endif |
||||
|
||||
#include "opencv2/core/utils/tls.hpp" |
||||
|
||||
namespace opencv_test { namespace { |
||||
|
||||
class TLSReporter |
||||
{ |
||||
public: |
||||
static int g_last_id; |
||||
static int g_allocated; |
||||
|
||||
int id; |
||||
|
||||
TLSReporter() |
||||
{ |
||||
id = CV_XADD(&g_last_id, 1); |
||||
CV_XADD(&g_allocated, 1); |
||||
} |
||||
~TLSReporter() |
||||
{ |
||||
CV_XADD(&g_allocated, -1); |
||||
} |
||||
}; |
||||
|
||||
int TLSReporter::g_last_id = 0; |
||||
int TLSReporter::g_allocated = 0; |
||||
|
||||
#ifdef CV_CXX11 |
||||
|
||||
template<typename T> |
||||
static void callNThreadsWithTLS(int N, TLSData<T>& tls) |
||||
{ |
||||
std::vector<std::thread> threads(N); |
||||
for (int i = 0; i < N; i++) |
||||
{ |
||||
threads[i] = std::thread([&]() { |
||||
TLSReporter* pData = tls.get(); |
||||
(void)pData; |
||||
}); |
||||
} |
||||
for (int i = 0; i < N; i++) |
||||
{ |
||||
threads[i].join(); |
||||
} |
||||
threads.clear(); |
||||
} |
||||
|
||||
TEST(Core_TLS, HandleThreadTermination) |
||||
{ |
||||
const int init_id = TLSReporter::g_last_id; |
||||
const int init_allocated = TLSReporter::g_allocated; |
||||
|
||||
const int N = 4; |
||||
TLSData<TLSReporter> tls; |
||||
|
||||
// use TLS
|
||||
ASSERT_NO_THROW(callNThreadsWithTLS(N, tls)); |
||||
|
||||
EXPECT_EQ(init_id + N, TLSReporter::g_last_id); |
||||
EXPECT_EQ(init_allocated + 0, TLSReporter::g_allocated); |
||||
} |
||||
|
||||
|
||||
static void testTLSAccumulator(bool detachFirst) |
||||
{ |
||||
const int init_id = TLSReporter::g_last_id; |
||||
const int init_allocated = TLSReporter::g_allocated; |
||||
|
||||
const int N = 4; |
||||
TLSDataAccumulator<TLSReporter> tls; |
||||
|
||||
{ // empty TLS checks
|
||||
std::vector<TLSReporter*>& data0 = tls.detachData(); |
||||
EXPECT_EQ((size_t)0, data0.size()); |
||||
tls.cleanupDetachedData(); |
||||
} |
||||
|
||||
// use TLS
|
||||
ASSERT_NO_THROW(callNThreadsWithTLS(N, tls)); |
||||
|
||||
EXPECT_EQ(init_id + N, TLSReporter::g_last_id); |
||||
EXPECT_EQ(init_allocated + N, TLSReporter::g_allocated); |
||||
|
||||
if (detachFirst) |
||||
{ |
||||
std::vector<TLSReporter*>& data1 = tls.detachData(); |
||||
EXPECT_EQ((size_t)N, data1.size()); |
||||
|
||||
// no data through gather after detachData()
|
||||
std::vector<TLSReporter*> data2; |
||||
tls.gather(data2); |
||||
EXPECT_EQ((size_t)0, data2.size()); |
||||
|
||||
tls.cleanupDetachedData(); |
||||
|
||||
EXPECT_EQ(init_id + N, TLSReporter::g_last_id); |
||||
EXPECT_EQ(init_allocated + 0, TLSReporter::g_allocated); |
||||
EXPECT_EQ((size_t)0, data1.size()); |
||||
} |
||||
else |
||||
{ |
||||
std::vector<TLSReporter*> data2; |
||||
tls.gather(data2); |
||||
EXPECT_EQ((size_t)N, data2.size()); |
||||
|
||||
std::vector<TLSReporter*>& data1 = tls.detachData(); |
||||
EXPECT_EQ((size_t)N, data1.size()); |
||||
|
||||
tls.cleanupDetachedData(); |
||||
|
||||
EXPECT_EQ((size_t)0, data1.size()); |
||||
// data2 is not empty, but it has invalid contents
|
||||
EXPECT_EQ((size_t)N, data2.size()); |
||||
} |
||||
|
||||
EXPECT_EQ(init_id + N, TLSReporter::g_last_id); |
||||
EXPECT_EQ(init_allocated + 0, TLSReporter::g_allocated); |
||||
} |
||||
|
||||
TEST(Core_TLS, AccumulatorHoldData_detachData) { testTLSAccumulator(true); } |
||||
TEST(Core_TLS, AccumulatorHoldData_gather) { testTLSAccumulator(false); } |
||||
|
||||
#endif |
||||
|
||||
}} // namespace
|
Loading…
Reference in new issue