Skip to content

Commit fce4766

Browse files
authored
Correct performance regression in StrategySupportProfile (#742)
The reimplementation of StrategySupportProfile which was focused on improving contingency iteration had substantial negative performance impact on code which relied on strategy iteration directly on the support. This restores the implementation of the support profile as a sub-index of the full support and reverses the negative performance impact. The internal changes which support improved contingency iteration have been retained.
1 parent 570a5cf commit fce4766

3 files changed

Lines changed: 62 additions & 96 deletions

File tree

src/games/gametable.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,17 @@ template <class T> class TableMixedStrategyProfileRep : public MixedStrategyProf
115115
T &value) const;
116116
//@}
117117

118+
const CartesianProductSpace &m_pureStrategies;
119+
118120
long StrategyOffset(const GameStrategy &s) const
119121
{
120-
const auto &space = this->GetSupport().GetGame()->m_pureStrategies;
121-
const auto &player = s->GetPlayer();
122-
const size_t i = player->GetNumber() - 1;
123-
return (s->GetNumber() - 1) * space.m_strides[i];
122+
return (s->GetNumber() - 1) * m_pureStrategies.m_strides[s->m_player->GetNumber() - 1];
124123
}
125124

126125
public:
127126
explicit TableMixedStrategyProfileRep(const StrategySupportProfile &p_support)
128-
: MixedStrategyProfileRep<T>(p_support)
127+
: MixedStrategyProfileRep<T>(p_support),
128+
m_pureStrategies(p_support.GetGame()->m_pureStrategies)
129129
{
130130
}
131131
~TableMixedStrategyProfileRep() override = default;

src/games/stratspt.cc

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,26 @@ namespace Gambit {
3535
StrategySupportProfile::StrategySupportProfile(const Game &p_game) : m_game(p_game)
3636
{
3737
m_game->BuildComputedValues();
38-
m_strategies.m_space = &p_game->m_pureStrategies;
39-
m_strategies.m_allowedDigits.resize(p_game->NumPlayers());
38+
for (const auto &player : m_game->GetPlayers()) {
39+
for (const auto &strategy : player->GetStrategies()) {
40+
m_support[player].push_back(strategy);
41+
}
42+
}
43+
44+
m_strategyDigits.m_space = &p_game->m_pureStrategies;
45+
m_strategyDigits.m_allowedDigits.resize(p_game->NumPlayers());
4046

4147
for (size_t i = 0; i < p_game->NumPlayers(); ++i) {
4248
const int radix = p_game->m_pureStrategies.m_radices[i];
43-
auto &digits = m_strategies.m_allowedDigits[i];
49+
auto &digits = m_strategyDigits.m_allowedDigits[i];
4450
digits.resize(radix);
4551
std::iota(digits.begin(), digits.end(), 0);
4652
}
4753
}
4854

4955
int StrategySupportProfile::MixedProfileLength() const
5056
{
51-
return sum_function(m_strategies.m_allowedDigits, [](const std::vector<int> &digits) {
57+
return sum_function(m_strategyDigits.m_allowedDigits, [](const std::vector<int> &digits) {
5258
return static_cast<int>(digits.size());
5359
});
5460
}
@@ -68,8 +74,8 @@ bool StrategySupportProfile::IsSubsetOf(const StrategySupportProfile &p_other) c
6874
if (m_game != p_other.m_game) {
6975
return false;
7076
}
71-
const auto &A = m_strategies.m_allowedDigits;
72-
const auto &B = p_other.m_strategies.m_allowedDigits;
77+
const auto &A = m_strategyDigits.m_allowedDigits;
78+
const auto &B = p_other.m_strategyDigits.m_allowedDigits;
7379
const size_t n = A.size();
7480

7581
for (size_t i = 0; i < n; ++i) {
@@ -112,12 +118,22 @@ void StrategySupportProfile::AddStrategy(const GameStrategy &p_strategy)
112118
if (p_strategy->GetGame() != m_game) {
113119
throw MismatchException();
114120
}
121+
122+
auto &support = m_support[p_strategy->GetPlayer()];
123+
auto pos = std::find_if(support.begin(), support.end(), [p_strategy](const GameStrategy &s) {
124+
return s->GetNumber() >= p_strategy->GetNumber();
125+
});
126+
if (pos == support.end() || *pos != p_strategy) {
127+
// Strategy is not in the support for the player; add at this location to keep sorted by number
128+
support.insert(pos, p_strategy);
129+
}
130+
115131
const size_t index = p_strategy->GetPlayer()->GetNumber() - 1;
116132
const int digit = p_strategy->GetNumber() - 1;
117-
auto &digits = m_strategies.m_allowedDigits[index];
118-
auto pos = std::lower_bound(digits.begin(), digits.end(), digit);
119-
if (pos == digits.end() || *pos != digit) {
120-
digits.insert(pos, digit);
133+
auto &digits = m_strategyDigits.m_allowedDigits[index];
134+
auto ipos = std::lower_bound(digits.begin(), digits.end(), digit);
135+
if (ipos == digits.end() || *ipos != digit) {
136+
digits.insert(ipos, digit);
121137
}
122138
}
123139

@@ -126,15 +142,21 @@ bool StrategySupportProfile::RemoveStrategy(const GameStrategy &p_strategy)
126142
if (p_strategy->GetGame() != m_game) {
127143
throw MismatchException();
128144
}
129-
const size_t index = p_strategy->GetPlayer()->GetNumber() - 1;
130-
const int digit = p_strategy->GetNumber() - 1;
131-
auto &digits = m_strategies.m_allowedDigits[index];
132-
if (digits.size() == 1) {
145+
auto &support = m_support[p_strategy->GetPlayer()];
146+
if (support.size() == 1) {
133147
return false;
134148
}
135-
auto pos = std::lower_bound(digits.begin(), digits.end(), digit);
136-
if (pos != digits.end() && *pos == digit) {
137-
digits.erase(pos);
149+
auto pos = std::find(support.begin(), support.end(), p_strategy);
150+
if (pos != support.end()) {
151+
support.erase(pos);
152+
}
153+
154+
const size_t index = p_strategy->GetPlayer()->GetNumber() - 1;
155+
const int digit = p_strategy->GetNumber() - 1;
156+
auto &digits = m_strategyDigits.m_allowedDigits[index];
157+
auto ipos = std::lower_bound(digits.begin(), digits.end(), digit);
158+
if (ipos != digits.end() && *ipos == digit) {
159+
digits.erase(ipos);
138160
return true;
139161
}
140162
return false;

src/games/stratspt.h

Lines changed: 18 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -40,88 +40,32 @@ namespace Gambit {
4040
/// in which they appear in the underlying game.
4141
class StrategySupportProfile {
4242
Game m_game;
43-
CartesianSubset m_strategies;
43+
std::map<GamePlayer, std::vector<GameStrategy>> m_support;
44+
CartesianSubset m_strategyDigits;
4445

4546
public:
4647
class Support {
4748
const StrategySupportProfile *m_profile;
48-
size_t m_playerIndex;
49+
GamePlayer m_player;
4950

5051
public:
51-
class const_iterator {
52-
const StrategySupportProfile *m_profile{nullptr};
53-
size_t m_playerIndex{0};
54-
std::vector<int>::const_iterator m_it;
55-
56-
public:
57-
using value_type = GameStrategy;
58-
using reference = GameStrategy;
59-
using pointer = void;
60-
using difference_type = std::ptrdiff_t;
61-
using iterator_category = std::forward_iterator_tag;
62-
63-
const_iterator() = default;
64-
const_iterator(const StrategySupportProfile *profile, const size_t playerIndex,
65-
const std::vector<int>::const_iterator it)
66-
: m_profile(profile), m_playerIndex(playerIndex), m_it(it)
67-
{
68-
}
69-
70-
GameStrategy operator*() const
71-
{
72-
const auto &player = m_profile->m_game->GetPlayer(m_playerIndex + 1);
73-
return player->GetStrategy(*m_it + 1);
74-
}
75-
76-
const_iterator &operator++()
77-
{
78-
++m_it;
79-
return *this;
80-
}
81-
82-
bool operator==(const const_iterator &other) const { return m_it == other.m_it; }
83-
84-
bool operator!=(const const_iterator &other) const { return !(*this == other); }
85-
};
86-
87-
Support() : m_profile(nullptr), m_playerIndex(0) {}
88-
89-
Support(const StrategySupportProfile *profile, GamePlayer player)
90-
: m_profile(profile), m_playerIndex(player->GetNumber() - 1)
91-
{
92-
}
93-
94-
size_t size() const { return m_profile->m_strategies.m_allowedDigits[m_playerIndex].size(); }
95-
96-
GameStrategy operator[](const size_t index) const
97-
{
98-
const int digit = m_profile->m_strategies.m_allowedDigits[m_playerIndex][index];
99-
return m_profile->m_game->GetPlayer(m_playerIndex + 1)->GetStrategy(digit + 1);
100-
}
52+
using const_iterator = std::vector<GameStrategy>::const_iterator;
10153

102-
GameStrategy front() const
54+
Support() : m_profile(nullptr), m_player(nullptr) {}
55+
Support(const StrategySupportProfile *profile, const GamePlayer &player)
56+
: m_profile(profile), m_player(player)
10357
{
104-
const int digit = m_profile->m_strategies.m_allowedDigits[m_playerIndex].front();
105-
return m_profile->m_game->GetPlayer(m_playerIndex + 1)->GetStrategy(digit + 1);
10658
}
10759

108-
GameStrategy back() const
109-
{
110-
const int digit = m_profile->m_strategies.m_allowedDigits[m_playerIndex].back();
111-
return m_profile->m_game->GetPlayer(m_playerIndex + 1)->GetStrategy(digit + 1);
112-
}
113-
114-
const_iterator begin() const
115-
{
116-
const auto &digits = m_profile->m_strategies.m_allowedDigits[m_playerIndex];
117-
return {m_profile, m_playerIndex, digits.begin()};
118-
}
119-
120-
const_iterator end() const
60+
size_t size() const { return m_profile->m_support.at(m_player).size(); }
61+
GameStrategy operator[](const size_t index) const
12162
{
122-
const auto &digits = m_profile->m_strategies.m_allowedDigits[m_playerIndex];
123-
return {m_profile, m_playerIndex, digits.end()};
63+
return m_profile->m_support.at(m_player)[index];
12464
}
65+
GameStrategy front() const { return m_profile->m_support.at(m_player).front(); }
66+
GameStrategy back() const { return m_profile->m_support.at(m_player).back(); }
67+
const_iterator begin() const { return m_profile->m_support.at(m_player).begin(); }
68+
const_iterator end() const { return m_profile->m_support.at(m_player).end(); }
12569
};
12670

12771
/// @name Lifecycle
@@ -136,13 +80,13 @@ class StrategySupportProfile {
13680
bool operator==(const StrategySupportProfile &p_support) const
13781
{
13882
return m_game == p_support.m_game &&
139-
m_strategies.m_allowedDigits == p_support.m_strategies.m_allowedDigits;
83+
m_strategyDigits.m_allowedDigits == p_support.m_strategyDigits.m_allowedDigits;
14084
}
14185
/// Test for the inequality of two supports
14286
bool operator!=(const StrategySupportProfile &p_support) const
14387
{
14488
return m_game != p_support.m_game ||
145-
m_strategies.m_allowedDigits != p_support.m_strategies.m_allowedDigits;
89+
m_strategyDigits.m_allowedDigits != p_support.m_strategyDigits.m_allowedDigits;
14690
}
14791
//@}
14892

@@ -166,7 +110,7 @@ class StrategySupportProfile {
166110
/// Returns true exactly when the strategy is in the support.
167111
bool Contains(const GameStrategy &s) const
168112
{
169-
const auto &digits = m_strategies.m_allowedDigits[s->GetPlayer()->GetNumber() - 1];
113+
const auto &digits = m_strategyDigits.m_allowedDigits[s->GetPlayer()->GetNumber() - 1];
170114
const int digit = s->GetNumber() - 1;
171115
return std::binary_search(digits.begin(), digits.end(), digit);
172116
}
@@ -203,7 +147,7 @@ class StrategySupportProfile {
203147
const size_t player_index = player->GetNumber() - 1;
204148
const int digit = p_strategy->GetNumber() - 1;
205149
StrategySupportProfile restricted(*this);
206-
restricted.m_strategies.m_allowedDigits[player_index].assign(1, digit);
150+
restricted.m_strategyDigits.m_allowedDigits[player_index].assign(1, digit);
207151
return restricted;
208152
}
209153
//@}

0 commit comments

Comments
 (0)