1. Added random board generator for development mode based on number of tiles. Now every run produces a new board and hence makes a new test case :) 2. WaitHandle.WaitAll could take only 64 handles at max. Modified threaded execution to use the next handle available. Threaded implementation is pretty bad at the moment and takes more time than unthreaded execution when application is run multiple times. Did performance analysis and it revealed bad object memory management and high number of contentions. Will fix it sometime.
Dev Ghai

Dev Ghai commited on 2014-01-05 09:37:05
Showing 2 changed files, with 74 additions and 15 deletions.

... ...
@@ -1,9 +1,12 @@
1 1
 #define DEVELOPING
2
+#define DO_THREADED
3
+#define DO_UNTHREADED
2 4
 
3 5
 using System;
4 6
 using System.Collections.Generic;
5 7
 using System.IO;
6 8
 using System.Diagnostics;
9
+using System.Text;
7 10
 
8 11
 namespace Boggle
9 12
 {
... ...
@@ -28,7 +31,7 @@ namespace Boggle
28 31
             string input;
29 32
             bool isError = false;
30 33
 #endif
31
-            int boardSideLength = 3;
34
+            int boardSideLength = 10;
32 35
             int minWordLength = 4;
33 36
 
34 37
             char wordListInput = 'Z';
... ...
@@ -91,12 +94,22 @@ namespace Boggle
91 94
                 }
92 95
             } while (isError);
93 96
 #endif
97
+            Console.WriteLine("Board Side Length (in Tiles): {0}, Minimum word length (in alphabets): {1}, Word list: {2}", boardSideLength, minWordLength, listToUseForLookup);            
94 98
             st.Start();
95 99
             bl.LoadList(listToUseForLookup, boardSideLength, minWordLength, Directory.GetCurrentDirectory());
96 100
             st.Stop();
97
-            Console.WriteLine("Loaded list in {0} ms.", st.ElapsedMilliseconds);
101
+            Console.WriteLine("Loaded list (and serialized) in {0} ms.", st.ElapsedMilliseconds);
102
+
103
+            //Generate a default random string for each run.
104
+            string boardString = string.Empty;
105
+            StringBuilder randomDefaultString = new StringBuilder(boardSideLength * boardSideLength);
106
+            Random rand = new Random(DateTime.Now.Millisecond);
107
+            for (int i = 0; i < boardSideLength * boardSideLength; i++)
108
+            {
109
+                randomDefaultString.Append((char)rand.Next('A', 'Z'));
110
+            }
111
+            boardString = randomDefaultString.ToString();
98 112
 
99
-            string boardString = "qwertyuio";
100 113
 #if !DEVELOPING
101 114
             do
102 115
             {
... ...
@@ -116,20 +129,23 @@ namespace Boggle
116 129
             board.Print();
117 130
             BoggleSolver solver = new BoggleSolver(board, bl, minWordLength);
118 131
             st.Reset();
132
+#if DO_UNTHREADED
119 133
             st.Start();
120 134
             HashSet<WordOnBoard> wordsUnThreaded = solver.GetWordsOnBoard(false);
121 135
             st.Stop();
122 136
             PrintWords(wordsUnThreaded);
123 137
 
124 138
             Console.WriteLine("Got solution in {0} ms. Number of words (UNTHREADED): {1}\n\n", st.ElapsedMilliseconds, wordsUnThreaded.Count);
139
+#endif
125 140
             st.Reset();
141
+#if DO_THREADED
126 142
             st.Start();
127 143
             HashSet<WordOnBoard> wordsThreaded = solver.GetWordsOnBoard(true);
128 144
             st.Stop();
129
-            st.Reset();
130 145
             PrintWords(wordsThreaded);
131 146
             Console.WriteLine("\nGot solution in {0} ms. Number of words (THREADED): {1}.", st.ElapsedMilliseconds, wordsThreaded.Count);
132
-
147
+#endif
148
+#if DO_THREADED && DO_UNTHREADED
133 149
             Console.WriteLine("Time for sanity checks... comparing solutions from threaded and non-threaded mode.");
134 150
             if (wordsThreaded.Count == wordsUnThreaded.Count)
135 151
             {
... ...
@@ -147,7 +163,7 @@ namespace Boggle
147 163
                 Console.WriteLine("Words in threaded collection that are not in unthreaded collection:");
148 164
                 PrintWords(wordsThreaded);
149 165
             }
150
-
166
+#endif
151 167
             Console.WriteLine("\nPress any key to exit.");
152 168
             Console.ReadKey();
153 169
         }
... ...
@@ -12,6 +12,13 @@ namespace Boggle
12 12
         BoggleBoard _Board;
13 13
         int _MinWordLength;
14 14
         HashSet<WordOnBoard> _wordsOnBoard;
15
+        //WaitHandle.WaitAll can wait at most for 64 handles.
16
+        int _MaxParallelSolvers;
17
+        ManualResetEvent[] doneEvents;
18
+        Queue<int> openSlots;
19
+        int[] correspondingSlots;
20
+
21
+        static int doneEventCount;
15 22
 
16 23
         /// <summary>
17 24
         /// Constructor.
... ...
@@ -26,6 +33,13 @@ namespace Boggle
26 33
             _MinWordLength = minWordLength;
27 34
             _wordEqualityComparer = new WordComparer();
28 35
             _wordsOnBoard = new HashSet<WordOnBoard>(_wordEqualityComparer);
36
+            _MaxParallelSolvers = _Board.Tiles.Length > 64 ? 64 : _Board.Tiles.Length;
37
+            doneEvents = new ManualResetEvent[_MaxParallelSolvers];
38
+            openSlots = new Queue<int>();
39
+            correspondingSlots = new int[_Board.Tiles.Length];
40
+
41
+            for (int i = 0; i < _MaxParallelSolvers; i++)
42
+                openSlots.Enqueue(i);
29 43
         }
30 44
 
31 45
         /// <summary>
... ...
@@ -40,25 +54,52 @@ namespace Boggle
40 54
             return GetWordsOnBoardThreaded();
41 55
         }
42 56
 
43
-        private ManualResetEvent[] doneEvents;
57
+        
44 58
 
45 59
         /// <summary>
46 60
         /// Entry point for getting all possible words on board in threaded manner.
47 61
         /// </summary>
62
+        /// <remarks>
63
+        /// There can be two approaches: 
64
+        /// 1. Start scanning from each tile on the board and add new tile if any of
65
+        /// it is left over.
66
+        /// 2. Divide the number of tiles on the board by max number of threads that the
67
+        /// system can support and wait for each chunk to complete. This is less efficient 
68
+        /// than the first approach because there can be chunks that exit earlier compared
69
+        /// to most time consuming chunk.
70
+        /// 
71
+        /// Hence this function implements the first approach.
72
+        /// </remarks>
48 73
         /// <returns>Hashset of all words that can appear on the board.</returns>
49 74
         private HashSet<WordOnBoard> GetWordsOnBoardThreaded()
50 75
         {
51
-            doneEvents = new ManualResetEvent[_Board.SideLength * _Board.SideLength];
52
-            for (int i = 0; i < _Board.SideLength; i++)
76
+            int i = 0, j = 0;
77
+            int tileIndex = 0;
78
+            int nextSlot;
79
+            while (tileIndex < _Board.Tiles.Length)
53 80
             {
54
-                for (int j = 0; j < _Board.SideLength; j++)
81
+                while (openSlots.Count > 0 && tileIndex < _Board.Tiles.Length)
55 82
                 {
56
-                    doneEvents[i * _Board.SideLength + j] = new ManualResetEvent(false);
57
-
83
+                    //Resume from the previous value
84
+                    i = tileIndex / _Board.SideLength;
85
+                    j = tileIndex % _Board.SideLength;
86
+                    nextSlot = openSlots.Dequeue();
87
+                    if (doneEvents[nextSlot] != null)
88
+                        doneEvents[nextSlot].Reset();
89
+                    else
90
+                        doneEvents[nextSlot] = new ManualResetEvent(false);
58 91
                     ThreadPool.QueueUserWorkItem(CollectWordsFromPivotThreaded, _Board[i, j]);
92
+                    correspondingSlots[tileIndex] = nextSlot;
93
+                    tileIndex++;
59 94
                 }
95
+
96
+                //If all the slots have filled up, then wait for atleast one to get free before
97
+                //queueing another thread.
98
+                WaitHandle.WaitAny(doneEvents);
60 99
             }
100
+
61 101
             WaitHandle.WaitAll(doneEvents);
102
+            
62 103
             return _wordsOnBoard;
63 104
         }
64 105
 
... ...
@@ -68,14 +109,16 @@ namespace Boggle
68 109
             //Copy the board
69 110
             BoggleBoard boardForThisThread = new BoggleBoard(this._Board);
70 111
             //Initialize a list that will hold the tiles that will make up the word. Its max size will be square of side.
71
-            List<Tile> newWordList = new List<Tile>(boardForThisThread.SideLength * boardForThisThread.SideLength);
112
+            List<Tile> newWordList = new List<Tile>(_Board.Tiles.Length);
72 113
             //Figure out the tile in the new board
73 114
             Tile t = boardForThisThread[passedTile.X, passedTile.Y];
74 115
             //Initialize this list with the tile that we will start finding words from.
75 116
             newWordList.Add(t);
76 117
             //Collect all the possible words from this tile. End result should be in the Hashset.
77 118
             CollectWordsFromPivot(boardForThisThread, _WordList.Wordlist[t.Alphabet].Next, newWordList);
78
-            doneEvents[t.X * _Board.SideLength + t.Y].Set();
119
+            int tileNumberOnBoard = (t.X * _Board.SideLength + t.Y) ;
120
+            doneEvents[correspondingSlots[tileNumberOnBoard]].Set();
121
+            openSlots.Enqueue(tileNumberOnBoard % _MaxParallelSolvers);
79 122
         }
80 123
 
81 124
         private HashSet<WordOnBoard> GetWordsOnBoardNoThreads()
... ...
@@ -85,7 +128,7 @@ namespace Boggle
85 128
                 for (int j = 0; j < _Board.SideLength; j++)
86 129
                 {
87 130
                     //Collect all the tiles that make up the word.
88
-                    List<Tile> newWordList = new List<Tile>(_Board.SideLength * _Board.SideLength);
131
+                    List<Tile> newWordList = new List<Tile>(_Board.Tiles.Length);
89 132
                     //Initialize this list with the tile that we will start finding words from.
90 133
                     newWordList.Add(_Board[i, j]);
91 134
                     //Collect all the possible words from this tile. End result should be in the Hashset.
92 135