2
\$\begingroup\$

I was thinking to rewrite my Python code to C++ since it contains quite a lot of simple loops. I was stunned to find out that this critical part of code (the code below) is about 5 times slower than the Python code, and I'm hoping you could help me figure out why.

The context is some kind of game similar to Tetris, where I'm recursing** through the board and visiting each tile (and then its neighbours if not yet visited) during a traverse.

C++

void Board::traverse (Tile *t, Neighbours neighbours)
 {
 t->visited = true;
 COO coo = make_pair(t->i, t->j);
 Buddies buddies = neighbours[coo];
 if (t->color != '0' && t->color != '.')
 {
 for (uint i = 0; i < buddies.size(); i++)
 {
 Tile *buddy = &this->field[buddies[i]];
 if (buddy->visited && buddy->color == t->color)
 {
 if (buddy->chainId == -1)
 {
 buddy->chainId = this->chains.size();
 this->chains[buddy->chainId].push_back(*buddy);
 }
 t->chainId = buddy->chainId;
 this->chains[t->chainId].push_back(*t);
 break;
 }
 }
 if (t->chainId == -1)
 {
 t->chainId = this->chains.size();
 this->chains[t->chainId].push_back(*t);
 }
 }
 for (uint i = 0; i < buddies.size(); i++)
 {
 if (!this->field[buddies[i]].visited)
 {
 this->traverse(&this->field[buddies[i]], neighbours);
 }
 }
 }

Python

class Board():
 def __init__(self, s):
 self.field = []
 self.chains = {}
 self.heights = None
 self.score = 0
 self.step = 1
 def traverse(self, t=None):
 t.visited = True
 buddies = neighbours[(t.i, t.j)]
 if t.color != '0' and t.color != '.':
 for i, j in buddies:
 if self.field[i][j].visited and self.field[i][j].color == t.color:
 if self.field[i][j].chain_id is None:
 self.field[i][j].chain_id = len(self.chains)
 self.chains[self.field[i][j].chain_id] = [self.field[i][j]]
 t.chain_id = self.field[i][j].chain_id
 self.chains[t.chain_id].append(t)
 break
 if t.chain_id is None:
 t.chain_id = len(self.chains)
 self.chains[t.chain_id] = [t]
 for i, j in buddies:
 if not self.field[i][j].visited:
 self.traverse(self.field[i][j])

** The reason for this recursing is that we are not looking for simple line clear but a "line" could be seen as 4 or more blocks adjacent; making recursion through connected nodes more logical. A chain in this context is a group of connected nodes of the same color.

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked May 6, 2016 at 16:46
\$\endgroup\$
1
  • \$\begingroup\$ It's hard to tell you don't provide the type information on a lot of objects. But one thing that is majorly different between the languages is names. In pythong a name is a reference to an object. In C++ objects are named and distinct (thus when pushing them you are making copies (which can be expensive)). In these cases you should be passing pointers or references depending on the situation. Can you post the class definition of Board COO Budies and anything else relevant. \$\endgroup\$ Commented May 6, 2016 at 19:41

1 Answer 1

4
\$\begingroup\$

First Pass:

void Board::traverse (Tile *t, Neighbours neighbours)
 ^^^^ Pointers are bad idea.
 They make resource management hard to 
 reason about as pointers have no
 ownership semantics. Prefer a reference
 if it can not be nullptr.
void Board::traverse (Tile *t, Neighbours neighbours)
 ^^^^^^^^^^^^^^^^^^^^^ 
 Here you are passing neighbours by value.
 This means you are making a copy of the
 object. Prefer const reference.
 // What is the type of COO
 // is there a better way to create it?
 COO coo = make_pair(t->i, t->j);
 // Here you are copy the content of `neighbours[coo]`
 // into buddies. Do you need a copy or can we just
 // use a reference to the original value.
 Buddies buddies = neighbours[coo];
 // Prefer a reference to a pointer.
 Tile *buddy = &this->field[buddies[i]];
 // Here you are making a copy of buddy.
 // Do you really need another copy?
 this->chains[buddy->chainId].push_back(*buddy);
 // Again you are copy the value of tile into chains.
 this->chains[t->chainId].push_back(*t);
 // Another copy of tiles is being put here.
 this->chains[t->chainId].push_back(*t);
 // And you are copying neighbours into the
 // recursive call here.
 this->traverse(&this->field[buddies[i]], neighbours);

Use of this

Your use of this is not idiomatic.

this->chains[t->chainId].push_back(*t);

Its much easier to write:

chains[t->chainId].push_back(*t);

The only reason to use this is to disambiguate the use of shadowed variables. But shadowed variables is an error waiting to happen. So it is best to avoid shadowed variables entirely (even turn on your errors to make sure they don't happen)

-Wall -Wextra -Wshadow -Werror
answered May 6, 2016 at 19:52
\$\endgroup\$
2
  • \$\begingroup\$ If I'm not mistaken, -Wshadow isn't enabled by either of -Wall and -Wextra. It needs to be specified separately. \$\endgroup\$ Commented May 6, 2016 at 20:30
  • \$\begingroup\$ This has been a great answer! I've learned a lot from it, thank you. The speed improvement (with also -O2) was quite huge. \$\endgroup\$ Commented May 6, 2016 at 22:16

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.