/* * * Copyright (c) 1999 * Dr John Maddock * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Dr John Maddock makes no representations * about the suitability of this software for any purpose. * It is provided "as is" without express or implied warranty. * * This file provides some example of type_traits usage - * by "optimising" various algorithms: * * opt::copy - optimised for trivial copy (cf std::copy) * opt::fill - optimised for trivial copy/small types (cf std::fill) * opt::destroy_array - an example of optimisation based upon omitted destructor calls * opt::iter_swap - uses type_traits to determine whether the iterator is a proxy * in which case it uses a "safe" approach, otherwise calls swap * on the assumption that swap may be specialised for the pointed-to type. * */ #include #include #include #include #include #include #include #include #include using std::cout; using std::endl; using std::cin; namespace opt{ // // algorithm destroy_arry: // The reverse of std::unitialized_copy, takes a block of // unitialized memory and calls destructors on all objects therein. // namespace detail{ template struct array_destroyer { template static void destroy_array(T* i, T* j){ do_destroy_array(i, j); } }; template <> struct array_destroyer { template static void destroy_array(T*, T*){} }; template void do_destroy_array(T* first, T* last) { while(first != last) { first->~T(); ++first; } } }; // namespace detail template inline void destroy_array(T* p1, T* p2) { detail::array_destroyer::value>::destroy_array(p1, p2); } // // unoptimised versions of destroy_array: // template void destroy_array1(T* first, T* last) { while(first != last) { first->~T(); ++first; } } template void destroy_array2(T* first, T* last) { for(; first != last; ++first) first->~T(); } // // opt::copy // same semantics as std::copy // calls memcpy where appropiate. // namespace detail{ template struct copier { template static I2 do_copy(I1 first, I1 last, I2 out); }; template template I2 copier::do_copy(I1 first, I1 last, I2 out) { while(first != last) { *out = *first; ++out; ++first; } return out; } template <> struct copier { template static I2* do_copy(I1* first, I1* last, I2* out) { memcpy(out, first, (last-first)*sizeof(I2)); return out+(last-first); } }; } template inline I2 copy(I1 first, I1 last, I2 out) { typedef typename boost::remove_cv::value_type>::type v1_t; typedef typename boost::remove_cv::value_type>::type v2_t; enum{ can_opt = boost::is_same::value && boost::is_pointer::value && boost::is_pointer::value && boost::has_trivial_assign::value }; return detail::copier::do_copy(first, last, out); } // // fill // same as std::fill, uses memset where appropriate, along with call_traits // to "optimise" parameter passing. // namespace detail{ template struct filler { template static void do_fill(I first, I last, typename boost::call_traits::param_type val); }; template template void filler::do_fill(I first, I last, typename boost::call_traits::param_type val) { while(first != last) { *first = val; ++first; } } template <> struct filler { template static void do_fill(I first, I last, T val) { memset(first, val, last-first); } }; } template inline void fill(I first, I last, const T& val) { enum{ can_opt = boost::is_pointer::value && boost::is_arithmetic::value && (sizeof(T) == 1) }; typedef detail::filler filler_t; filler_t::template do_fill(first, last, val); } // // iter_swap: // tests whether iterator is a proxying iterator or not, and // uses optimal form accordingly: // namespace detail{ template struct swapper { template static void do_swap(I one, I two) { typedef typename std::iterator_traits::value_type v_t; v_t v = *one; *one = *two; *two = v; } }; template <> struct swapper { template static void do_swap(I one, I two) { using std::swap; swap(*one, *two); } }; } template inline void iter_swap(I1 one, I2 two) { typedef typename std::iterator_traits::reference r1_t; typedef typename std::iterator_traits::reference r2_t; enum{ can_opt = boost::is_reference::value && boost::is_reference::value && boost::is_same::value }; detail::swapper::do_swap(one, two); } }; // namespace opt // // define some global data: // const int array_size = 1000; int i_array[array_size] = {0,}; const int ci_array[array_size] = {0,}; char c_array[array_size] = {0,}; const char cc_array[array_size] = { 0,}; const int iter_count = 1000000; int main() { // // test destroy_array, // compare destruction time of an array of ints // with unoptimised form. // cout << "Measuring times in micro-seconds per 1000 elements processed" << endl << endl; cout << "testing destroy_array...\n" "[Some compilers may be able to optimise the \"unoptimised\"\n versions as well as type_traits does.]" << endl; /*cache load*/ opt::destroy_array(i_array, i_array + array_size); boost::timer t; double result; int i; for(i = 0; i < iter_count; ++i) { opt::destroy_array(i_array, i_array + array_size); } result = t.elapsed(); cout << "destroy_array: " << result << endl; /*cache load*/ opt::destroy_array1(i_array, i_array + array_size); t.restart(); for(i = 0; i < iter_count; ++i) { opt::destroy_array1(i_array, i_array + array_size); } result = t.elapsed(); cout << "destroy_array(unoptimised#1): " << result << endl; /*cache load*/ opt::destroy_array2(i_array, i_array + array_size); t.restart(); for(i = 0; i < iter_count; ++i) { opt::destroy_array2(i_array, i_array + array_size); } result = t.elapsed(); cout << "destroy_array(unoptimised#2): " << result << endl << endl; cout << "testing copy...\n" "[Some standard library versions may already perform this optimisation.]" << endl; /*cache load*/ opt::copy(ci_array, ci_array + array_size, i_array); t.restart(); for(i = 0; i < iter_count; ++i) { opt::copy(ci_array, ci_array + array_size, i_array); } result = t.elapsed(); cout << "opt::copy: " << result << endl; /*cache load*/ std::copy(ci_array, ci_array + array_size, i_array); t.restart(); for(i = 0; i < iter_count; ++i) { std::copy(ci_array, ci_array + array_size, i_array); } result = t.elapsed(); cout << "std::copy: " << result << endl; /*cache load*/ opt::detail::copier::template do_copy(ci_array, ci_array + array_size, i_array); t.restart(); for(i = 0; i < iter_count; ++i) { opt::detail::copier::template do_copy(ci_array, ci_array + array_size, i_array); } result = t.elapsed(); cout << "standard \"unoptimised\" copy: " << result << endl << endl; /*cache load*/ opt::copy(cc_array, cc_array + array_size, c_array); t.restart(); for(i = 0; i < iter_count; ++i) { opt::copy(cc_array, cc_array + array_size, c_array); } result = t.elapsed(); cout << "opt::copy: " << result << endl; /*cache load*/ std::copy(cc_array, cc_array + array_size, c_array); t.restart(); for(i = 0; i < iter_count; ++i) { std::copy(cc_array, cc_array + array_size, c_array); } result = t.elapsed(); cout << "std::copy: " << result << endl; /*cache load*/ opt::detail::copier::template do_copy(cc_array, cc_array + array_size, c_array); t.restart(); for(i = 0; i < iter_count; ++i) { opt::detail::copier::template do_copy(cc_array, cc_array + array_size, c_array); } result = t.elapsed(); cout << "standard \"unoptimised\" copy: " << result << endl << endl; cout << "testing fill(char)...\n" "[Some standard library versions may already perform this optimisation.]" << endl; /*cache load*/ opt::fill(c_array, c_array + array_size, (char)3); t.restart(); for(i = 0; i < iter_count; ++i) { opt::fill(c_array, c_array + array_size, (char)3); } result = t.elapsed(); cout << "opt::fill: " << result << endl; /*cache load*/ std::fill(c_array, c_array + array_size, (char)3); t.restart(); for(i = 0; i < iter_count; ++i) { std::fill(c_array, c_array + array_size, (char)3); } result = t.elapsed(); cout << "std::fill: " << result << endl << endl; cout << "testing fill(int)...\n" "[Tests the effect of call_traits pass-by-value optimisation -\nthe value of this optimisation may depend upon hardware characteristics.]" << endl; /*cache load*/ opt::fill(i_array, i_array + array_size, 3); t.restart(); for(i = 0; i < iter_count; ++i) { opt::fill(i_array, i_array + array_size, 3); } result = t.elapsed(); cout << "opt::fill: " << result << endl; /*cache load*/ std::fill(i_array, i_array + array_size, 3); t.restart(); for(i = 0; i < iter_count; ++i) { std::fill(i_array, i_array + array_size, 3); } result = t.elapsed(); cout << "std::fill: " << result << endl << endl; // // testing iter_swap // really just a check that it does in fact compile... std::vector v1; v1.push_back(0); v1.push_back(1); std::vector v2; v2.push_back(0); v2.push_back(1); opt::iter_swap(v1.begin(), v1.begin()+1); opt::iter_swap(v2.begin(), v2.begin()+1); cout << "Press any key to exit..."; cin.get(); }