Last active
September 21, 2016 10:22
-
-
Save flisboac/61de67d3deb2c7cd23d0c63b7358197e to your computer and use it in GitHub Desktop.
Testing different dynamic method dispatch techniques in C++11. Compile with `--std=c++11` or equivalent.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <iostream> | |
| #include <string> | |
| #include <sstream> | |
| #include <vector> | |
| #include <memory> | |
| #include <functional> | |
| #include <chrono> | |
| #include <type_traits> | |
| #include <thread> | |
| #include <algorithm> | |
| #include <thread> | |
| #if 0 | |
| # Compile with this (save file as test-interface-method-dispatch.cpp): | |
| compile_interface_method_dispatch() { | |
| local basename="$(realpath "$(dirname "$1")")/$(basename "$1" | cut -d. -f1)"; | |
| g++ -DPROFILE -pg -o "$basename" "$1" && "$basename" > "${basename}.txt" && \ | |
| g++ -DDEBUG -g -o "$basename" "$1" && "$basename" >> "${basename}.txt" && \ | |
| g++ -DRELEASE -O3 -s -o "$basename" "$1" && "$basename" >> "${basename}.txt" && \ | |
| g++ -o "$basename" "$1" && "$basename" >> "${basename}.txt" | |
| } | |
| compile_interface_method_dispatch ~/test-interface-method-dispatch.cpp | |
| #endif | |
| #define UNKNOWN_BUILD_ID 0 | |
| #define RELEASE_BUILD_ID 1 | |
| #define DEBUG_BUILD_ID 2 | |
| #define PROFILE_BUILD_ID 3 | |
| #ifdef DEBUG | |
| # define BUILD_TYPE DEBUG_BUILD_ID | |
| # define BUILD_STR "debug" | |
| #else | |
| # ifdef PROFILE | |
| # define BUILD_TYPE PROFILE_BUILD_ID | |
| # define BUILD_STR "profile" | |
| # else | |
| # ifdef RELEASE | |
| # define BUILD_TYPE RELEASE_BUILD_ID | |
| # define BUILD_STR "release" | |
| # else | |
| # define BUILD_TYPE UNKNOWN_BUILD_ID | |
| # define BUILD_STR "unknown" | |
| # endif | |
| # endif | |
| #endif | |
| #ifndef NTESTS | |
| # define NTESTS 100000000 | |
| #endif | |
| #define STRQT(T) #T | |
| #define STRFY(T) STRQT(T) | |
| struct Object { | |
| void method() const {} | |
| }; | |
| template <typename T> struct ClosureInterface { | |
| ClosureInterface() : deref(nullptr) {} | |
| ClosureInterface(std::function<T*(void)> _deref): deref(_deref) {} | |
| ClosureInterface(T* _value) : deref([=]() -> T* { return _value; } ) {} | |
| ClosureInterface(T& _value) : deref([&]() -> T* { return &_value; } ) {} | |
| std::function<T*(void)> deref; | |
| }; | |
| template <typename T> struct ClosureInterface<const T> { | |
| ClosureInterface() : deref(nullptr) {} | |
| ClosureInterface(std::function<T*(void)> _deref): deref(_deref) {} | |
| ClosureInterface(const T* _value) : deref([=]() -> const T* { return _value; } ) {} | |
| ClosureInterface(const T& _value) : deref([&]() -> const T* { return &_value; } ) {} | |
| std::function<const T*(void)> deref; | |
| }; | |
| template <typename T> class PointerInterface { | |
| public: | |
| typedef T* (*FDeref)(void*); | |
| PointerInterface() : _fderef(defaultDeref), _pderef(nullptr) {} | |
| PointerInterface(const T* _deref): | |
| _fderef(defaultDeref), | |
| _pderef(reinterpret_cast<void*>(_deref)) | |
| {} | |
| PointerInterface(const T& _deref): | |
| _fderef(defaultDeref), | |
| _pderef(reinterpret_cast<void*>(&_deref)) | |
| {} | |
| inline T* deref() const { return _fderef(_pderef); } | |
| private: | |
| static inline T* defaultDeref(void* pderef) | |
| { return (void*)(pderef); } | |
| FDeref _fderef; | |
| void* _pderef; | |
| }; | |
| template <typename T> class PointerInterface<const T> { | |
| public: | |
| typedef const T* (*FDeref)(void*); | |
| PointerInterface() : _fderef(defaultDeref), _pderef(nullptr) {} | |
| PointerInterface(const T* _deref): | |
| _fderef(defaultDeref), | |
| _pderef(const_cast<void*>(reinterpret_cast<const void*>(_deref))) | |
| {} | |
| PointerInterface(const T& _deref): | |
| _fderef(defaultDeref), | |
| _pderef(const_cast<void*>(reinterpret_cast<const void*>(&_deref))) | |
| {} | |
| PointerInterface(FDeref fderef, void* pderef) : _fderef(fderef), _pderef(pderef) {} | |
| inline const T* deref() const { return _fderef(_pderef); } | |
| private: | |
| static inline const T* defaultDeref(void* pderef) | |
| { return const_cast<const T*>(reinterpret_cast<T*>(pderef)); } | |
| FDeref _fderef; | |
| void* _pderef; | |
| }; | |
| template <typename T> struct IInheritanceInterface { | |
| virtual ~IInheritanceInterface() {} | |
| virtual T* deref() const = 0; | |
| }; | |
| template <typename T> struct IInheritanceInterface<const T> { | |
| virtual ~IInheritanceInterface() {} | |
| virtual const T* deref() const = 0; | |
| }; | |
| template <typename T> class InheritanceInterface : public IInheritanceInterface<T> { | |
| public: | |
| InheritanceInterface() : value_(nullptr) {} | |
| InheritanceInterface(T* _value) : value_(_value) {} | |
| InheritanceInterface(T& _value) : value_(&_value) {} | |
| inline T* deref() const { return value_; } | |
| private: | |
| T* value_; | |
| }; | |
| template <typename T> class InheritanceInterface<const T> : public IInheritanceInterface<const T> { | |
| public: | |
| InheritanceInterface() : value_(nullptr) {} | |
| InheritanceInterface(const T* _value) : value_(_value) {} | |
| InheritanceInterface(const T& _value) : value_(&_value) {} | |
| inline const T* deref() const { return value_; } | |
| private: | |
| const T* value_; | |
| }; | |
| template < | |
| template <typename TFT> typename F, | |
| typename T | |
| > | |
| class Facade { | |
| public: | |
| Facade() : _interface(nullptr) {} | |
| Facade(F<T>* interface) : _interface(interface) {} | |
| Facade(F<T>& interface) : _interface(&interface) {} | |
| Facade(const Facade<F, T>& rhs) = delete; | |
| Facade(Facade<F, T>&& rhs) = default; | |
| ~Facade() { delete _interface; } | |
| inline T* operator->() const { return _interface->deref(); } | |
| inline T& operator*() const { return *_interface->deref(); } | |
| private: | |
| F<T>* _interface; | |
| }; | |
| template < | |
| template <typename TFT> typename F, | |
| typename T | |
| > | |
| class Facade<F, const T> { | |
| public: | |
| Facade() : _interface(nullptr) {} | |
| Facade(F<const T>* interface) : _interface(interface) {} | |
| Facade(F<const T>& interface) : _interface(&interface) {} | |
| Facade(const Facade<F, const T>& rhs) = delete; | |
| Facade(Facade<F, const T>&& rhs) = default; | |
| ~Facade() { delete _interface; } | |
| inline const T* operator->() const { return _interface->deref(); } | |
| inline const T& operator*() const { return *_interface->deref(); } | |
| private: | |
| F<const T>* _interface; | |
| }; | |
| template< | |
| template <typename TFT> typename F, | |
| typename T = Object | |
| > | |
| static inline Facade<F, T> makeFacade(T& object) { | |
| return Facade<F, T>(new F<T>(object)); | |
| } | |
| template <typename ID, ID InvalidValue> | |
| class BasicNullableEnumValue_ { | |
| public: | |
| const ID id = InvalidValue; | |
| constexpr BasicNullableEnumValue_() = default; | |
| constexpr BasicNullableEnumValue_(const BasicNullableEnumValue_& rhs) = default; | |
| constexpr BasicNullableEnumValue_(BasicNullableEnumValue_&& rhs) = default; | |
| BasicNullableEnumValue_& operator=(const BasicNullableEnumValue_& rhs) = default; | |
| BasicNullableEnumValue_& operator=(BasicNullableEnumValue_&& rhs) = default; | |
| inline operator bool() const { return id == InvalidValue; } | |
| inline bool operator==(const BasicNullableEnumValue_& rhs) const { | |
| const bool thisValid = *this, rhsValid = rhs; | |
| return (!thisValid && !rhsValid) || (thisValid && rhsValid && id == rhs.id); | |
| } | |
| inline bool operator!=(const BasicNullableEnumValue_& rhs) const { return !(*this == rhs); } | |
| inline bool operator<(const BasicNullableEnumValue_& rhs) const { return id < rhs.id; } | |
| protected: | |
| constexpr BasicNullableEnumValue_(ID id_): | |
| id(id_) {} | |
| }; | |
| enum class ETestType { | |
| Unknown, | |
| Raw, | |
| Closure, | |
| Pointer, | |
| Inheritance | |
| }; | |
| template <ETestType type = ETestType::Unknown> | |
| class TestType { | |
| }; | |
| template <> | |
| struct TestType<ETestType::Raw> { | |
| constexpr static const ETestType id = ETestType::Raw; | |
| constexpr static const char repr = 'r'; | |
| constexpr static const char *const name = "raw"; | |
| }; | |
| template <> | |
| struct TestType<ETestType::Closure> { | |
| constexpr static const ETestType id = ETestType::Closure; | |
| constexpr static const char repr = 'c'; | |
| constexpr static const char *const name = "closure"; | |
| }; | |
| template <> | |
| struct TestType<ETestType::Pointer> { | |
| constexpr static const ETestType id = ETestType::Pointer; | |
| constexpr static const char repr = 'p'; | |
| constexpr static const char *const name = "pointer"; | |
| }; | |
| template <> | |
| struct TestType<ETestType::Inheritance> { | |
| constexpr static const ETestType id = ETestType::Inheritance; | |
| constexpr static const char repr = 'i'; | |
| constexpr static const char *const name = "inheritance"; | |
| }; | |
| template <> | |
| class TestType<ETestType::Unknown> { | |
| public: | |
| constexpr static const ETestType id = ETestType::Unknown; | |
| constexpr static const char repr = '?'; | |
| constexpr static const char *const name = "unknown"; | |
| class Value : public BasicNullableEnumValue_<ETestType, ETestType::Unknown> { | |
| public: | |
| friend class TestType<ETestType::Unknown>; | |
| const char repr = '\0'; | |
| const char *const name = nullptr; | |
| constexpr Value() = default; | |
| private: | |
| constexpr Value(ETestType id_, char repr_, const char *const name_): | |
| BasicNullableEnumValue_<ETestType, ETestType::Unknown>::BasicNullableEnumValue_(id_), | |
| repr(repr_), | |
| name(name_) {} | |
| }; | |
| constexpr static inline std::array<ETestType, 5> ids() { | |
| return { | |
| ETestType::Unknown, | |
| ETestType::Raw, | |
| ETestType::Closure, | |
| ETestType::Pointer, | |
| ETestType::Inheritance | |
| }; | |
| } | |
| constexpr static inline Value null_value() | |
| { return Value(); } | |
| template <ETestType t = ETestType::Unknown> | |
| constexpr static inline Value value() { | |
| return Value( | |
| TestType<t>::id, TestType<t>::repr, TestType<t>::name | |
| ); | |
| } | |
| constexpr static inline Value value(ETestType t) { | |
| switch(t) { | |
| case ETestType::Raw: return value<ETestType::Raw>(); | |
| case ETestType::Closure: return value<ETestType::Closure>(); | |
| case ETestType::Pointer: return value<ETestType::Pointer>(); | |
| case ETestType::Inheritance: return value<ETestType::Inheritance>(); | |
| } | |
| return TestType<ETestType::Unknown>::value(); | |
| } | |
| }; | |
| template < | |
| template <typename TFT> typename F | |
| > | |
| struct DeductTestType { | |
| }; | |
| template <> | |
| struct DeductTestType<ClosureInterface> { | |
| constexpr static ETestType id = ETestType::Closure; | |
| using type = TestType<ETestType::Closure>; | |
| }; | |
| template <> | |
| struct DeductTestType<InheritanceInterface> { | |
| constexpr static ETestType id = ETestType::Inheritance; | |
| using type = TestType<ETestType::Inheritance>; | |
| }; | |
| template <> | |
| struct DeductTestType<PointerInterface> { | |
| constexpr static ETestType id = ETestType::Pointer; | |
| using type = TestType<ETestType::Pointer>; | |
| }; | |
| class DispatchTest { | |
| public: | |
| friend class std::less<DispatchTest>; | |
| using Clock = std::conditional< | |
| std::chrono::high_resolution_clock::is_steady, | |
| std::chrono::high_resolution_clock, | |
| std::chrono::steady_clock | |
| >::type; | |
| using TimePoint = Clock::time_point; | |
| DispatchTest() : _ntests(NTESTS) {} | |
| DispatchTest(unsigned long ntests_) : _ntests(ntests_) {} | |
| DispatchTest(const DispatchTest& rhs) = default; | |
| DispatchTest(DispatchTest&& rhs) = default; | |
| DispatchTest& operator=(const DispatchTest& rhs) = default; | |
| DispatchTest& test( | |
| std::function<void(void)> subject, | |
| ETestType type = ETestType::Unknown | |
| ) { | |
| _type = type; | |
| _startTime = Clock::now(); | |
| for (unsigned long i = 0; i < _ntests; ++i) | |
| subject(); | |
| _stopTime = Clock::now(); | |
| _diffDuration = _stopTime - _startTime; | |
| return *this; | |
| } | |
| inline unsigned long ntests() const | |
| { return _ntests; } | |
| inline TestType<>::Value type() const | |
| { return TestType<>::value(_type); } | |
| Clock::duration diffDuration() const | |
| { return _diffDuration; } | |
| template <typename V> | |
| inline V deltaDurationCount() const | |
| { return std::chrono::duration<V>(_diffDuration).count(); } | |
| template <typename V> | |
| inline V averageDurationCount() const | |
| { return std::chrono::duration<V>(_diffDuration / _ntests).count(); } | |
| inline bool operator<(const DispatchTest& rhs) const { | |
| return (_stopTime - _startTime) < (rhs._stopTime - rhs._startTime); | |
| } | |
| private: | |
| unsigned long _ntests; | |
| ETestType _type; | |
| TimePoint _startTime; | |
| TimePoint _stopTime; | |
| Clock::duration _diffDuration; | |
| }; | |
| template< | |
| template <typename TFT> typename F, | |
| typename T = Object | |
| > | |
| static inline DispatchTest executeTest(size_t ntests, const T& subject) { | |
| DispatchTest test(ntests); | |
| auto facade = makeFacade<F>(subject); | |
| test.test([&]() { facade->method(); }, DeductTestType<F>::id); | |
| return test; | |
| } | |
| template <typename T = Object> | |
| static inline DispatchTest executeSimpleTest(size_t ntests, const T& subject) { | |
| DispatchTest test(ntests); | |
| test.test([&]() { subject.method(); }, ETestType::Raw); | |
| return test; | |
| } | |
| static inline std::ostream& showResults(const DispatchTest& test, std::ostream& os = std::cerr) { | |
| auto testType = test.type(); | |
| std::stringstream ss; | |
| ss << "* Test results for '" << testType.name << "': " | |
| << std::endl << "\tNumber of calls: " << test.ntests() | |
| << std::endl << "\tTotal time: " << test.deltaDurationCount<double>() << " second(s)" | |
| << std::endl << "\tAverage time: " << test.averageDurationCount<double>() << " second(s)" | |
| << std::endl; | |
| return os << ss.str(); | |
| } | |
| static inline std::ostream& exportResults(const DispatchTest& test, std::ostream& os = std::cout) { | |
| auto testType = test.type(); | |
| std::stringstream ss; | |
| ss | |
| << BUILD_TYPE | |
| << " " << (int)testType.id | |
| << " " << test.ntests() | |
| << " " << test.deltaDurationCount<double>() | |
| << " " << test.averageDurationCount<double>() | |
| << " " << test.diffDuration().count() | |
| << " " << DispatchTest::Clock::period::num | |
| << " " << DispatchTest::Clock::period::den | |
| << " # " << BUILD_STR << ", " << testType.name | |
| << std::endl; | |
| return os << ss.str(); | |
| } | |
| struct Options { | |
| int ntests = NTESTS; | |
| std::string executableName = "__noname__"; | |
| std::string outputFile = "-"; | |
| }; | |
| static void help(int argc, char** argv) { | |
| } | |
| int main(int argc, char** argv) { | |
| unsigned long ntests = NTESTS; | |
| int argIdx = 0; | |
| char **arg = ++argv; | |
| if (argc > 1) { | |
| ntests = std::stoi(argv[1]); | |
| } | |
| std::cerr << "Starting tests for build '" BUILD_STR "'..." << std::endl; | |
| Object subject; | |
| std::vector<DispatchTest> tests = { | |
| executeSimpleTest(ntests, subject), | |
| executeTest<InheritanceInterface>(ntests, subject), | |
| executeTest<PointerInterface>(ntests, subject), | |
| executeTest<ClosureInterface>(ntests, subject) | |
| }; | |
| std::cerr << "Finished tests. Results ordered by best time." << std::endl; | |
| std::sort(tests.begin(), tests.end()); | |
| std::cerr << "Showing results..." << std::endl; | |
| std::for_each(tests.begin(), tests.end(), [](DispatchTest test) { | |
| showResults(test); | |
| }); | |
| std::cerr << "Exporting results..." << std::endl; | |
| std::for_each(tests.begin(), tests.end(), [](DispatchTest test) { | |
| exportResults(test); | |
| }); | |
| std::cerr << "Finished." << std::endl; | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment