For some reason, all of the functions in my program with a map as a parameter are not working. This function is the one that calls all of them (pageAndTimestamp is a struct btw):
void fifo(int framesize, int numref, int* pagestream)
{
double hit = 0, size = numref;
map<int, pageAndTimestamp> frames = frameMaker(framesize);
for (int time = 0; time < numref; time++)
{
if (pageLoaded(pagestream[time], frames))
{
hit++;
output(time, pagestream[time], size, hit, frames);
}
else
{
int loc = findPageToReplace(frames);
replacePage(loc, pagestream[time], time, frames);
output(time, pagestream[time], size, hit, frames);
}
}
}
These are the functions that are not working properly:
bool pageLoaded(int page, map<int, pageAndTimestamp> m)
{
for (const auto& it : m)
{
if (it.second.a[0] == page)
return true;
}
return false;
}
int findPageToReplace(map<int, pageAndTimestamp> m)
{
int timestamp = INT_MAX;
int replaceLoc = 0;
for (const auto& it : m)
{
if (it.second.a[1] == -1)
return it.first;
else
{
if (it.second.a[1] < timestamp)
{
timestamp = it.second.a[1];
replaceLoc = it.first;
}
}
}
return replaceLoc;
}
void replacePage(int loc, int page, int time, map<int, pageAndTimestamp> m)
{
m.at(loc).a[0] = page;
m.at(loc).a[1] = time;
}
void output(int t, int p, double s, double h, map<int, pageAndTimestamp> m)
{
cout << "Time: " << t << endl << "Page: " << p << endl;
for(const auto& it : m)
cout << "Frame" << it.first << ": " << it.second.a[0] << endl;
cout << "Hit ratio: " << h << " / " << s << " (" << h / s << ")" << endl
<< endl << endl;
}
When I run the program in the Visual Studio 2017 debugger, when I step into any of the above functions, the debugger takes me to this function header in the map standard header:
map(const map& _Right)
: _Mybase(_Right, _Alnode_traits::select_on_container_copy_construction(_Right._Getal()))
{ // construct map by copying _Right
}
I don't know what the problem is, or why the debugger is taking me to this function header. How do I fix this?
The function replacePage for example, is defined as:
void replacePage(int loc, int page, int time, map<int, pageAndTimestamp> m)
This function takes a map as a value, not a reference or pointer. Thus, when you call it as follows:
replacePage(loc, pagestream[time], time, frames);
then the map frames is copied into the variable m in your function. This is why the debugger, for example, takes you to the copy constructor for a map.
Further, it means that the replacePage code
m.at(loc).a[0] = page;
m.at(loc).a[1] = time;
is making its changes to a copy of frames, not frames itself.
You probably want functions with signatures of the form:
bool pageLoaded(int page, const map<int, pageAndTimestamp>& m)
int findPageToReplace(const map<int, pageAndTimestamp>& m)
void replacePage(int loc, int page, int time, map<int, pageAndTimestamp>& m)
void output(int t, int p, double s, double h, const map<int, pageAndTimestamp>& m)
in which most of the functions take a constant reference, while replacePage requires a (non-const) reference.
Related
I've got a C++ program with two input files and an output file. The first input file has a list of products (subclass stock), and the second input file has a list of orders. I've worked out how to deal with most of the problems that I've had, and I've gotten so close, but I need to read the items in the arrOrders vector, and compare them with the orderTitle and stockLevel in the arrStock vector. I've managed to get it to work for the first item in the order list, but I can't get it to work beyond there.
class Product {
public:
std::string title, surname;
long long int isbn;
double wholesalePrice;
void setProductInfo(std::string, std::string, long long int, double);
void setTitle(std::string);
void setSurname(std::string);
void setWholesalePrice(double);
void setIsbn(long long int);
double getWholesalePrice();
long long int getIsbn();
std::string getTitle();
std::string getSurname();
Product();
~Product();
Product(std::string, std::string, long long int, double);
};
class Stock :public Product
{
public:
double retailPrice;
char bookFormat;
int stockLevel;
std::string calcRP;
Stock(std::string, int, char, std::string, double, long long int, double);
Stock();
~Stock();
double getRetailPrice();
char getBookFormat();
int getStockLevel();
void setStockLevel(int);
void setBookFormat(char);
void setRetailPrice(double);
};
class Order {
public:
std::string orderTitle;
int orderStock;
Order(std::string, int);
Order();
~Order();
void setStock(int);
void setTitle(std::string);
std::string getTitle();
int getStock();
};
void importBooks();
void runReport();
void newBook();
void delBook();
std::vector<Stock> arrStock{};
std::vector<Order> arrOrders{};
void checkStock();
//lots of code
//checkStock function
void checkStock()
{
std::ifstream inFile("orders_v5.txt");
Order anOrder;
std::string orderTitle;
int orderStock;
inFile >> anOrder.orderTitle >> anOrder.orderStock;
arrOrders.push_back(anOrder);
std::cout << "#########################################################################################"; std::cout << std::endl;
std::cout << " ORDER REPORT"; std::cout << std::endl;
std::cout << "#########################################################################################"; std::cout << std::endl;
// code adapted from https://en.cppreference.com/w/cpp/algorithm/find
for (unsigned int k = 0; k < arrOrders.size(); k++)
{
orderTitle = arrOrders[k].getTitle();
orderStock = arrOrders[k].getStock();
std::vector<Stock>:: iterator test1 = find_if(arrStock.begin(), arrStock.end(), [&orderTitle](const Stock stock)->bool
{
for (unsigned int m = 0; m < arrStock.size(); m++)
{
return stock.title == orderTitle;
}
});
std::vector<Stock>::iterator test2 = find_if(arrStock.begin(), arrStock.end(), [&orderStock](const Stock stock)->bool
{
for (unsigned int m = 0; m < arrStock.size(); m++)
{
return arrStock[m].getStockLevel() >= orderStock;
}
});
if (test1 != arrStock.end())
{
if (test2 != arrStock.end())
{
std::cout << orderTitle << " is available at Biblioden and there is enough stock to fulfil your older.";
}
else
{
std::cout << orderTitle << " is available at Biblioden but there is not enough stock to fulfil your order.";
}
}
else
{
std::cout << orderTitle << " is not available at Biblioden.";
}
}
}
I am using 4 threads to create a few objects using a thread_local memory pool.
I am using std::vector<std::future<int>> and std::async(std::launch::async, function); to dispatch the threads and std::for_each with t.get to get their value back. Here's the code:
struct GameObject
{
int x_, y_, z_;
int m_cost;
GameObject() = default;
GameObject(int x, int y, int z, int cost)
: x_(x), y_(y), z_(z), m_cost(cost)
{}
};
struct Elf : GameObject
{
Elf() = default;
Elf(int x, int y, int z, int cost)
: GameObject(x, y, z, cost)
{
std::cout << "Elf created" << '\n';
}
~Elf() noexcept
{
std::cout << "Elf destroyed" << '\n';
}
std::string m_cry = "\nA hymn for Gandalf\n";
};
struct Dwarf : GameObject
{
Dwarf() = default;
Dwarf(int x, int y, int z, int cost)
: GameObject(x, y, z, cost)
{
std::cout << "dwarf created" << '\n';
}
~Dwarf() noexcept
{
std::cout << "dwarf destroyed" << '\n';
}
std::string m_cry = "\nFind more cheer in a graveyard\n";
};
int elvenFunc()
{
thread_local ObjectPool<Elf> elvenPool{ 229 };
for (int i = 0; i < elvenPool.getSize(); ++i)
{
Elf* elf = elvenPool.construct(i, i + 1, i + 2, 100);
std::cout << elf->m_cry << '\n';
elvenPool.destroy(elf);
}
thread_local std::promise<int> pr;
pr.set_value(rand());
return 1024;
}
int dwarvenFunc()
{
thread_local ObjectPool<Dwarf> dwarvenPool{ 256 };
for (int i = 0; i < dwarvenPool.getSize(); ++i)
{
Dwarf* dwarf = dwarvenPool.construct(i - 1, i - 2, i - 3, 100);
std::cout << dwarf->m_cry << '\n';
dwarvenPool.destroy(dwarf);
}
thread_local std::promise<int> pr;
pr.set_value(rand());
return 2048;
}
int main()
{
std::ios_base::sync_with_stdio(false);
srand(time(0));
std::vector<std::future<int>> vec{ 4 };
vec.emplace_back(std::async(std::launch::async, elvenFunc));
vec.emplace_back(std::async(std::launch::async, elvenFunc));
vec.emplace_back(std::async(std::launch::async, dwarvenFunc));
vec.emplace_back(std::async(std::launch::async, dwarvenFunc));
int term = 0;
try
{
std::for_each(std::execution::par, vec.begin(), vec.end(), [&term](std::future<int>& t)
{
auto ret = t.get();
std::cout << "thread brought me " << ret << '\n';
term += ret;
});
}
catch (const std::exception& ex)
{
std::cout << ex.what() << '\n';
}
std::cout << "Final word = " << term << '\n';
}
(the construct and destroy call allocate and deallocate internally.) I get a lot of expected output from the terminal, but somewhere along the lines abort gets called and the program doesn't complete normally. I can't find out why. I believe that the t.get() call of a thread that's been started with std::async automatically calls .join too right?
Using C++17 and Visual Studio 2017. What am I doing wrong?
You have undefined behaviour. You call get on non-valid future. Your vector of futures has first 4 items as empty future. get can be called only if future::valid returns true.
What do you think this line
std::vector<std::future<int>> vec{ 4 };
does ?
Default constructor. Constructs a std::future with no shared state.
After construction, valid() == false.
And here you can read what happens when future::get is called:
The behavior is undefined if valid() is false before the call to this
function.
I've been trying to use a hash_multimap for sometime now, but the find method keeps giving me a iterator to the end of the container even though I know it found a matching key. What has me confused is that I've used the same code before for a different project with it working perfectly but now its playing up. The reason I know its finding something is because I've put a few cout in the hash function and hash compare, which is telling me that a key is found and that it matches what I gave the hash_multimap::find meathod, yet still it gives me an iterator.
first the header file
//
// HashGrid.h
// Planetarium
//
// Created by Taura J Greig on 24/08/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#ifndef _HASHGRID_
#define _HASHGRID_
#include <iostream>
#include <hash_map>
#include <deque>
#include "..//hashKey.h"
struct myTraits
{
static const size_t bucket_size = 4;
static const size_t min_buckets = 8;
myTraits() { };
myHash hashfunction;
myEqualTo equal_to;
size_t operator() (const hashKey& key) const
{
size_t hashval = 0;
hashval = ((key.y * globalGridWidth) + key.x);
cout << "x : " << key.x << " y : " << key.y << endl;
cout << "hashVal : " << hashval << endl;
return hashval;
}
bool operator() (const hashKey& key1, const hashKey& key2) const
{
bool test = (key1.x == key2.x && key1.y == key2.y);
cout << "equal_to = " << test << endl;
return test;
}
};
using namespace std;
//using namespace stdext;
using namespace stdext;
template <class T>
class HashGrid
{
public:
typedef deque<T *> localObjects;
typedef pair<hashKey, T *> addingPair;
typedef hash_multimap <hashKey, T *, myTraits> hashmMap;
typedef typename hash_multimap <hashKey, T *, myTraits> :: iterator hashmMapItor;
typedef pair<hashmMapItor, hashmMapItor> valueRange;
private:
hashKey keyOffsets[9];
int gridSize;
hash_multimap<hashKey, T*, myTraits> theMap;
inline bool exists(hashKey & theKey);
inline bool exists(hashKey & theKey, hashmMapItor & it);
public:
HashGrid();
void setup(int gridSize);
void update();
void draw(); // this is used for viusal debug,
void resize();
void addObject(T * object);
void getLocalObjects(float & x, float & y, int range, localObjects & p1);
};
template <class T>
inline bool HashGrid<T>::exists(hashKey & theKey)
{
hashmMapItor it;
it = theMap.find(theKey);
if (it == theMap.end())
{
return false;
}
else
{
return true;
}
}
template <class T>
inline bool HashGrid<T>::exists(hashKey & theKey,
hashmMapItor & it)
{
it = theMap.find(theKey);
if (it == theMap.end())
{
return false;
}
else
{
return true;
}
}
#include "HashGrid.cpp"
#endif
and the source file
//
// HashGrid.cpp
// Planetarium
//
// Created by Taura J Greig on 26/08/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#ifndef _HASHGRID_SOURCE_
#define _HASHGRID_SOURCE_
#include "HashGrid.h"
#include "ofMain.h"
template<class T>
void HashGrid<T>::update()
{
theMap.clear();
}
template <class T>
void HashGrid<T>::addObject(T *obj)
{
hashKey tempKey;
tempKey.x = int(obj -> getPos().x) / gridSize;
tempKey.y = int(obj -> getPos().y) / gridSize;
cout << "tempKey.x : " << tempKey.x << endl;
cout << "tempKey.y : " << tempKey.y << endl;
theMap.insert(addingPair(tempKey, obj));
}
template <class T>
void HashGrid<T>::getLocalObjects(float & x, float & y, int range, localObjects & p1)
{
cout << "you are gettin local objects" << endl;
int gridX = (int(x) / gridSize);
int gridY = (int(y) / gridSize);
cout << "player x : " << x << endl;
cout << "player y : " << y << endl;
cout << "girdX " << gridX << endl;
cout << "girdY " << gridY << endl;
for (int i = 0; i < 9; i++)
{
hashKey tempkey;
tempkey.x = gridX;
tempkey.y = gridY;
tempkey += keyOffsets[i];
cout << i << " tempKey : " << tempkey.x << " " << tempkey.y << endl;
cout << "exists " << exists(tempkey) << " ";
//this is where the problem lies, the exists function will always return
//false even when the key is found
if (exists(tempkey))
{
cout << "found" << endl;
hashmMapItor it;
valueRange elements;
elements = theMap.equal_range(tempkey);
for (it = elements.first; it != elements.second; it++)
{
p1.push_back(it->second);
}
}
else
{
cout << "not found" << endl;
}
}
}
#endif
Note that I've cut a lot of methods out of the block above to save space because they are unrelated to the problem at hand. However I've left their declarations in the header file. Also I am aware that there a few things that I'm doing with templates that are ugly. Just deal with it for now.
Now I'll go into detail about whats happening in the code and where the problem lies. In the getlocalobjects method, the method "exists(key)" is called to determine if the hash_multimap has an element with the key provided. I know that it does find something because as I mentioned above because I put cout in the equal_to function to tell me when its used an what its result are.
Consistently its telling me yes (via equal_to debug) it found something but the exist method will still return false. This leading me to believe that there may be a bug in hash_multimap::find since it means that even if it finds something its gives me an iterator to hash_multimap::end
So my question is am I doing horribly wrong regarding the use of the multimap? does my traits struct not have something required for the multimap to work correctly
EDIT and the implementation for the hashKey that i forgot it include
header
#ifndef _HASHKEY_
#define _HASHKEY_
#include <iostream>
using namespace std;
static int globalGridSize = 1;
static int globalGridWidth = 1;
static int globalGridHeight = 1;
struct hashKey
{
public:
int x;
int y;
hashKey();
hashKey(int x, int y);
void set(int x, int y);
void set(hashKey & key);
void printKey()
{
cout << x << " " << y << endl;
}
bool operator < (const hashKey & key1) const;
bool operator == (const hashKey & key1) const;
hashKey& operator += (hashKey & key1);
};
#endif
and source
#ifndef _HASHKEY_SOURCE_
#define _HASHKEY_SOURCE_
#include "hashKey.h"
hashKey::hashKey()
{
x = 0;
y = 0;
}
hashKey::hashKey(int x, int y)
{
hashKey::x = x;
hashKey::y = y;
}
void hashKey::set(int x, int y)
{
hashKey::x = x;
hashKey::y = y;
}
void hashKey::set(hashKey &key)
{
x = key.x;
y = key.y;
cout << "set: x = " << x << " y = " << y << endl;
}
bool hashKey::operator<(const hashKey &key1) const
{
if ( (this->x < key1.x) && (this->y < key1.y))
{
return true;
}
return false;
}
bool hashKey::operator == (const hashKey &key1) const
{
if ((this-> x == key1.x) && (this->y == key1.y))
{
return true;
}
return false;
}
hashKey& hashKey::operator+=(hashKey &key1)
{
this->x += key1.x;
this->y += key1.y;
return *this;
}
#endif
EDIT [SOVLED] I changed the hash_multimap tp an unordered_multimap and now it works, so initial suspicion was right, that at this time the hash_multimap is bugged an its find method will always give an iterator to the the end. Note that i'm using visual studio c++ 2010, it may not be bugged on other platforms or other compilers, however it defiantly was bugged in my case
The content below is speculation as not all the relevant code is visible.
It seems that you have:
A hash which is of type size_t (as created from the first operator() of myTraits)
A key of type hashKey (which is not a hash from the hash_multimap's perspective)
You did not provide the implementation of hashKey, so my immediate question is:
Did you provide the equality operator for hashKey?
Or alternatively, did you override equal_to<haskHey>?
The potential problem (and reason for the above questions) that I see is that you defined your hashmMap as hash_multimap <hashKey, T *, myTraits> which overrides the hashing function, but it does not override the key equality (which is of type hashKey). So, I presume that the default comparator of hashKey (and not the one defined in myTraits) might be used.
Perhaps hash_multimap <hashKey, T *, myTraits, myTraits> would suffice?
Update: I just notice that VS's hash_multimap has a different signature, than the one coming from STL. Compare:
Visual Studio version
STL version
The latter has hashing function and key comparator separated. This is just asking for terrible problems once you switch compilers!
I have requirement as follows.
I have to generate increment negative numbers from -1 to -100 which is used a unique id for a request. Like it should be like this: -1, -2, -3, ...-100, -1, -2, and so on. How can I do this effectively? I am not supposed to use Boost. C++ STL is fine. I prefer to write simple function like int GetNextID() and it should generate ID. Request sample program on how to do this effectively?
Thanks for your time and help
int ID = -1;
auto getnext = [=] mutable {
if (ID == -100) ID = -1;
return ID--;
};
Fairly basic stuff here, really. If you have to ask somebody on the Interwebs to write this program for you, you should really consider finding some educational material in C++.
I love the functor solution:
template <int limit> class NegativeNumber
{
public:
NegativeNumber() : current(0) {};
int operator()()
{
return -(1 + (current++ % limit));
};
private:
int current;
};
Then, you can define any generator with any limit and use it:
NegativeNumber<5> five;
NegativeNumber<2> two;
for (int x = 0; x < 20; ++x)
std::cout << "limit five: " << five() << "\tlimit two: " << two() << '\n';
You can also pass the generator as parameter to another function, with each funtor with its own state:
void f5(NegativeNumber<5> &n)
{
std::cout << "limit five: " << n() << '\n';
}
void f2(NegativeNumber<2> &n)
{
std::cout << "limit two: " << n() << '\n';
}
f5(five);
f2(two);
If you don't like the template solution to declare the limit, there's also the no-template version:
class NegativeNumberNoTemplate
{
public:
NegativeNumberNoTemplate(int limit) : m_limit(limit), current(0) {};
int operator()()
{
return -(1 + (current++ % m_limit));
};
private:
const int m_limit;
int current;
};
Using as argument to a function works in the same way, and it's internal state is transfered as well:
void f(NegativeNumberNoTemplate &n)
{
std::cout << "no template: " << n() << '\n';
}
NegativeNumberNoTemplate notemplate(3);
f(notemplate);
I hope you don't want to use it with threading, they're not thread safe ;)
Here you have all the examples; hope it helps.
Something like.... (haven't compiled)
class myClass
{
int number = 0;
int GetValue ()
{
return - (number = ((number+1) % 101))
}
}
Even a simple problem like this could lead you to several approximations, both in the algorithmic solution and in the concrete usage of the programming language.
This was my first solution using C++03. I preferred to switch the sign after computing the value.
#include <iostream>
int GetNextID() {
// This variable is private to this function. Be careful of not calling it
// from multiple threads!
static int current_value = 0;
const int MAX_CYCLE_VALUE = 100;
return - (current_value++ % MAX_CYCLE_VALUE) - 1;
}
int main()
{
const int TOTAL_GETS = 500;
for (int i = 0; i < TOTAL_GETS; ++i)
std::cout << GetNextID() << std::endl;
}
A different solution taking into account that the integer modulo in C++ takes the sign of the dividend (!) as commented in the Wikipedia
#include <iostream>
int GetNextID() {
// This variable is private to this function. Be careful of not calling it
// from multiple threads!
static int current_value = 0;
const int MAX_CYCLE_VALUE = 10;
return (current_value-- % MAX_CYCLE_VALUE) - 1;
}
int main()
{
const int TOTAL_GETS = 50;
for (int i = 0; i < TOTAL_GETS; ++i)
std::cout << GetNextID() << std::endl;
}
I read that using a policy class for a function that will be called in a tight loop is much faster than using a polymorphic function. However, I setup this demo and the timing indicates that it is exactly the opposite!? The policy version takes between 2-3x longer than the polymorphic version.
#include <iostream>
#include <boost/timer.hpp>
// Policy version
template < typename operation_policy>
class DoOperationPolicy : public operation_policy
{
using operation_policy::Operation;
public:
void Run(const float a, const float b)
{
Operation(a,b);
}
};
class OperationPolicy_Add
{
protected:
float Operation(const float a, const float b)
{
return a + b;
}
};
// Polymorphic version
class DoOperation
{
public:
virtual float Run(const float a, const float b)= 0;
};
class OperationAdd : public DoOperation
{
public:
float Run(const float a, const float b)
{
return a + b;
}
};
int main()
{
boost::timer timer;
unsigned int numberOfIterations = 1e7;
DoOperationPolicy<OperationPolicy_Add> policy_operation;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
policy_operation.Run(1,2);
}
std::cout << timer.elapsed() << " seconds." << std::endl;
timer.restart();
DoOperation* polymorphic_operation = new OperationAdd;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
polymorphic_operation->Run(1,2);
}
std::cout << timer.elapsed() << " seconds." << std::endl;
}
Is there something wrong with the demo? Or is just incorrect that the policy should be faster?
Your benchmark is meaningless (sorry).
Making real benchmarks is hard, unfortunately, as compilers are very clever.
Things to look for here:
devirtualization: the polymorphic call is expected to be slower because it is supposed to be virtual, but here the compiler can realize than polymorphic_operation is necessarily a OperationAdd and thus directly call OperationAdd::Run without invoking runtime dispatch
inlining: since the compiler has access to the methods body, it can inline them, and avoid the function calls altogether.
"dead store removal": values that are not used need not be stored, and the computations that lead to them and do not provoke side-effects can be avoided entirely.
Indeed, your entire benchmark code can be optimized to:
int main()
{
boost::timer timer;
std::cout << timer.elapsed() << " seconds." << std::endl;
timer.restart();
DoOperation* polymorphic_operation = new OperationAdd;
std::cout << timer.elapsed() << " seconds." << std::endl;
}
Which is when you realize that you are not timing what you'd like to...
In order to make your benchmark meaningful you need to:
prevent devirtualization
force side-effects
To prevent devirtualization, just declare a DoOperation& Get() function, and then in another cpp file: DoOperation& Get() { static OperationAdd O; return O; }.
To force side-effects (only necessary if the methods are inlined): return the value and accumulate it, then display it.
In action using this program:
// test2.cpp
namespace so8746025 {
class DoOperation
{
public:
virtual float Run(const float a, const float b) = 0;
};
class OperationAdd : public DoOperation
{
public:
float Run(const float a, const float b)
{
return a + b;
}
};
class OperationAddOutOfLine: public DoOperation
{
public:
float Run(const float a, const float b);
};
float OperationAddOutOfLine::Run(const float a, const float b)
{
return a + b;
}
DoOperation& GetInline() {
static OperationAdd O;
return O;
}
DoOperation& GetOutOfLine() {
static OperationAddOutOfLine O;
return O;
}
} // namespace so8746025
// test.cpp
#include <iostream>
#include <boost/timer.hpp>
namespace so8746025 {
// Policy version
template < typename operation_policy>
struct DoOperationPolicy
{
float Run(const float a, const float b)
{
return operation_policy::Operation(a,b);
}
};
struct OperationPolicy_Add
{
static float Operation(const float a, const float b)
{
return a + b;
}
};
// Polymorphic version
class DoOperation
{
public:
virtual float Run(const float a, const float b) = 0;
};
class OperationAdd : public DoOperation
{
public:
float Run(const float a, const float b)
{
return a + b;
}
};
class OperationAddOutOfLine: public DoOperation
{
public:
float Run(const float a, const float b);
};
DoOperation& GetInline();
DoOperation& GetOutOfLine();
} // namespace so8746025
using namespace so8746025;
int main()
{
unsigned int numberOfIterations = 1e8;
DoOperationPolicy<OperationPolicy_Add> policy;
OperationAdd stackInline;
DoOperation& virtualInline = GetInline();
OperationAddOutOfLine stackOutOfLine;
DoOperation& virtualOutOfLine = GetOutOfLine();
boost::timer timer;
float result = 0;
for(unsigned int i = 0; i < numberOfIterations; ++i) {
result += policy.Run(1,2);
}
std::cout << "Policy: " << timer.elapsed() << " seconds (" << result << ")" << std::endl;
timer.restart();
result = 0;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
result += stackInline.Run(1,2);
}
std::cout << "Stack Inline: " << timer.elapsed() << " seconds (" << result << ")" << std::endl;
timer.restart();
result = 0;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
result += virtualInline.Run(1,2);
}
std::cout << "Virtual Inline: " << timer.elapsed() << " seconds (" << result << ")" << std::endl;
timer.restart();
result = 0;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
result += stackOutOfLine.Run(1,2);
}
std::cout << "Stack Out Of Line: " << timer.elapsed() << " seconds (" << result << ")" << std::endl;
timer.restart();
result = 0;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
result += virtualOutOfLine.Run(1,2);
}
std::cout << "Virtual Out Of Line: " << timer.elapsed() << " seconds (" << result << ")" << std::endl;
}
We get:
$ gcc --version
gcc (GCC) 4.3.2
$ ./testR
Policy: 0.17 seconds (6.71089e+07)
Stack Inline: 0.17 seconds (6.71089e+07)
Virtual Inline: 0.52 seconds (6.71089e+07)
Stack Out Of Line: 0.6 seconds (6.71089e+07)
Virtual Out Of Line: 0.59 seconds (6.71089e+07)
Note the subtle difference between devirtualization + inline and the absence of devirtualization.
FWIW I made it
a policy, as opposed to mixn
return the value
use a volatile to avoid optimizing away of the loop and unrelated optimization of the loop (like, reducing load/stores due to loop unrolling and vectorization on targets that support it).
compare with a direct, static function call
use way more iterations
compile with -O3 on gcc
Timings are:
DoDirect: 3.4 seconds.
Policy: 3.41 seconds.
Polymorphic: 3.4 seconds.
Ergo: there is no difference. Mainly because GCC is able to statically analyze the type of DoOperation* to be DoOperationAdd - there is vtable lookup inside the loop :)
IMPORTANT
If you wanted to benchmark reallife performance of this exact loop, instead of function invocation overhead, drop the volatile. The timings now become
DoDirect: 6.71089e+07 in 1.12 seconds.
Policy: 6.71089e+07 in 1.15 seconds.
Polymorphic: 6.71089e+07 in 3.38 seconds.
As you can see, without volatile, the compiler is able to optimize some load-store cycles away; I assume it might be doing loop unrolling+register allocation there (however I haven't inspected the machine code). The point is, that the loop as a whole can be optimized much more with the 'policy' approach than with the dynamic dispatch (i.e. the virtual method)
CODE
#include <iostream>
#include <boost/timer.hpp>
// Direct version
struct DoDirect {
static float Run(const float a, const float b) { return a + b; }
};
// Policy version
template <typename operation_policy>
struct DoOperationPolicy {
float Run(const float a, const float b) const {
return operation_policy::Operation(a,b);
}
};
struct OperationPolicy_Add {
static float Operation(const float a, const float b) {
return a + b;
}
};
// Polymorphic version
struct DoOperation {
virtual float Run(const float a, const float b) const = 0;
};
struct OperationAdd : public DoOperation {
float Run(const float a, const float b) const { return a + b; }
};
int main(int argc, const char *argv[])
{
boost::timer timer;
const unsigned long numberOfIterations = 1<<30ul;
volatile float result = 0;
for(unsigned long i = 0; i < numberOfIterations; ++i) {
result += DoDirect::Run(1,2);
}
std::cout << "DoDirect: " << result << " in " << timer.elapsed() << " seconds." << std::endl;
timer.restart();
DoOperationPolicy<OperationPolicy_Add> policy_operation;
for(unsigned long i = 0; i < numberOfIterations; ++i) {
result += policy_operation.Run(1,2);
}
std::cout << "Policy: " << result << " in " << timer.elapsed() << " seconds." << std::endl;
timer.restart();
result = 0;
DoOperation* polymorphic_operation = new OperationAdd;
for(unsigned long i = 0; i < numberOfIterations; ++i) {
result += polymorphic_operation->Run(1,2);
}
std::cout << "Polymorphic: " << result << " in " << timer.elapsed() << " seconds." << std::endl;
}
Turn on optimisation. The policy-based variant profits highly from that because most intermediate steps are completely optimised out, while the polymorphic version cannot skip for example the dereferencing of the object.
You have to turn on optimization, and make sure that
both code parts actually do the same thing (which they currently do not, your policy-variant does not return the result)
the result is used for something, so that the compiler does not discard the code path altogether (just sum the results and print them somewhere should be enough)
I had to change your policy code to return the computed value:
float Run(const float a, const float b)
{
return Operation(a,b);
}
Secondly, I had to store the returned value to ensure that the loop wouldn't be optimized away:
int main()
{
unsigned int numberOfIterations = 1e9;
float answer = 0.0;
boost::timer timer;
DoOperationPolicy<OperationPolicy_Add> policy_operation;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
answer += policy_operation.Run(1,2);
}
std::cout << "Policy got " << answer << " in " << timer.elapsed() << " seconds" << std::endl;
answer = 0.0;
timer.restart();
DoOperation* polymorphic_operation = new OperationAdd;
for(unsigned int i = 0; i < numberOfIterations; ++i)
{
answer += polymorphic_operation->Run(1,2);
}
std::cout << "Polymo got " << answer << " in " << timer.elapsed() << " seconds" << std::endl;
return 0;
}
Without optimizations on g++ 4.1.2:
Policy got 6.71089e+07 in 13.75 seconds
Polymo got 6.71089e+07 in 7.52 seconds
With -O3 on g++ 4.1.2:
Policy got 6.71089e+07 in 1.18 seconds
Polymo got 6.71089e+07 in 3.23 seconds
So the policy is definitely faster once optimizations are turned on.