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