Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 209 additions & 0 deletions applications/demos/nqueens/nqueens-smt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
////////////////////////////////////////////////////////////////////////
// This file is part of Grappa, a system for scaling irregular
// applications on commodity clusters.

// Copyright (C) 2010-2014 University of Washington and Battelle
// Memorial Institute. University of Washington authorizes use of this
// Grappa software.

// Grappa is free software: you can redistribute it and/or modify it
// under the terms of the Affero General Public License as published
// by Affero, Inc., either version 1 of the License, or (at your
// option) any later version.

// Grappa is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero General Public License for more details.

// You should have received a copy of the Affero General Public
// License along with this program. If not, you may obtain one from
// http://www.affero.org/oagpl.html.
////////////////////////////////////////////////////////////////////////

#include <Grappa.hpp>
#include <SmartPointer.hpp>


using namespace Grappa;
using namespace std;


DEFINE_int64( n, 8, "Board size" );

GRAPPA_DEFINE_METRIC( SimpleMetric<double>, nqueens_runtime, 0.0 );




/*
* Each core maintains the number of solutions it has found + the board size
*/
int64_t nqSolutions;
int nqBoardSize;
GlobalCompletionEvent gce; // tasks synchronization



/*
* The main board class.
*
* The board is represented as a column array specifying the row position of the
* Queen on each column.
*/
class Board {
public:
Board(size_t size)
{
this->size = size;
columns = new int[size];
}

~Board() { delete [] columns; }


/*
* Checks whether it is safe to put a queen in row 'row'.
*/
bool isSafe(int row)
{
/* we check if putting a queen on row 'row' will cause any of the previous
* queens (in 'columns') to capture it. */
for (auto i=0; i<size; i++) {
if (row == columns[i] || abs(size-i) == abs(row-columns[i]))
return false; // captured - not safe
}

return true;
}

size_t Size() const { return size; } // getter method

// TODO: bounds checking
void Set(size_t pos, int val)
{
columns[pos] = val;
}

int At(size_t pos) const
{
assert(pos < size);
return columns[pos];
}

// Inserts a queen in the last column at 'rowIndex'
void insertQueen(int rowIndex)
{
columns[size-1] = rowIndex;
}

private:
int *columns; /* has the row position for the queen on each column */
size_t size; /* board size */
};


template<>
Board *clone<Board>(GlobalAddress<Board> source)
{
/* read the remote board size */
int remsize = delegate::call(source, [](Board &b) { return b.Size(); });

/* create a new board with an extra column to place the next queen */
Board *local = new Board(remsize+1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preference would be to have clone actually make an exact duplicate, and then have a method that would let you add a column to the end of a Board. Seems cleaner.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, insertQueen could just resize the underlying array when needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah! I had that at some point, but the optimiser in me took over =P

I can restore that version tho.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seeing as this meant to be the maximally-readable version, I'd like to keep the semantics of clone as clean as possible.


/* copy the contents of the remote board to the local one */
for (auto k=0; k<remsize; k++)
local->Set(k, delegate::call(source, [k](Board &b) {
return b.At(k);
}));

return local;
}


/*
* This is basically a brute force solution using recursion.
*
* This procedure creates a new copy of the board, adds a new column
* (with a queen at the row given by 'rowIndex') and check whether
* it is safe to add a new queen in any of the rows of the next column.
* If it is safe, it then recursively spawn new tasks to do the job.
*/
void nqSearch(SmtPtr<Board> remoteBoard, int rowIndex)
{
/* create a new copy of remoteBoard, adding a new column */
SmtPtr<Board> newBoard = remoteBoard.clone();

/* place the queen at row 'rowIndex' in the new column */
newBoard->insertQueen(rowIndex);

/* are we done yet? */
if (newBoard->Size() == nqBoardSize)
nqSolutions++; // yes, solution found
else
{ /* not done yet */

/* check whether it is safe to have a queen on row 'i' */
for (int i=0; i<nqBoardSize; i++) {

if (newBoard->isSafe(i)) {

/* safe - spawn a new task to check for the next column */
spawn<unbound,&gce>([newBoard,i] { nqSearch(newBoard, i); });
}
}
}
}


int main(int argc, char * argv[]) {
init( &argc, &argv );

const int expectedSolutions[] =
{0, 1, 0, 0, 2, 10, 4, 40, 92, 352, 724, 2680, 14200, 73712, 365596, \
2279184, 14772512};

nqBoardSize = FLAGS_n;
nqSolutions = 0;


run([=]{

Metrics::reset_all_cores();
Metrics::start_tracing();

double start = walltime();

/* initial empty board */
SmtPtr<Board> board(new Board(0));

finish<&gce>([board]{
for (auto i=0; i<nqBoardSize; i++)
{
spawn<unbound, &gce>([board,i] { nqSearch(board,i); });
}
});


int64_t total = reduce<int64_t,collective_add<int64_t>>(&nqSolutions);

nqueens_runtime = walltime() - start;

Metrics::stop_tracing();

if (nqBoardSize <= 16) {
LOG(INFO) << "NQueens (" << nqBoardSize << ") = " << total << \
" (solution is " << (total == expectedSolutions[nqBoardSize] ? "correct)" : "wrong)");
}
else
LOG(INFO) << "NQueens (" << nqBoardSize << ") = " << total;

LOG(INFO) << "Elapsed time: " << nqueens_runtime.value() << " seconds";

Metrics::merge_and_dump_to_file();

});
finalize();
}

11 changes: 6 additions & 5 deletions applications/demos/nqueens/nqueens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,15 @@ class Board {
/*
* This is basically a brute force solution using recursion.
*
* Given a board ('remoteBoard') we check if placing a queen in any of the
* rows of the column given by 'columnIndex' is safe. In such cases a new task
* is spawned recursively for the next column.
* Given a board ('remoteBoard') we add a queen at the row given by 'rowIndex'
* in the last column, and check whether it is safe to add a new queen in any
* of the rows of the next column. In such cases a new task is spawned
* recursively for the next column.
*/
void nqSearch(GlobalAddress<Board> remoteBoard, int columnIndex)
void nqSearch(GlobalAddress<Board> remoteBoard, int rowIndex)
{
/* create a new copy of remoteBoard, adding a new column */
Board *newBoard = new Board(remoteBoard, columnIndex);
Board *newBoard = new Board(remoteBoard, rowIndex);

/* are we done yet? */
if (newBoard->Size() == nqBoardSize)
Expand Down
157 changes: 157 additions & 0 deletions system/SmartPointer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
////////////////////////////////////////////////////////////////////////
// This file is part of Grappa, a system for scaling irregular
// applications on commodity clusters.

// Copyright (C) 2010-2014 University of Washington and Battelle
// Memorial Institute. University of Washington authorizes use of this
// Grappa software.

// Grappa is free software: you can redistribute it and/or modify it
// under the terms of the Affero General Public License as published
// by Affero, Inc., either version 1 of the License, or (at your
// option) any later version.

// Grappa is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero General Public License for more details.

// You should have received a copy of the Affero General Public
// License along with this program. If not, you may obtain one from
// http://www.affero.org/oagpl.html.
////////////////////////////////////////////////////////////////////////


#ifndef _SMTPTR_HPP__
#define _SMTPTR_HPP__

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer using #pragma once rather than the old-style #ifndef _H_ stuff, though I know we have both in our codebase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Didn't know about that one.

One thing I forgot to mention: I am using some macros for debugging. I saw that Grappa is using a method based on LOG(x). How do I use it exactly (i.e., what do I use instead of 'x')?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comment below.


#include <Grappa.hpp>

using namespace Grappa;

#if 0
# include <iomanip>
using namespace std;
# define DPRINT(m) cout << setw(14) << (this) << ": " << m << endl
# define DPRINT2(m,n) cout << setw(14) << (this) << ": " << m << n << endl
#else
# define DPRINT(m)
# define DPRINT2(m,n)
#endif

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use Google's glog library for debug prints, I'd prefer if you used a variant of those rather than cout here. Keep the DPRINT macros here, I'd just suggesting DVLOG(2) << in place of cout. Then you don't have to do if 0 here, just define them once, and Glog will take care of compiling them away if DEBUG isn't defined. To see the output, then, you just need to compile with debug mode (./configure --mode=Debug) and run with --v=2 (if you choose to use verbose-level 2).


/*
* Reference manager class.
*
*
*/
template <class T>
class RefManager
{
T *ptr; /* pointer to the object */
size_t refcnt; /* reference counter */

public:

explicit RefManager(T *p = 0) : ptr(p), refcnt(1)
{
DPRINT("*** RefManager constructor ***");
}

~RefManager()
{
DPRINT("*** RefManager destructor ***");
}

T& getRef() { return *ptr; }
T* getPtr() { return ptr; }

void Increment()
{
refcnt++;
DPRINT2(" -> RefManager incremented: ", refcnt);
}

size_t Decrement()
{
refcnt--;
DPRINT2(" -> RefManager decremented: ", refcnt);
if (refcnt == 0) {
DPRINT(" -> RefManager: Object deleted");
delete ptr;
}
return refcnt;
}

size_t numRefs() const { return refcnt; }
};


/* forward declaration for the clone function */
template <typename T>
T *clone(GlobalAddress<T> obj);




//
// Smart Pointer class
//
//
template <class T>
class SmtPtr
{
GlobalAddress< RefManager<T> > refMan; /* Global address of the reference manager */

public:
explicit SmtPtr(T *ptr): refMan(make_global(new RefManager<T>(ptr)))
{
DPRINT("*** SmtPtr constructor ***");
}

// copy constructor
SmtPtr(const SmtPtr &other): refMan(other.refMan)
{
DPRINT("*** SmtPtr copy constructor ***");

delegate::call(refMan, [](RefManager<T> *ref) { ref->Increment(); });
}

// T& operator*() { return reference_->getRef(); }

T* operator->()
{
// TODO: assert it is not called from remote node
return refMan->getPtr();
}


SmtPtr<T> clone() const
{
DPRINT(" -> SmtPtr clone");

T *local = ::clone(delegate::call(refMan, [](RefManager<T> *ref)
{ return make_global(ref->getPtr()); }));

return SmtPtr<T>(local);
}

size_t getNumRefs()
{
return delegate::call(refMan, [](RefManager<T> *ref) { return ref->numRefs(); });
}

// destructor
~SmtPtr()
{
DPRINT("*** SmtPtr destructor ***");

RefManager<T> *ref = (RefManager<T> *)refMan.pointer();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's going on here? why did you need this cast?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read below.. it shouldn't be there =P

delegate::call(refMan.core(), [ref] { if (!ref->Decrement()) delete ref; });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's actually a bad idea to call pointer on a GlobalAddress when not on the correct core. For 2D addresses this isn't a problem, but for Linear it'll give you the wrong pointer, since it'll use this core's offset. For this reason, we typically try to avoid getting the pointer and then capturing it as you're doing here.

Why not just do:

delegate::call(refMan, [](RefManager<T>& rm){ if (!rm->Decrement()) delete ref; });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a leftover code from an early version I forgot to change. As you can see, the getNumRefs() method above is doing exactly what you suggested.

}

};




#endif /* _SMTPTR_HPP_ */
Loading