Click here to Skip to main content
16,020,114 members
Articles / DevOps / Testing

Performance comparison test suit

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
19 Aug 2013CPOL4 min read 23.4K   26   15   8
Code to easily compare performance of code variants.

Introduction

All tests frameworks that I saw are targeted towards correctness checking, and none provide performance comparison checking. May be I reinvented the wheel, but here is small test "suit" that simplifies management of performance comparison.

Background

When you write some code, sometimes you need a way to measure it's comparative performance. Most of the time it's quite simple - just write test function, time it with new code and old, and be done. But sometimes you face more complicated situation. For example, you try several variations of interdependent functions. Improvement in one function can lead in degradation in another. It might worth it (i.e. function 1 became 100% faster, but function 2 became 10% slower, and their usage is about equal), it might not worth it (i.e. function 1 became 1000% faster, function became 10% slower, but function 1 is used once in a year, while function 2 is used every second). I.e. sometimes you find yourself in a situation when you need some management of comparison tests.

Using the code

Usage is pretty simple. You define list of classes that you like to compare performance of (it could be variations of the same class, or completely unrelated classes), and write number of test functions. Test function signature is simple:

C++
struct my_test_name
{
  template<typename value_type> 
  void execute()
  {
   // your code to test on type "value_type"
  }
}

Because functions need to wrapped into struct, there is a macro to simplify declaration, so you can declare your test functions this way:

C++
COMPARATIVE_TEST(my_function_name)
{
// your code to test on type "value_type"
}

Then you just combine them in typelists, and feed to the test suit class. Here is an example. Suppose, you want to check what works faster: multiply by 3, or add same number 3 times? And how results compare for different types - int, float, double or _int64? What about division? (I understand that example is bogus, but it's here only to illustrate the syntax and class usage). So we declare three test functions:

C++
COMPARATIVE_TEST(mutiply)
{
   volatile value_type var;
   for (size_t i = 0; i<10000000; ++i)
      var=((value_type)i)*((value_type)3);
}
COMPARATIVE_TEST(mutiply_via_add) 
{
   volatile value_type var;
   for (size_t i = 0; i<10000000; ++i)
      var= (value_type)i + (value_type)i + (value_type)i;
}
COMPARATIVE_TEST(divide)
{
   volatile value_type var;
   for (size_t i = 1; i<10000000; ++i)
      var=(value_type)100000000/(value_type)i;
}

(I added "volatile" to stop compiler from optimizing code away). Now, declare our typelist for types to check:

C++
typedef  SimpleTypeList<int>::
         Append<float>::Result::
         Append<double>::Result::
         Append<__int64>::Result TestTypes;

Or, you can use a macro instead:

C++
typedef  TYPELIST_4(int, float, double, __int64) TestTypes;

Then, declare typelist for test functions:

C++
typedef  SimpleTypeList<mutiply>::
         Append<mutiply_via_add>::Result::
         Append<divide>::Result   TestFuncs;

Or, again use a macro:

C++
typedef  TYPELIST_3(mutiply, mutiply_via_add, divide) TestFuncs;

And finally, use the test suit:

C++
CSimpleComparativeTest test;
test.registerTests<TestTypes, TestFuncs>();

When you call "registerTests", it generates combination of all provided types with all provided functions. In this example, it'll create 12 "type-function call" combinations. If, let's say, you have few functions that should be executed only on a subset of types, define another typelists, and make another call to registerTests with those typelists - new combination will be added to the existing ones. For example, we want to check float multiplication only for float types. First, let's define test function for that:

C++
COMPARATIVE_TEST(multiply_float)
{
   volatile value_type var;
   for (size_t i = 1; i<10000000; ++i)
      var=((value_type)i)*((value_type)1.234f);
}

Now define our typelists, and register this combination in addition to previous:

C++
typedef  TYPELIST_2(float, double) TestFloatTypes;
typedef  TYPELIST_1(multiply_float) TestFloatFuncs;
test.registerTests<TestFloatTypes, TestFloatFuncs>();

Now we are ready to execute tests:

C++
test.test(10);

During this call, each "type-function call" combination will be executed 10 times (parameter of "test") in random order, and then results will be averaged. Now, you can get an output table:

C++
std::string result = test.getResult();

Output table looks like this:

Image 1

You also can get it in transponed view:

C++
std::string result = test.getResult(true);

Image 2

You see "N/A" in those cells which didn't have a combination to execute. Numbers in cells represent averaged time (in ticks, each tick is ~1ms) that each combination took to execute. Of course, this is wall time, and on the same machine in different periods numbers will somewhat differ, but the whole purpose of the suit is comparative measure, not the absolute one. Some classes have very long system name, especially templates - their names get fully unrolled, which makes them almost non-readable. If this is a case, after you registered those classes, you can set "nice" names for the output, for example:

C++
test.setTypeName<std::string>("std::string");

Same goes for function names:

C++
test.setFunctionName<myverylongunreadablefunctionname>("nice_name");

Also, you can call "clear()" method to clear all combinations and start again.

Download contains five files:

  1. SimpleTest.h - the actual "suit" implementation
  2. SimpleTypeLists.h - helper file (simple, straightforward type list implementation)
  3. SimpleAnyValue.h - helper file (implementation of "any", somewhat similar to boost::any, but extended)
  4. Test.cpp - example file
  5. Test.vcxproj - example project (VC10)

That's it, and happy coding!

Update 19-Aug-2013:

I had a second chance to look at the code, so I cleaned interface and code; now single type/func don't need to be wrapped into TYPELIST_1() - you can register types/functions as is, i.e. "registerTests<int, funcname>()".

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionwhy can not I download the file? Pin
szcathay19-May-15 15:52
szcathay19-May-15 15:52 
AnswerRe: why can not I download the file? Pin
Kosta Cherry20-May-15 17:59
Kosta Cherry20-May-15 17:59 
QuestionI like this concept Pin
Paul M Watt8-Apr-15 7:44
mentorPaul M Watt8-Apr-15 7:44 
AnswerRe: I like this concept Pin
Kosta Cherry11-Apr-15 20:32
Kosta Cherry11-Apr-15 20:32 
QuestionGood stuff (+5) Pin
H.Brydon5-Sep-13 20:40
professionalH.Brydon5-Sep-13 20:40 
AnswerRe: Good stuff (+5) Pin
Kosta Cherry6-Sep-13 4:16
Kosta Cherry6-Sep-13 4:16 
GeneralMy vote of 5 Pin
Paulo Zemek14-Aug-13 14:17
Paulo Zemek14-Aug-13 14:17 
GeneralRe: My vote of 5 Pin
Kosta Cherry14-Aug-13 14:27
Kosta Cherry14-Aug-13 14:27 
Thank you! Although it's a standard recursion, just done template way Smile | :)

modified 15-Aug-13 0:08am.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.