Hello again. I'm still working on a tool for solving game theory solutions. I'm not yet happy with the performance though and since I'm kinda inexperienced, I believe there's still alot I could improve.
So basically what my program does, is
a) computing expected payouts for strategy A vs B
b) setting As strategy to maximally exploit Bs stategy
c) do the same thing for b vs a
d) repeat
I have a vector<vector<floats>> of the size 1326*(1326/2), which save the expected win% of any hand vs any hand. The code for getting that % is:
/* equities is the vector<vector<float>> */
inline equity getEquity(startingHand hero, startingHand villain) const {
if (hero > villain)
return 1.0f - equities[villain][hero-villain];
else
return equities[hero][villain-hero];
}
My question is: is the vector slowing my program down here, compared to using a simple array? I read somewhere, that the [] operator of the vector is defined as inline, and when it's called too often (fe. inside a loop), the compiler doesn't inline it and thus the access is slowed down. Is that right? I'm calling that function from inside a 0..1326 loop.
While talking about inlines. Does it make sense to use DEFINE macros somewhere to get a speedboost?
Ok, next question. I had the following piece of code:
// kNumHands = 1326
else { // Showdown
EV pot = currDecPt->getPlayerCIP(hero) + currDecPt->getPlayerCIP(villain);
EV remStack = tree->getEffStack() - currDecPt->getPlayerCIP(hero);
for (int hand = 0; hand < kNumHands; hand++) {
// getMostRecentRangeOf would traverse the tree upwards and find the most recent Range of villain.
// it returned a copy of that range though (thus copying and array of 1326floats)
EVs[hero][iDecPt][hand] = pot*(1.0f - getMostRecentRangeOf(villain, iDecPt).getEquityVsHand(hand, currDecPt->eArray)) + remStack;
if (finish)
CFs[hero][iDecPt][hand] = (EVs[hero][iDecPt][hand] - remStack) / pot;
}
}
I changed the code to:
else { // Showdown
const Range *r = getMostRecentRangeOf(villain, iDecPt);
EV pot = currDecPt->getPlayerCIP(hero) + currDecPt->getPlayerCIP(villain);
EV remStack = tree->getEffStack() - currDecPt->getPlayerCIP(hero);
for (int hand = 0; hand < kNumHands; hand++) {
EVs[hero][iDecPt][hand] = pot*(1.0f - r->getEquityVsHand(hand, currDecPt->eArray)) + remStack;
if (finish)
CFs[hero][iDecPt][hand] = (EVs[hero][iDecPt][hand] - remStack) / pot;
}
}
Cause I took the getMostRecentRangeOf-function out of the loop and didn't return a copy but a ptr, I expected a huge speed boost. But when running with printing the std::clock() difference, it showed no speed boost at all. Shouldn't pointers be alot quicker?
Next stop, "getEquityVsHand":
/**
* Returns the Equity of heroHand vs. villainRange on board - using an EquityArray.
*/
float Range::getEquityVsHand(startingHand villain, const EquityArray *ea) const {
equity eq = 0.0f; frequency count = 0.0f;
/* copy Villains Range */
// handRange is an Array of 1326floats
handRange temp_freqs;
std::copy(std::begin(frequencies), std::end(frequencies), std::begin(temp_freqs));
// getCardFromHand - converts the starting hand, represented as an int to two ints, representing two single cards
card deadcards[7] = { getCardFromHand(villain, 0), getCardFromHand(villain, 1), ea->board[0], ea->board[1], ea->board[2], ea->board[3], ea->board[4] };
// setHandsWithConflicts iterates over the handRange and deadcards.
// If they are the same, the frequency of the hand is set to 0.
setHandsWithConflicts(temp_freqs, deadcards, 7, 0.0f);
for (int i = 0; i < kNumHands; i++) {
if (temp_freqs[i] == 0.0f)
continue;
count += temp_freqs[i];
eq += ea->getEquity(i, villain) * temp_freqs[i];
}
return eq / count;
}
So this is a little complicated. I thought copying the handRange-array would take alot of performance. So I got rid of all the copying stuff and got the following:
float Range::getEquityVsHand(startingHand villainHand, const EquityArray *ea) const {
equity eq = 0.0f; frequency count = 0.0f;
for (int i = 0; i < kNumHands; i++) {
if (frequencies[i] == 0.0f
|| handConflictsBoard(ea->board, hand) || handConflictsHand(hand, villainHand))
continue;
count += frequencies[i];
eq += ea->getEquity(i, villain) * frequencies[i];
}
return eq / count;
}
So, what handConflictsBoard and handConflictsHand do, is iterate over the given hands and look up if they conflict in a precomputed std::vector<std::vector<bool>>. So basically the same thing that happened in "setHandsWithConflicts", I just don't have to copy the frequencies first. Now again, I expected a nice boost of performance, but instead, the performance dropped drastically and slowed down by 300%.
Since I don't wanne annoy you guys with anymore code, that's it for now. Enlighten me please, guys ;)
Thanks in advance.
Edit: Oh, I almost forgot. As I said above, I'm computing the best strategy for A, then the best response for B, again for A and so on and so forth. Shouldn't it be possible two have two threads running parallel, one starts to compute A vs B, the other one B vs A. Each one obv has to wait for the other one to finish, before continuing. Ofcourse one player would then be "one step behind", but since the solution is converging against an equilibrium, it should work anyway, right?
Shouldn't that give an immense speed boost?