Introduction
Hi! I want to offer to you tree tools, which
help me in my everyday work (especially first two). Of course, they are maybe
successfully used a years before this article, but I’m not claiming to be
original author of these tools. I simply want to share them, as I do it for my
students. So, let’s begin.
A first one: A
cross platform execution measurement tool
If you will say after reading this part, that “this
is a bicycle” I will agree. But I like my bicycles! They are simple,
convenient, are fast to write them and there is no need to study some big tool,
intended to cover everything, if I want simply to compare two algorithms to
deduce: which are faster. Working under Windows I’m used to
QueryPerformanceCounter
api function, which “retrieves
the current value of the high-resolution performance counter”
(citation from MSDN) . But work with cross platform applications has required
more versatile tool. Here it is:
#include <stdint.h>
#ifdef _WIN32 // Windows
#include <intrin.h>
uint64_t rdtsc()
{
return __rdtsc();
}
#else // Linux/GCC
uint64_t rdtsc()
{
unsigned int lo,hi;
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
#endif //_WIN32
#define EXPR_TIME_CONS_AVER(nCount, expr, bShowCurTime)\
{\
char ExprText[sizeof(#expr) + 1] = {0};\
memcpy(ExprText, #expr, sizeof(#expr));\
if(bShowCurTime == true)\
cout<<"=== "<<ExprText<<" === start"<<endl;\
uint64_t ui1, ui2, uiTicks, uiAverage = 0;\
for(int iIn = 0; iIn < nCount; ++iIn)\
{\
ui1 = rdtsc();\
expr;\
ui2 = rdtsc();\
uiTicks = ui2 - ui1;\
uiAverage += uiTicks;\
if(bShowCurTime == true)\
cout<<uiTicks<<endl;\
}\
cout<<"=== "<<ExprText<<" average == "<<uiAverage / nCount<<"\n\n";\
}
This is a
macro. Despite the fact, that we can’t debug them, they still have more pluses
(de facto, we can debug macroses simply by put previously their code in a
function).
- We can pass to it entire instruction. Not as a parameter in a function,
which must have some type, but pass any instruction or even several of them.
#
operator can save any text (don’t forget: any code is just a plain text),
and gives us ability to work with passed instruction as with conventional const
char *.
Let me
explain every tricky lines of code:
rdtsc();
- envelop
function that returns a number of CPU clock’s ticks.
#define EXPR_TIME_CONS_AVER(nCount, expr, bShowCurTime)
It is a definition of the macros, which intended to
show in console an average execution time.
First param nCount
– how many times a
passed instruction must be executed to calculate an average execution time
(here in the cycle for(int i;… ).
Second expr – instruction, that
will be executed in the cycle.
Third bShowCurTime
– is
responsible for showing in console window a current execution time for every
iteration. It is helpful if we want to see approximately pure execution time,
because if during execution of our instruction another thread will interfere
and switch context, time will be drastically bigger. (You can add in this
macros additional parameters – array of uint64_t
and
it’s size to process outside the times and exclude those values, that are
definitely out of sequence.)
char ExprText[sizeof(#expr) + 1] = {0};
Here we have
the following:
#expr
– treats
passed instruction as a
const char *
.
sizeof(#expr) + 1
– operator sizeof can calculate at compile time a size of char
array (our instruction), so we can declare another char array
ExprText
(to be a buffer) on the stack.
+1
needed to put
‘\0’
at the it’s end = {0}; . (to
output it using convenient c/c++ tools)
char
ExprText
– I
included this array in code simply as a hint: how you can return this string
out of macros to work with it in any way you like. You can use
#
operator in a
macros only.
For example:
- Let’s compare per element copying of int array in a “
for
” cycle with work of
memcpy function.
- What is faster? Copy constructor with 9 lines in disassembled code or move
constructor with 16 lines. (in gcc you need to add c++11 support to compiler
arguments) In Array.cpp
Array::Array(const Array & Right): m_nSize(Right.m_nSize),
m_nReadonly(Right.m_nReadonly)
{
m_pAr = new int[m_nSize];
memcpy(m_pAr, Right.m_pAr, sizeof(int) * m_nSize);
}
Array::Array(Array && Right) noexcept : m_nSize(Right.m_nSize),
m_pAr(Right.m_pAr), m_nReadonly(Right.m_nReadonly)
{
Right.m_nSize = 0;
Right.m_pAr = 0;
}
In main:
#define ARR_SIZE 2000
int iAr[ARR_SIZE], iAr2[ARR_SIZE];
for(int i = 0; i < ARR_SIZE; ++i)
iAr[i] = rand();
int nSamplesCount = 3;
EXPR_TIME_CONS_AVER(nSamplesCount,
for(int j = 0; j < ARR_SIZE; ++j) iAr2[j] = iAr[j], true);
EXPR_TIME_CONS_AVER(nSamplesCount,
memcpy(iAr2, iAr, sizeof(int) * ARR_SIZE), true);
cout<<endl;
Array ar(10);
EXPR_TIME_CONS_AVER(nSamplesCount, Array ar2(ar), false);
EXPR_TIME_CONS_AVER(nSamplesCount, Array ar3(std::move(ar)), false);
Output (gcc 4.8.0):
Try it, and you will discover a lot of
interesting and unexpected, particularly working with different compilers
(trust me!).
Second tool: Using preprocessor to write a simple test
units.
I have
started to use this trick since I was a freelancer. It is simply a convenient
feature of preprocessor , which I use to quickly turn ON or OFF a testing code
in my (not very big) projects. Now I use it also to accustom my students to
check all questionable data together with assert
s, exception handling or writing in file.
Boost.test
and CppUnit
are really good, but they required a lot of time to
master it. Here is the much simpler variant. Look:
We have two
ways to declare our own directive: as a macros:
#define Test_FuncTime(param) param;
and as a directive itself - without expanding it to some code or value:
#define Test_FuncTime(param)
If we pass
as a param an instruction or a function call, it can be expanded in place of
macros as code itself, or as nothing. So, I always write these defines in
pairs. One of them must be commented. If I want to test something in my
application, I write some kind of approving code, which de facto polluted a
program code and hamper it's readability. So, when I need a test - I uncomment
macros, when I want to hide this extra output - I comment macros and
simultaneously uncomment empty define. In my opinion, it is very convenient
tool.
Let’s look
at it's expanding but still simple usage:
To cover any new task with test, you must simply copy-past these declarations
and give them appropriate name according to task number or something like that.
(Test directive name can contain for example the name of this module of the big
project.) If we want some kind of extensive test, we can write a function with
as many parameters, as we need and simply call it from macro. I like the
following c++ code organization: I have one header for task descriptions, and
one for tests declarations (#define Test#123(a) a;
) with good comments,
describing in general what I want to test. Next, because one compilation unit
(a *.cpp file) contains all includes to write a code, at the top of file after
them I declare in a separated section all my prototypes. At the end -
definitions of all functions with detailed description of what I must receive
here!! I do it in every particular function, to quickly remember all after
returning to it again. If you want to collect some data and pass it in another
part of test - no problem. If your application is a cross platform one, and you
want to see test data not in debug but as a program output - use pattern Bridge
to output information. That is: abstract base output class, and several
children for every particular platform. That's all.
Another
feature, that I like in my tests - they are placed in the code just as one line
only, which I put after 80-th column - not to see them during work and
thinking. Here is the real position of tests in my code:
Empty lines
at the end of some method definitions (like 19-th and 27-th above) serve for me
as a hint – “there are something”. If you want to see them - simply scroll to
the right, or look at your tests covering log - which tasks are covered with
tests.
Let’s see at
example (all examples are included with article as a zip file.):
Array::~Array()
{
delete [] m_pAr;
Test123_ClassArray(cout<<"~Array()"<<this<<' '<<m_nSize<<endl);
}
Of course, this isn't a real test. In this way I explain to my students
all stages of object’s life and when and what class functionality
(constructors, destructors and so on) are called. So, such tests are included
in every studied places (in every files). You can switch them ON:
#define Test123_ClassArray(a) a;
and see: in
main
{
std::vector<Array> vc;
int nCount = 2;
for(int i = 0; i < nCount; ++i)
{
Test123_ClassArray(cout<<"-- before push: size cap "\
<<vc.size()<<' '<<vc.capacity()<<endl);
vc.push_back(Array(rand()% 21));
Test123_ClassArray(cout<<"-- after push: size cap "\
<<vc.size()<<' '<<vc.capacity()<<"\n\n");
}
for(int i = 0; i < nCount; ++i)
vc[i].Show();
}
which will produce:
or OFF:
#define Test123_ClassArray(a)
And receive only
The last trick: Template for overloading operator =.
We have to
overload operator =
if we are allocating resource and are not against to allow
for users of our class to call it. (we can forbid usage of this operator.) A
classical actions sequence:
- check – are we assigning an object to itself?;
- allocate exactly the same resources as has right operand;
- free our own resource;
- deep copy;
- return
*this
;
But... What
if a class has a const member – so called readonly
field? (c# has this keyword
) It is a convenient tool if we want to set a member variable in runtime and
guaranty, that it will remain unchanged until death of this object.
class Array
{
int m_nSize;
int * m_pAr;
const int m_nReadonly;
In this case
any attempts to reassign it in our overloaded operator =
will make our compiler
angry. There are two solutions:
1) Follow the intention of this tool - if you use const member – don’t change
it!!! You must redesign your program.
2) If such reassignment is not a work around, but completely suitable to your
program logic, you must remember about copy constructor.
I’m
intentionally in overloading sequence put allocating a new resource before it
will be free . This is related to exception and transaction safety – we are
guarantying that left operand will have resource anyway. (If according to logic
our left operand have a choice: store the old resource or receive a new one.)
So, we will use copy constructor to create a temporary object because:
- We will in this way create a new resource before we decide to delete former.
- In overloaded
operator =
we will perform deep copy, which is already well
done in copy constructor. So, we will reuse existed code.(By the way, as a
rule, if we decided to allow usage a copy construction of our objects, we will
give to user a possibility to assign it too and vice versa.)
- This is a completely legitimate way to reassign const member. (There is no
need to hack it.) First
variant:
Array & Array::operator = (const Array & Right)
{
if(this != &Right)
{
Array temp(Right);
char sBuff[sizeof(Array)];
memcpy(sBuff, this, sizeof(Array));
memcpy(this, &temp, sizeof(Array));
memcpy(&temp, sBuff, sizeof(Array));
}
return *this;
}
Here I
create deep copy of Right
object, create buffer sBuff
to swap left operand
(*this
) with temp
and perform the swap itself. At closed bracket “}” temp
will
die, as any variable allocated on stack, and de facto will solve all our
problems with old resources.
This action
sequence is common for all classes, so let it be a template:
template <typename T>
void TSwap(T * pThis, const T & Right)
{
T temp(Right);
char sBuff[sizeof(T)];
memcpy(sBuff, pThis, sizeof(T));
memcpy(pThis, &temp, sizeof(T));
memcpy(&temp, sBuff, sizeof(T));
}
Now a modified operator = will look like
this:
Array & Array::operator = (const Array & Right)
{
if(this != &Right)
TSwap(this, Right);
return *this;
}
All functionality was tested on Windows XP,
7 in VS2010 and VS2012 (Using these compilers you must comment keyword noexcept in class Array
move constructor due to not complete support of c+11 standard by Microsoft.).
and on Ubuntu 14.04 with gcc 4.8.2 (here all works as it is in .zip file.).
Good luck!!