Browse files

Merge branch 'master' into v1.

This marks the release of TPIE version 1.0.

The following improvements have been merged in since 1.0rc2:

* Time estimation warning has been moved to debug log. (8a9138e)
* Priority queue memory usage slightly improved. (fa1e12e)
* Move backtrace printed by fractional_subindicator to debug log. (f016d26)
* Fix memory management crashes in debug mode. (91b940b)
* Get rid of CMake option TPIE_THREADSAFE_MEMORY_MANAGEMNT. (21eb5b8)
* Various documentation cleanup; code comments have been added/clarified.
* Some compiler warnings have been addressed.
  • Loading branch information...
2 parents 4b251dd + fd5806a commit e2b2b65727e6ed9a98179ac8b2156892b03385bb @Mortal Mortal committed Dec 14, 2012
View
1 CMakeLists.txt
@@ -91,7 +91,6 @@ add_subdirectory(doc)
option(COMPILE_TEST "Compile test programs" ON)
option(TPL_LOGGING "Enable tpie logging." ON)
-option(TPIE_THREADSAFE_MEMORY_MANAGEMNT "Thread safe memory managment" ON)
option(TPIE_DEPRECATED_WARNINGS "Enable warnings for deprecated classes, methods and typedefs" OFF)
option(TPIE_PARALLEL_SORT "Enable parallel quick sort implementation" ON)
View
4 test/unit/test_disjoint_set.cpp
@@ -63,7 +63,7 @@ class disjointsets_memory_test: public memory_test {
virtual size_type claimed_size() {return static_cast<size_type>(disjoint_sets<int>::memory_usage(123456));}
};
-bool stress_test(size_t n) {
+bool stress_test(int n) {
test_timer t("disjoint_sets");
for (int _ = 0; _ < 5; ++_) {
t.start();
@@ -87,5 +87,5 @@ int main(int argc, char **argv) {
return tpie::tests(argc, argv)
.test(basic_test, "basic")
.test(disjointsets_memory_test(), "memory")
- .test(stress_test, "stress", "n", 1024);
+ .test(stress_test, "stress", "n", static_cast<int>(1024));
}
View
89 test/unit/test_memory.cpp
@@ -20,7 +20,11 @@
#include "common.h"
#include <tpie/memory.h>
#include <vector>
-
+#include <tpie/internal_queue.h>
+#include <tpie/internal_vector.h>
+#include <boost/random.hpp>
+#include <tpie/job.h>
+#include <tpie/cpu_timer.h>
struct mtest {
size_t & r;
@@ -132,7 +136,88 @@ bool basic_test() {
return true;
}
+struct tpie_alloc {
+ template <typename T>
+ static T * alloc() { return tpie::tpie_new<T>(); }
+ template <typename T>
+ static void dealloc(T * t) { tpie::tpie_delete(t); }
+};
+
+struct std_alloc {
+ template <typename T>
+ static T * alloc() { return new T; }
+ template <typename T>
+ static void dealloc(T * t) { delete t; }
+};
+
+struct c_alloc {
+ template <typename T>
+ static T * alloc() { return (T *) malloc(sizeof(T)); }
+ template <typename T>
+ static void dealloc(T * t) { free(t); }
+};
+
+template <typename Alloc>
+class memory_user : public tpie::job {
+ typedef int test_t;
+ size_t times;
+ tpie::internal_queue<test_t *> pointers;
+ boost::rand48 rnd;
+ boost::uniform_01<double> urnd;
+
+public:
+ memory_user(size_t times, size_t capacity) : times(times), pointers(capacity) {}
+
+ virtual void operator()() /*override*/ {
+ for (size_t i = 0; i < times; ++i) {
+ if (pointers.empty() ||
+ (!pointers.full() && urnd(rnd) <= (cos(static_cast<double>(i) * 60.0 / static_cast<double>(pointers.size())) + 1.0)/2.0)) {
+
+ pointers.push(Alloc::template alloc<test_t>());
+ } else {
+ Alloc::dealloc(pointers.front());
+ pointers.pop();
+ }
+ }
+ while (!pointers.empty()) {
+ Alloc::dealloc(pointers.front());
+ pointers.pop();
+ }
+ }
+};
+
+template <typename Alloc>
+bool parallel_test(const size_t nJobs, const size_t times, const size_t capacity) {
+ tpie::cpu_timer t;
+ t.start();
+ tpie::internal_vector<memory_user<Alloc> *> workers(nJobs);
+ for (size_t i = 0; i < nJobs; ++i) {
+ workers[i] = tpie::tpie_new<memory_user<Alloc> >(times, capacity);
+ workers[i]->enqueue();
+ }
+ for (size_t i = 0; i < nJobs; ++i) {
+ workers[i]->join();
+ tpie::tpie_delete(workers[i]);
+ }
+ t.stop();
+ tpie::log_info() << t << std::endl;
+ return true;
+}
+
int main(int argc, char ** argv) {
return tpie::tests(argc, argv, 128)
- .test(basic_test, "basic");
+ .test(basic_test, "basic")
+ .test(parallel_test<tpie_alloc>, "parallel",
+ "n", static_cast<size_t>(8),
+ "times", static_cast<size_t>(500000),
+ "capacity", static_cast<size_t>(50000))
+ .test(parallel_test<std_alloc>, "parallel_stdnew",
+ "n", static_cast<size_t>(8),
+ "times", static_cast<size_t>(500000),
+ "capacity", static_cast<size_t>(50000))
+ .test(parallel_test<c_alloc>, "parallel_malloc",
+ "n", static_cast<size_t>(8),
+ "times", static_cast<size_t>(500000),
+ "capacity", static_cast<size_t>(50000))
+ ;
}
View
1 tpie/config.h.cmake
@@ -20,7 +20,6 @@
#endif
#cmakedefine TPL_LOGGING 1
-#cmakedefine TPIE_THREADSAFE_MEMORY_MANAGEMNT 1
#cmakedefine TPIE_NDEBUG
View
4 tpie/execution_time_predictor.cpp
@@ -188,9 +188,7 @@ class time_estimator_database {
if (l == e.end()) {
--l;
if (l->first == 0) {
-#ifdef TPIE_NDEBUG
- log_warning() << "In time estimation first was 0, this should not happen!" << std::endl;
-#endif
+ log_debug() << "In time estimation, first was 0." << std::endl;
confidence=0.0;
return -1;
}
View
3 tpie/file_accessor/file_accessor.h
@@ -30,6 +30,7 @@
#include <tpie/file_accessor/win32.h>
namespace tpie {
namespace file_accessor {
+typedef win32 raw_file_accessor;
typedef stream_accessor<win32> file_accessor;
}
}
@@ -39,13 +40,15 @@ typedef stream_accessor<win32> file_accessor;
#include <tpie/file_accessor/posix.h>
namespace tpie {
namespace file_accessor {
+typedef posix raw_file_accessor;
typedef stream_accessor<posix> file_accessor;
}
}
#endif // WIN32
namespace tpie {
+typedef file_accessor::raw_file_accessor default_raw_file_accessor;
typedef file_accessor::file_accessor default_file_accessor;
} // namespace tpie
View
1 tpie/file_accessor/posix.h
@@ -51,6 +51,7 @@ class posix {
inline void seek_i(stream_size_type offset);
inline void close_i();
inline void truncate_i(stream_size_type bytes);
+ inline bool is_open() const;
///////////////////////////////////////////////////////////////////////////
/// \brief Check the global errno variable and throw an exception that
View
4 tpie/file_accessor/posix.inl
@@ -101,6 +101,10 @@ void posix::open_rw_new(const std::string & path) {
::posix_fadvise(m_fd, 0, 0, m_advice);
}
+bool posix::is_open() const {
+ return m_fd != 0;
+}
+
void posix::close_i() {
if (m_fd != 0) {
::close(m_fd);
View
1 tpie/file_accessor/win32.h
@@ -57,6 +57,7 @@ class win32 {
inline void seek_i(stream_size_type offset);
inline void close_i();
inline void truncate_i(stream_size_type bytes);
+ inline bool is_open() const;
inline void set_cache_hint(cache_hint cacheHint);
};
View
4 tpie/file_accessor/win32.inl
@@ -99,6 +99,10 @@ void win32::open_rw_new(const std::string & path) {
if (m_fd == INVALID_HANDLE_VALUE) throw_getlasterror();
}
+bool win32::is_open() const {
+ return m_fd != INVALID_HANDLE_VALUE;
+}
+
void win32::close_i() {
if (m_fd != INVALID_HANDLE_VALUE) {
CloseHandle(m_fd);
View
10 tpie/file_base.h
@@ -49,16 +49,20 @@ class file_base: public file_base_crtp<file_base> {
/// This is the type of our block buffers. We have one per file::stream
/// distributed over two linked lists.
///////////////////////////////////////////////////////////////////////////
-#pragma warning( push )
-#pragma warning( disable : 4200 )
+#ifdef WIN32
+#pragma warning( push )
+#pragma warning( disable : 4200 )
+#endif
struct block_t : public boost::intrusive::list_base_hook<> {
memory_size_type size;
memory_size_type usage;
stream_size_type number;
bool dirty;
char data[0];
};
-#pragma warning( pop )
+#ifdef WIN32
+#pragma warning( pop )
+#endif
inline void update_size(stream_size_type size) {
m_size = std::max(m_size, size);
View
18 tpie/fractional_progress.cpp
@@ -245,8 +245,10 @@ fractional_subindicator::~fractional_subindicator() {
} else {
s << "A fractional_subindicator was assigned a non-zero fraction but never initialized." << std::endl;
}
- tpie::backtrace(s, 5);
TP_LOG_FATAL(s.str());
+ s.str("");
+ tpie::backtrace(s, 5);
+ TP_LOG_DEBUG(s.str());
TP_LOG_FLUSH_LOG;
}
#endif
@@ -267,8 +269,10 @@ void fractional_progress::init(stream_size_type range) {
std::stringstream s;
s << "init() was called on a fractional_progress for which init had already been called. Subindicators were:" << std::endl;
s << sub_indicators_ss();
- tpie::backtrace(s, 5);
TP_LOG_FATAL(s.str());
+ s.str("");
+ tpie::backtrace(s, 5);
+ TP_LOG_DEBUG(s.str());
TP_LOG_FLUSH_LOG;
}
m_init_called=true;
@@ -286,8 +290,10 @@ void fractional_progress::done() {
s << "done() was called on fractional_progress, but init has not been called. Subindicators were:" << std::endl;
}
s << sub_indicators_ss();
- tpie::backtrace(s, 5);
TP_LOG_FATAL(s.str());
+ s.str("");
+ tpie::backtrace(s, 5);
+ TP_LOG_DEBUG(s.str());
TP_LOG_FLUSH_LOG;
}
m_done_called=true;
@@ -299,10 +305,12 @@ fractional_progress::~fractional_progress() {
#ifndef TPIE_NDEBUG
if (m_init_called && !m_done_called && !std::uncaught_exception()) {
std::stringstream s;
- s << "A fractional_progress was destructed without done being called. Subindicators were:" << std::endl;
+ s << "A fractional_progress was destructed without done being called." << std::endl;
+ TP_LOG_FATAL(s.str());
+ s.str("Subindicators were:\n");
s << sub_indicators_ss();
tpie::backtrace(s, 5);
- TP_LOG_FATAL(s.str());
+ TP_LOG_DEBUG(s.str());
TP_LOG_FLUSH_LOG;
}
#endif
View
39 tpie/internal_stack.h
@@ -1,6 +1,6 @@
// -*- mode: c++; tab-width: 4; indent-tabs-mode: t; c-file-style: "stroustrup"; -*-
// vi:set ts=4 sts=4 sw=4 noet :
-// Copyright 2010, The TPIE development team
+// Copyright 2010, 2012, The TPIE development team
//
// This file is part of TPIE.
//
@@ -28,33 +28,42 @@
#include <tpie/internal_stack_vector_base.h>
namespace tpie {
-/////////////////////////////////////////////////////////
-/// \brief A generic internal stack
+///////////////////////////////////////////////////////////////////////////////
+/// \brief A generic internal stack.
///
-/// \tparam T The type of items storred on the stack
-/////////////////////////////////////////////////////////
+/// \tparam T The type of items stored in the structure.
+///////////////////////////////////////////////////////////////////////////////
template <typename T>
class internal_stack: public internal_stack_vector_base<T,internal_stack<T> > {
public:
typedef internal_stack_vector_base<T,internal_stack<T> > parent_t;
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Construct structure with given capacity.
+ /// \copydetails tpie::internal_stack_vector_base::internal_stack_vector_base
+ ///////////////////////////////////////////////////////////////////////////
internal_stack(size_t size=0): parent_t(size){}
+
using parent_t::m_elements;
using parent_t::m_size;
- /////////////////////////////////////////////////////////
- /// \brief Return the top most element on the stack
- /////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Return the topmost element on the stack.
+ ///////////////////////////////////////////////////////////////////////////
inline T & top() {return m_elements[m_size-1];}
- /////////////////////////////////////////////////////////
- /// \brief Add an element to the top of the stack
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Add an element to the top of the stack.
+ /// If size() is equal to the capacity (set in the constructor or in
+ /// resize()), effects are undefined. resize() is not called implicitly.
///
- /// \param val The element to add
- /////////////////////////////////////////////////////////
+ /// \param val The element to add.
+ ///////////////////////////////////////////////////////////////////////////
inline void push(const T & val){m_elements[m_size++] = val;}
- /////////////////////////////////////////////////////////
- /// \brief Remove the top most element from the stack
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Remove the topmost element from the stack.
+ ///////////////////////////////////////////////////////////////////////////
inline void pop(){--m_size;}
};
View
71 tpie/internal_stack_vector_base.h
@@ -1,6 +1,6 @@
// -*- mode: c++; tab-width: 4; indent-tabs-mode: t; c-file-style: "stroustrup"; -*-
// vi:set ts=4 sts=4 sw=4 noet :
-// Copyright 2010, The TPIE development team
+// Copyright 2010, 2012, The TPIE development team
//
// This file is part of TPIE.
//
@@ -19,69 +19,74 @@
#ifndef __TPIE_INTERNAL_STACK_VECTOR_BASE_H__
#define __TPIE_INTERNAL_STACK_VECTOR_BASE_H__
-///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
/// \file internal_stack_vector_base.h
/// Generic base for internal stack and vector with known memory requirements.
-///////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
#include <tpie/array.h>
#include <tpie/util.h>
namespace tpie {
-
-/////////////////////////////////////////////////////////
-/// \brief A base class for generic internal fixed size stack and vector
+
+///////////////////////////////////////////////////////////////////////////////
+/// \brief A base class for a generic internal fixed size stack and vector.
///
-/// \tparam T The type of items stored in the container
-/// \tparam child_t The subtype of the class
-/////////////////////////////////////////////////////////
+/// \tparam T The type of items stored in the container.
+/// \tparam child_t The subtype of the class (CRTP).
+///////////////////////////////////////////////////////////////////////////////
template <typename T, typename child_t>
class internal_stack_vector_base: public linear_memory_base<child_t> {
protected:
+ /** Element storage. */
array<T> m_elements;
+
+ /** Number of elements pushed to the structure. */
size_t m_size;
public:
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
/// \copydoc tpie::linear_memory_structure_doc::memory_coefficient()
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
static double memory_coefficient() {return array<T>::memory_coefficient();}
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
/// \copydoc tpie::linear_memory_structure_doc::memory_overhead()
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
static double memory_overhead() {
return array<T>::memory_overhead() - sizeof(array<T>) + sizeof(child_t);
}
- /////////////////////////////////////////////////////////
- /// \brief Construct the data structure
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Construct structure with given capacity.
+ ///
+ /// If a zero capacity is given (the default), no elements may be pushed to
+ /// the structure until the structure is <tt>resize</tt>d to a different
+ /// capacity.
///
- /// \param size The maximal number of items allowed in the container at any one time
- /////////////////////////////////////////////////////////
+ /// \param size Capacity of the structure.
+ ///////////////////////////////////////////////////////////////////////////
internal_stack_vector_base(size_t size=0): m_size(0) {m_elements.resize(size);}
- /////////////////////////////////////////////////////////
- /// \brief Resize the data structure, all data is lost
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Change the capacity of the structure and clear all elements.
///
- /// \param size The number of pushes supported between calles to
- /////////////////////////////////////////////////////////
+ /// \param size New capacity of the structure.
+ ///////////////////////////////////////////////////////////////////////////
void resize(size_t size=0) {m_elements.resize(size); m_size=0;}
-
- /////////////////////////////////////////////////////////
- /// \brief Check if the data structure is empty
- /// \return true if the data structure is empty, otherwise false
- /////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Check if no elements are currently pushed to the structure.
+ ///////////////////////////////////////////////////////////////////////////
inline bool empty() const {return m_size==0;}
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
/// \brief Return the number of elements in the data structure.
- /// \return The number of elements in the container
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
inline size_t size() const {return m_size;}
-
- /////////////////////////////////////////////////////////
- /// \brief Clear the data structure of all elements
- /////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Clear the data structure of all elements.
+ ///////////////////////////////////////////////////////////////////////////
inline void clear(){m_size=0;}
};
View
97 tpie/internal_vector.h
@@ -1,6 +1,6 @@
// -*- mode: c++; tab-width: 4; indent-tabs-mode: t; c-file-style: "stroustrup"; -*-
// vi:set ts=4 sts=4 sw=4 noet :
-// Copyright 2010, The TPIE development team
+// Copyright 2010, 2012, The TPIE development team
//
// This file is part of TPIE.
//
@@ -28,11 +28,11 @@
#include <tpie/internal_stack_vector_base.h>
namespace tpie {
-/////////////////////////////////////////////////////////
-/// \brief A generic internal vector
+///////////////////////////////////////////////////////////////////////////////
+/// \brief A generic internal vector.
///
-/// \tparam T The type of items storred on the vector
-/////////////////////////////////////////////////////////
+/// \tparam T The type of items stored in the structure.
+///////////////////////////////////////////////////////////////////////////////
template <typename T>
class internal_vector: public internal_stack_vector_base<T,internal_vector<T> > {
public:
@@ -41,45 +41,90 @@ class internal_vector: public internal_stack_vector_base<T,internal_vector<T> >
typedef typename array<T>::const_iterator const_iterator;
using parent_t::m_elements;
using parent_t::m_size;
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Construct structure with given capacity.
+ /// \copydetails tpie::internal_stack_vector_base::internal_stack_vector_base
+ ///////////////////////////////////////////////////////////////////////////
internal_vector(size_t size=0): parent_t(size){}
-
- /////////////////////////////////////////////////////////
- /// \brief Acces operator
- /////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Element access. No range checking is done.
+ ///////////////////////////////////////////////////////////////////////////
inline T & operator[](size_t s){return m_elements[s];}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Element access. No range checking is done.
+ ///////////////////////////////////////////////////////////////////////////
inline const T & operator[](size_t s)const {return m_elements[s];}
-
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get the first item pushed. Requires \c !empty().
+ ///////////////////////////////////////////////////////////////////////////
inline T & front(){return m_elements[0];}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get the first item pushed. Requires \c !empty().
+ ///////////////////////////////////////////////////////////////////////////
inline const T & front()const {return m_elements[0];}
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get the last item pushed. Requires \c !empty().
+ ///////////////////////////////////////////////////////////////////////////
inline T & back(){return m_elements[m_size-1];}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get the last item pushed. Requires \c !empty().
+ ///////////////////////////////////////////////////////////////////////////
inline const T & back()const {return m_elements[m_size-1];}
-
- /////////////////////////////////////////////////////////
- /// \brief Add an element to the end of the vector
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Add an element to the end of the vector.
+ /// If size() is equal to the capacity (set in the constructor or in
+ /// resize()), effects are undefined. resize() is not called implicitly.
///
- /// \param val The element to add
- /////////////////////////////////////////////////////////
+ /// Iterators are invalidated by this call.
+ ///
+ /// \param val The element to add.
+ ///////////////////////////////////////////////////////////////////////////
inline T & push_back(const T & val){m_elements[m_size++] = val; return back();}
- /////////////////////////////////////////////////////////
- /// \brief Add an element to the end of the vector
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief If an item was previously popped from this point in the
+ /// structure, push it to the structure again; otherwise, push the default
+ /// value.
+ ///
+ /// Iterators are invalidated by this call.
///
- /////////////////////////////////////////////////////////
+ /// \param val The element to add.
+ ///////////////////////////////////////////////////////////////////////////
inline T & push_back(){++m_size; return back();}
- /////////////////////////////////////////////////////////
- /// \brief Remove the last element from the vector
- /////////////////////////////////////////////////////////
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Remove the last element from the vector.
+ ///
+ /// Iterators are invalidated by this call.
+ ///////////////////////////////////////////////////////////////////////////
inline void pop_back(){--m_size;}
-
- /////////////////////////////////////////////////////////
- /// \brief Iterators
- /////////////////////////////////////////////////////////
- inline iterator begin(){ return m_elements.begin();}
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get an iterator to the beginning of the structure.
+ ///////////////////////////////////////////////////////////////////////////
+ inline iterator begin(){ return m_elements.begin();}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get an iterator to the beginning of the structure.
+ ///////////////////////////////////////////////////////////////////////////
inline const_iterator begin()const {return m_elements.begin();}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get an iterator to the end of the structure.
+ ///////////////////////////////////////////////////////////////////////////
inline iterator end(){return m_elements.find(m_size);}
+
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Get an iterator to the end of the structure.
+ ///////////////////////////////////////////////////////////////////////////
inline const_iterator end()const {return m_elements.find(m_size);}
};
View
24 tpie/memory.cpp
@@ -33,7 +33,7 @@ inline void segfault() {
memory_manager * mm = 0;
-memory_manager::memory_manager(): m_used(0), m_limit(0), m_maxExceeded(0), m_enforce(ENFORCE_WARN), m_nextWarning(0) {}
+memory_manager::memory_manager(): m_used(0), m_limit(0), m_maxExceeded(0), m_nextWarning(0), m_enforce(ENFORCE_WARN) {}
size_t memory_manager::available() const throw() {
size_t used = m_used;
@@ -180,6 +180,11 @@ std::pair<uint8_t *, size_t> memory_manager::__allocate_consecutive(size_t upper
#ifndef TPIE_NDEBUG
+void memory_manager::register_pointer(void * p, size_t size, const std::type_info & t) {
+ boost::mutex::scoped_lock lock(m_mutex);
+ __register_pointer(p, size, t);
+}
+
void memory_manager::__register_pointer(void * p, size_t size, const std::type_info & t) {
if (m_pointers.count(p) != 0) {
log_error() << "Trying to register pointer " << p << " of size "
@@ -189,6 +194,11 @@ void memory_manager::__register_pointer(void * p, size_t size, const std::type_i
m_pointers[p] = std::make_pair(size, &t);;
}
+void memory_manager::unregister_pointer(void * p, size_t size, const std::type_info & t) {
+ boost::mutex::scoped_lock lock(m_mutex);
+ __unregister_pointer(p, size, t);
+}
+
void memory_manager::__unregister_pointer(void * p, size_t size, const std::type_info & t) {
boost::unordered_map<void *, std::pair<size_t, const std::type_info *> >::const_iterator i=m_pointers.find(p);
if (i == m_pointers.end()) {
@@ -210,12 +220,22 @@ void memory_manager::__unregister_pointer(void * p, size_t size, const std::type
}
}
+void memory_manager::assert_tpie_ptr(void * p) {
+ boost::mutex::scoped_lock lock(m_mutex);
+ __assert_tpie_ptr(p);
+}
+
void memory_manager::__assert_tpie_ptr(void * p) {
if (!p || m_pointers.count(p)) return;
log_error() << p << " has not been allocated with tpie_new" << std::endl;
segfault();
}
+void memory_manager::complain_about_unfreed_memory() {
+ boost::mutex::scoped_lock lock(m_mutex);
+ __complain_about_unfreed_memory();
+}
+
void memory_manager::__complain_about_unfreed_memory() {
if(m_pointers.size() == 0) return;
log_error() << "The following pointers were either leaked or deleted with delete instead of tpie_delete" << std::endl << std::endl;
@@ -232,7 +252,7 @@ void init_memory_manager() {
void finish_memory_manager() {
#ifndef TPIE_NDEBUG
- mm->__complain_about_unfreed_memory();
+ mm->complain_about_unfreed_memory();
#endif
delete mm;
mm = 0;
View
23 tpie/memory.h
@@ -127,10 +127,12 @@ class memory_manager {
std::pair<uint8_t *, size_t> __allocate_consecutive(size_t upper_bound, size_t granularity);
#ifndef TPIE_NDEBUG
- void __register_pointer(void * p, size_t size, const std::type_info & t);
- void __unregister_pointer(void * p, size_t size, const std::type_info & t);
- void __assert_tpie_ptr(void * p);
- void __complain_about_unfreed_memory();
+ // The following methods take the mutex before calling the private doubly
+ // underscored equivalent.
+ void register_pointer(void * p, size_t size, const std::type_info & t);
+ void unregister_pointer(void * p, size_t size, const std::type_info & t);
+ void assert_tpie_ptr(void * p);
+ void complain_about_unfreed_memory();
#endif
@@ -141,7 +143,14 @@ class memory_manager {
size_t m_nextWarning;
enforce_t m_enforce;
boost::mutex m_mutex;
+
#ifndef TPIE_NDEBUG
+ // Before calling these methods, you must have the mutex.
+ void __register_pointer(void * p, size_t size, const std::type_info & t);
+ void __unregister_pointer(void * p, size_t size, const std::type_info & t);
+ void __assert_tpie_ptr(void * p);
+ void __complain_about_unfreed_memory();
+
boost::unordered_map<void *, std::pair<size_t, const std::type_info *> > m_pointers;
#endif
};
@@ -169,7 +178,7 @@ memory_manager & get_memory_manager();
///////////////////////////////////////////////////////////////////////////////
inline void __register_pointer(void * p, size_t size, const std::type_info & t) {
#ifndef TPIE_NDEBUG
- get_memory_manager().__register_pointer(p, size, t);
+ get_memory_manager().register_pointer(p, size, t);
#else
unused(p);
unused(size);
@@ -183,7 +192,7 @@ inline void __register_pointer(void * p, size_t size, const std::type_info & t)
///////////////////////////////////////////////////////////////////////////////
inline void __unregister_pointer(void * p, size_t size, const std::type_info & t) {
#ifndef TPIE_NDEBUG
- get_memory_manager().__unregister_pointer(p, size, t);
+ get_memory_manager().unregister_pointer(p, size, t);
#else
unused(p);
unused(size);
@@ -198,7 +207,7 @@ inline void __unregister_pointer(void * p, size_t size, const std::type_info & t
inline void assert_tpie_ptr(void * p) {
#ifndef TPIE_NDEBUG
if (p)
- get_memory_manager().__assert_tpie_ptr(p);
+ get_memory_manager().assert_tpie_ptr(p);
#else
unused(p);
#endif
View
84 tpie/priority_queue.h
@@ -49,38 +49,47 @@ namespace tpie {
priority_queue_error(const std::string& what) : std::logic_error(what)
{ }
};
-
-/////////////////////////////////////////////////////////
+
+///////////////////////////////////////////////////////////////////////////////
+/// \class priority_queue
+/// \brief External memory priority queue implementation.
+///
+/// Originally implemented by Lars Hvam Petersen for his Master's thesis
+/// titled "External Priority Queues in Practice", June 2007.
+/// This implementation, named "PQSequence3", is the fastest among the
+/// priority queue implementations studied in the paper.
+/// Inspiration: Sanders - Fast priority queues for cached memory (1999).
///
-/// \class priority_queue
-/// \author Lars Hvam Petersen
+/// For an overview of the algorithm, refer to Sanders (1999) section 2 and
+/// figure 1, or Lars Hvam's thesis, section 4.4.
///
-/// Inspiration: Sanders - Fast priority queues for cached memory (1999)
-/// Refer to Section 2 and Figure 1 for an overview of the algorithm
+/// In the debug log, the priority queue reports two values setting_k and
+/// setting_m. The priority queue has a maximum capacity which is on the order
+/// of setting_m * setting_k**setting_k elements (where ** denotes
+/// exponentiation).
///
-/////////////////////////////////////////////////////////
+/// However, even with as little as 8 MB of memory, this maximum capacity in
+/// practice exceeds 2**48, corresponding to a petabyte-sized dataset of 32-bit
+/// integers.
+///////////////////////////////////////////////////////////////////////////////
template<typename T, typename Comparator = std::less<T>, typename OPQType = pq_overflow_heap<T, Comparator> >
class priority_queue {
typedef memory_size_type group_type;
typedef memory_size_type slot_type;
public:
- /////////////////////////////////////////////////////////
- ///
- /// Constructor
- ///
- /// \param f Factor of memory that the priority queue is
- /// allowed to use.
+ ///////////////////////////////////////////////////////////////////////////
+ /// \brief Constructor.
+ ///
+ /// \param f Factor of memory that the priority queue is allowed to use.
/// \param b Block factor
- ///
- /////////////////////////////////////////////////////////
- priority_queue(double f=1.0, float b=0.0625);
+ ///////////////////////////////////////////////////////////////////////////
+ priority_queue(double f=1.0, float b=0.0625);
#ifndef DOXYGEN
- // \param mmavail Number of bytes the priority queue is
- // allowed to use.
+ // \param mmavail Number of bytes the priority queue is allowed to use.
// \param b Block factor
- priority_queue(memory_size_type mm_avail, float b=0.0625);
+ priority_queue(memory_size_type mm_avail, float b=0.0625);
#endif
@@ -154,17 +163,38 @@ class priority_queue {
T min;
bool min_in_buffer;
- tpie::auto_ptr<OPQType> opq; // insert heap
- tpie::array<T> buffer; // deletion buffer
- tpie::array<T> gbuffer0; // group buffer 0
- tpie::array<T> mergebuffer; // merge buffer for merging deletion buffer and group buffer 0
+ /** Overflow priority queue (for buffering inserted elements). Capacity m. */
+ tpie::auto_ptr<OPQType> opq;
+
+ /** Deletion buffer containing the m' top elements in the entire structure. */
+ tpie::array<T> buffer;
+
+ /** Group buffers contain at most m elements all less or equal to elements
+ * in the corresponding group slots. Elements in group buffers are *not*
+ * repeated in actual group slots. For efficiency, we keep group buffer 0
+ * in memory. */
+ tpie::array<T> gbuffer0;
+
+ /** Merge buffer of size 2*m. */
+ tpie::array<T> mergebuffer;
+
+ /** 3*(#slots) integers. Slot i contains its elements in cyclic ascending order,
+ * starting at index slot_state[3*i]. Slot i contains slot_state[3*i+1] elements.
+ * Its data is in data file index slot_state[3*i+2]. */
tpie::array<memory_size_type> slot_state;
+
+ /** 2*(#groups) integers. Group buffer i has its elements in cyclic ascending order,
+ * starting at index group_state[2*i]. Gbuffer i contains group_state[2*i+1] elements. */
tpie::array<memory_size_type> group_state;
- memory_size_type setting_k;
- memory_size_type current_r;
- memory_size_type setting_m;
- memory_size_type setting_mmark;
+ /** k, the fanout of each group and the max fanout R. */
+ memory_size_type setting_k;
+ /** Number of groups in use. */
+ memory_size_type current_r;
+ /** m, the size of a slot and the size of the group buffers. */
+ memory_size_type setting_m;
+ /** m', the size of the deletion buffer. */
+ memory_size_type setting_mmark;
memory_size_type slot_data_id;
View
102 tpie/priority_queue.inl
@@ -75,7 +75,8 @@ void priority_queue<T, Comparator, OPQType>::init(memory_size_type mm_avail) { /
+ sizeof(T) //extra buffer for remove_group_buffer
+ 2*sizeof(T); //mergebuffer
const memory_size_type buffer_m_overhead = sizeof(T) + 2*sizeof(T); //buffer
- const memory_size_type extra_overhead = 2*(usage+sizeof(file_stream<T>*)+alloc_overhead) //temporary streams
+ const memory_size_type extra_overhead =
+ 2*(usage+sizeof(file_stream<T>*)+alloc_overhead) //temporary streams
+ 2*(sizeof(T)+sizeof(group_type)); //mergeheap
const memory_size_type additional_overhead = 16*1024; //Just leave a bit unused
TP_LOG_DEBUG("fanout_overhead " << fanout_overhead << ",\n" <<
@@ -105,10 +106,18 @@ void priority_queue<T, Comparator, OPQType>::init(memory_size_type mm_avail) { /
//compute setting_k
//some of these numbers get big which is the reason for all this
//careful casting.
- stream_size_type squared_tmp = static_cast<stream_size_type>(fanout_overhead)*static_cast<stream_size_type>(fanout_overhead);
- squared_tmp += static_cast<stream_size_type>(4*sq_fanout_overhead)*static_cast<stream_size_type>(setting_k);
+ stream_size_type squared_tmp =
+ static_cast<stream_size_type>(fanout_overhead)
+ *static_cast<stream_size_type>(fanout_overhead);
+
+ squared_tmp +=
+ static_cast<stream_size_type>(4*sq_fanout_overhead)
+ *static_cast<stream_size_type>(setting_k);
+
long double dsquared_tmp = static_cast<long double>(squared_tmp);
- const stream_size_type root_discriminant = static_cast<stream_size_type>(std::floor(std::sqrt(dsquared_tmp)));
+
+ const stream_size_type root_discriminant =
+ static_cast<stream_size_type>(std::floor(std::sqrt(dsquared_tmp)));
const stream_size_type nominator = root_discriminant-fanout_overhead;
const stream_size_type denominator = 2*sq_fanout_overhead;
@@ -207,19 +216,22 @@ template <typename T, typename Comparator, typename OPQType>
void priority_queue<T, Comparator, OPQType>::push(const T& x) {
if(opq->full()) {
+ // When the overflow priority queue (aka. insertion buffer) is full,
+ // insert its contents into a new slot in group 0.
+ //
+ // To maintain the heap invariant
+ // deletion buffer <= group buffer 0 <= group 0 slots
+ // we bubble lesser elements from insertion buffer down into
+ // deletion buffer and group buffer 0.
- // Merge insertion buffer, deletion buffer and group buffer 0
- // such that deletion buffer <= group buffer 0 <= insertion buffer.
-
- // Afterwards, move insertion buffer to a free slot in group 0.
-
- slot_type slot = free_slot(0); // if group 0 is full, we recursively empty group i
- // by merging it into a slot in group i+1
+ slot_type slot = free_slot(0); // (if group 0 is full, we recursively empty group i
+ // by merging it into a slot in group i+1)
assert(opq->sorted_size() == setting_m);
T* arr = opq->sorted_array();
- if(buffer_size > 0) { // maintain heap invariant for deletion buffer
+ // Bubble lesser elements down into deletion buffer
+ if(buffer_size > 0) {
// fetch insertion buffer
memcpy(&mergebuffer[0], &arr[0], sizeof(T)*opq->sorted_size());
@@ -237,7 +249,8 @@ void priority_queue<T, Comparator, OPQType>::push(const T& x) {
memcpy(&arr[0], mergebuffer.get()+buffer_size, sizeof(T)*opq->sorted_size());
}
- if(group_size(0)> 0) { // maintain heap invariant for gbuffer0
+ // Bubble lesser elements down into group buffer 0
+ if(group_size(0)> 0) {
// Merge insertion buffer and group buffer 0
assert(group_size(0)+opq->sorted_size() <= setting_m*2);
@@ -488,8 +501,6 @@ priority_queue<T, Comparator, OPQType>::free_slot(group_type group) {
template <typename T, typename Comparator, typename OPQType>
void priority_queue<T, Comparator, OPQType>::fill_buffer() {
- //cout << "fill buffer" << "\n";
- //dump();
if(buffer_size !=0) {
return;
}
@@ -498,28 +509,23 @@ void priority_queue<T, Comparator, OPQType>::fill_buffer() {
}
// refill group buffers, if needed
- //cout << "refill group buffers" << "\n";
for(memory_size_type i=0;i<current_r;i++) {
if(group_size(i)<static_cast<stream_size_type>(setting_mmark)) {
- //cout << "fill group buffer " << i << "\n";
fill_group_buffer(i);
- //cout << "fill group buffer " << i << " done" << "\n";
- //dump();
}
if(group_size(i) == 0 && i==current_r-1) {
current_r--;
}
}
- //cout << "done filling groups" << "\n";
// merge to buffer
- //cout << "current_r: " << current_r << "\n";
mergebuffer.resize(0);
#ifndef TPIE_NDEBUG
std::cout << "memavail after mb free: "
<< get_memory_manager().available() << "b" << std::endl;
#endif
+ {
pq_merge_heap<T, Comparator> heap(current_r);
tpie::array<tpie::auto_ptr<file_stream<T> > > data(current_r);
@@ -529,20 +535,16 @@ void priority_queue<T, Comparator, OPQType>::fill_buffer() {
heap.push(gbuffer0[group_start(0)], 0);
} else if(group_size(i)>0) {
data[i]->open(group_data(i));
- // assert(slot_size(group*setting_k+i>0));
data[i]->seek(group_start(i));
heap.push(data[i]->read(), i);
} else if(i > 0) {
// dummy, well :o/
- //cout << "create dummy " << i << "\n";
}
}
- //cout << "init done" << "\n";
while(!heap.empty() && buffer_size!=setting_mmark) {
group_type current_group = heap.top_run();
if(current_group!= 0 && data[current_group]->offset() == setting_m) {
- //cout << "fill group seeking to 0" << "\n";
data[current_group]->seek(0);
}
buffer[(buffer_size+buffer_start)%setting_m] = heap.top();
@@ -555,22 +557,18 @@ void priority_queue<T, Comparator, OPQType>::fill_buffer() {
heap.pop();
} else {
if(current_group == 0) {
- //cout << "0 push: " << "\n";
- //cout << gbuffer0[group_start(0)] << "\n";
heap.pop_and_push(gbuffer0[group_start(0)], 0);
} else {
heap.pop_and_push(data[current_group]->read(), current_group);
}
}
}
- //cout << "while done" << "\n";
+ } // destruct and deallocate `heap'
#ifndef TPIE_NDEBUG
std::cout << "memavail before mb alloc: "
<< get_memory_manager().available() << "b" << std::endl;
#endif
mergebuffer.resize(setting_m*2);
-
- //cout << "end fill buffer" << "\n";
}
template <typename T, typename Comparator, typename OPQType>
@@ -723,7 +721,6 @@ void priority_queue<T, Comparator, OPQType>::empty_group(group_type group) {
slot_type current_slot = heap.top_run();
newstream.write(heap.top());
slot_size_set(newslot,slot_size(newslot)+1);
- //cout << heap.top() << " from slot " << current_slot << "\n";
slot_start_set(current_slot, slot_start(current_slot)+1);
slot_size_set(current_slot, slot_size(current_slot)-1);
if(slot_size(current_slot) == 0) {
@@ -741,6 +738,13 @@ void priority_queue<T, Comparator, OPQType>::empty_group(group_type group) {
mergebuffer.resize(setting_m*2);;
if(group_size(group+1) > 0 && !ret) {
+ // Maintain heap invariant:
+ // group buffer i <= group i slots
+ // If the group buffer of the group in which we inserted runs
+ // was not empty before, we might now have violated a heap invariant
+ // by inserting elements in a [group+1] slot that are less than elements
+ // in group buffer [group+1].
+ // Just remove the group buffer to ensure the invariant.
remove_group_buffer(group+1); // todo, this might recurse?
}
}
@@ -793,7 +797,8 @@ void priority_queue<T, Comparator, OPQType>::validate() {
T read = stream.read();
if(comp_(read, last)) { // compare
dump();
- TP_LOG_FATAL_ID("Error: Group buffer " << i << " order invalid (last: " << last << ", read: " << read << ")");
+ TP_LOG_FATAL_ID("Error: Group buffer " << i << " order invalid (last: " << last <<
+ ", read: " << read << ")");
exit(-1);
}
}
@@ -809,7 +814,8 @@ void priority_queue<T, Comparator, OPQType>::validate() {
for(stream_size_type j = 1; j < slot_size(i); j++) {
T read = stream.read();
if(comp_(read, last)) { // compare
- TP_LOG_FATAL_ID("Error: Slot " << i << " order invalid (last: " << last << ", read: " << read << ")");
+ TP_LOG_FATAL_ID("Error: Slot " << i << " order invalid (last: " << last <<
+ ", read: " << read << ")");
exit(-1);
}
}
@@ -830,7 +836,8 @@ void priority_queue<T, Comparator, OPQType>::validate() {
T first = stream.read();
if(comp_(first, buf_max)) { // compare
dump();
- TP_LOG_FATAL_ID("Error: Heap property invalid, buffer -> group buffer " << i << "(buffer: " << buf_max << ", first: " << first << ")");
+ TP_LOG_FATAL_ID("Error: Heap property invalid, buffer -> group buffer " << i <<
+ "(buffer: " << buf_max << ", first: " << first << ")");
exit(-1);
}
}
@@ -855,7 +862,9 @@ void priority_queue<T, Comparator, OPQType>::validate() {
if(comp_(item_slot, item_group)) { // compare
dump();
- TP_LOG_FATAL_ID("Error: Heap property invalid, group buffer " << i << " -> slot " << j << "(group: " << item_group << ", slot: " << item_slot << ")");
+ TP_LOG_FATAL_ID("Error: Heap property invalid, group buffer " << i <<
+ " -> slot " << j << "(group: " << item_group <<
+ ", slot: " << item_slot << ")");
exit(-1);
}
}
@@ -867,10 +876,10 @@ void priority_queue<T, Comparator, OPQType>::validate() {
#endif
}
-// After emptying a group, we empty its group buffer
-// by merging it with group buffer 0.
-// Smaller elements go in gb0,
-// and larger elements go in a group 0 slot.
+// Empty a group buffer by inserting it into an empty group 0 slot.
+// To maintain the invariant
+// group buffer 0 elements <= group 0 slot elements,
+// merge the given group buffer with group buffer 0 before writing the slot out.
template <typename T, typename Comparator, typename OPQType>
void priority_queue<T, Comparator, OPQType>::remove_group_buffer(group_type group) {
#ifndef NDEBUG
@@ -884,7 +893,9 @@ void priority_queue<T, Comparator, OPQType>::remove_group_buffer(group_type grou
slot_type slot = free_slot(0);
if(group_size(group) == 0) return;
- TP_LOG_DEBUG_ID("Remove group buffer " << group << " of size " << group_size(group) << " with available memory " << get_memory_manager().available());
+ TP_LOG_DEBUG_ID("Remove group buffer " << group <<
+ " of size " << group_size(group) <<
+ " with available memory " << get_memory_manager().available());
assert(group < setting_k);
array<T> arr(static_cast<size_t>(group_size(group)));
@@ -907,7 +918,6 @@ void priority_queue<T, Comparator, OPQType>::remove_group_buffer(group_type grou
// make sure that the new slot in group 0 is heap ordered with gbuffer0
if(group > 0 && group_size(0) != 0) {
- // this code is also used in PQFishspear
memory_size_type j = 0;
for(memory_size_type i = group_start(0); i < group_start(0)+group_size(0); i++) {
mergebuffer[j] = gbuffer0[i%setting_m];
@@ -939,7 +949,6 @@ memory_size_type priority_queue<T, Comparator, OPQType>::slot_start(slot_type sl
template <typename T, typename Comparator, typename OPQType>
void priority_queue<T, Comparator, OPQType>::slot_size_set(slot_type slot, memory_size_type n) {
- //cout << "change slot " << slot << " size" << "\n";
assert(slot<setting_k*setting_k);
slot_state[slot*3+1] = n;
}
@@ -987,24 +996,23 @@ temp_file & priority_queue<T, Comparator, OPQType>::group_data(group_type groupi
template <typename T, typename Comparator, typename OPQType>
memory_size_type priority_queue<T, Comparator, OPQType>::slot_max_size(slot_type slotid) {
- return setting_m*static_cast<memory_size_type>(pow((long double)setting_k,(long double)(slotid/setting_k))); // todo, too many casts
+ // todo, too many casts
+ return setting_m
+ *static_cast<memory_size_type>(pow((long double)setting_k,
+ (long double)(slotid/setting_k)));
}
template <typename T, typename Comparator, typename OPQType>
void priority_queue<T, Comparator, OPQType>::write_slot(slot_type slotid, T* arr, memory_size_type len) {
assert(len > 0);
- //cout << "write slot " << slotid << " " << len << "\n";
- //cout << "write slot " << slot_data(slotid) << "\n";
file_stream<T> data(block_factor);
data.open(slot_data(slotid));
- //cout << "write slot new done" << "\n";
data.write(arr+0, arr+len);
slot_start_set(slotid, 0);
slot_size_set(slotid, len);
if(current_r == 0 && slotid < setting_k) {
current_r = 1;
}
- //cout << "write slot done" << std::endl;
}
/////////////////////
View
6 tpie/progress_indicator_subindicator.cpp
@@ -75,9 +75,11 @@ void progress_indicator_subindicator::setup(progress_indicator_base * parent,
progress_indicator_subindicator::~progress_indicator_subindicator() {
if (m_init_called && !m_done_called && !std::uncaught_exception()) {
std::stringstream s;
- s << "A progress_indicator_subindicator was destructed without done being called" << std::endl;
- tpie::backtrace(s, 5);
+ s << "A progress_indicator_subindicator was destructed without done being called." << std::endl;
TP_LOG_FATAL(s.str());
+ s.str("");
+ tpie::backtrace(s, 5);
+ TP_LOG_DEBUG(s.str());
TP_LOG_FLUSH_LOG;
}
}
View
31 tpie/unittest.h
@@ -149,6 +149,12 @@ class tests {
const std::string & p2_name, T2 p2_default,
const std::string & p3_name, T3 p3_default);
+ template <typename T, typename T1, typename T2, typename T3, typename T4>
+ tests & multi_test(T fct, const std::string & name,
+ const std::string & p1_name, T1 p1_default,
+ const std::string & p2_name, T2 p2_default,
+ const std::string & p3_name, T3 p3_default,
+ const std::string & p4_name, T4 p4_default);
operator int();
protected:
@@ -486,6 +492,31 @@ tests & tests::multi_test(T fct, const std::string & name,
return *this;
}
+template <typename T, typename T1, typename T2, typename T3, typename T4>
+tests & tests::multi_test(T fct, const std::string & name,
+ const std::string & p1_name, T1 p1_default,
+ const std::string & p2_name, T2 p2_default,
+ const std::string & p3_name, T3 p3_default,
+ const std::string & p4_name, T4 p4_default) {
+ m_tests.push_back(name+
+ arg_str(p1_name, p1_default) +
+ arg_str(p2_name, p2_default) +
+ arg_str(p3_name, p3_default) +
+ arg_str(p4_name, p4_default));
+
+ if (testAll || name == test_name) {
+ start_test(name);
+ teststream ts;
+ fct(ts,
+ get_arg(p1_name, p1_default),
+ get_arg(p2_name, p2_default),
+ get_arg(p3_name, p3_default),
+ get_arg(p4_name, p4_default));
+ end_test(ts.success());
+ }
+ return *this;
+}
+
template <typename T>
tests & tests::setup(T t) {
setups.push_back(new func_impl<T>(t));

0 comments on commit e2b2b65

Please sign in to comment.