I am involved in developing a C ++ project using the Qt framework. In our project, Qt containers are used in many places and the macro foreach is often used to bypass elements. At one point, I wondered how justified the use of this macro was. In addition, I really wanted to "touch" c ++ 11 in action. And that's what I managed to find out at the moment ...
Writing support functions
Containers in STL and Qt do not have a common base class, so an attempt was made to avoid template functions in order not to write a test function for each container, since Qt containers support STL-style iterators.
At the moment, my main function for testing looks like this:
template<typename TEST_CLASS> void runTests(int testCycles) { auto container = makeTestClass<TEST_CLASS>(); using namespace TestProcs; static auto tests = TestProcs::getRegisteredTests<TEST_CLASS>(); std::cout << "Run test for type:" << typeid(TEST_CLASS()).name() << std::endl; for (auto test : tests) { auto warmResultsTest = makeTestResults(*container, test.second, testCycles, true); std::cout << "\"" << test.first << "\" results(ms):" << getResultsString(warmResultsTest) << std::endl; } }
The testCycles parameter specifies the number of iterations per test. The makeTestClass template function constructs a class for testing. For vectors, it looks like this:
template<typename TEST_CLASS> std::shared_ptr<TEST_CLASS> makeTestClass() { return std::shared_ptr < TEST_CLASS > (new TEST_CLASS(VECTOR_SIZE, 0)); }
Well, for QList something like this:
template<> std::shared_ptr<QList<TestType> > makeTestClass() { return std::shared_ptr < QList<TestType> > (new QList<TestType>( QList<TestType>::fromVector(QVector<TestType>(VECTOR_SIZE)))); }
TestProcs :: getRegisteredTests returns a vector from the test name and procedure to perform the test on the container.
makeTestResults itself executes the test a specified number of times and returns the vector of the measured execution time for each iteration. According to this data, the average, minimum and maximum value of the test run iteration time is considered.
The list of tests that I conducted
- std :: accumulate (doAccumulateTest)
- Qt macro foreach (doQtForeachTest)
- range-based for (doNewForTestt)
- STL-style for (doSTLForTest)
- STL-style for calculating the end of a container outside of a loop (doSTLForTest2)
namespace TestProcs { template<typename CONTAINER> int doAccumulateTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); sum = std::accumulate(container.begin(), container.end(), sum); return sum; } template<typename CONTAINER> int doQtForeachTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); foreach(auto item, container) { sum += item; } return sum; } template<typename CONTAINER> int doNewForTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); for (auto item : container) { sum += item; } return sum; } template<typename CONTAINER> int doSTLForTest(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); auto it = container.begin(); for (; it != container.end(); ++it) { sum += *it; } return sum; } template<typename CONTAINER> int doSTLForTest2(CONTAINER& container) { typename CONTAINER::value_type sum = typename CONTAINER::value_type(); auto it = container.begin(); auto end = container.end(); for (; it != end; ++it) { sum += *it; } return sum; } template<typename TEST_CLASS> const std::vector<typename Typedefs<TEST_CLASS>::TestProcRecord>& getRegisteredTests() { static const std::vector<typename Typedefs<TEST_CLASS>::TestProcRecord> tests { { "New for", &doNewForTest<TEST_CLASS> }, { "Accumulate", &doAccumulateTest<TEST_CLASS> }, { "Qt foreach", &doQtForeachTest<TEST_CLASS> }, { "STL for", &doSTLForTest<TEST_CLASS> }, { "STL for 2", &doSTLForTest2<TEST_CLASS> } }; return tests; } }
')
Test results
The list of containers subjected to testing:
- QVector <qint32> (runTests <QVector <TestType>> (TEST_CYCLES))
- std :: vector <qint32> (runTests <std :: vector <TestType>> (TEST_CYCLES))
- QList <qint32> (runTests <QList <TestType>> (TEST_CYCLES))
The number of iterations for each test = 20.
The size of the container data is 30 megabytes.
Time was measured using the difference in
rdtsc divided by
per 100,000.
Actually, the results table (less value means faster):

Graphical presentation of data for the result table:



Total
What I learned for myself by conducting the tests:
- The foreach macro is almost always slower than the "correct" STL cycles (such that the final iterator value is calculated before the loop begins)
- The macro foreach is categorically contraindicated for use with STL-containers, because makes a deep copy of containers (in Qt containers COW is used, therefore they are not subject to catastrophic "subsidence" of performance)
- For std :: vector, it doesn't matter where the final iterator value is calculated (before the start of the loop or “inside” the body)
My opinion is that you need to stop using the foreach macro in Qt projects.
Thanks for attention.
PS: yes, I know that you can write ">>" without a space.
Source