Hi Everyone,

Background:
I have recently implemented the A* pathfinding algorithm in C# based on some pseudo code that I found.
I need the algorithm to run as fast as possible and at present my code isn't quite cutting it. I understand that the heuristics used can have a big impact on the performance of the algorithm, however, I have gotten to the point where I feel that the only way that I can make further gains is to optimise the algorithm itself.

Reason:
The reason why I need the algorithm to be so fast is so that I can 'instantly' determine a decent estimate for the time it would take to travel between two points.

Further Info:
So far I have tried using a hierarchical graph structure to allow at least a partial path to be returned immediately -- The problem with this approach is that I don't actually need path data immediately, I simply need the path to be computed in order to get the time estimate.

I have also considered making the pathfind function unsafe in order to improve performance but I'm not really sure how this would work -- I can't create node pointers because they are "non-unmanaged" types.

This is essentially the first time where I've had to write performance critical code in C# (usually I'm working in C++) so I'm not sure what I could do to really squeeze those gains. If anyone has any ideas then I would greatly appreciate it.

Without seeing the code and how you implemented the priority queue there really isn't anything we can say, it would all be guesses.

A* algorithm:

using System.Collections.Generic;

namespace Route
{
    public delegate float HeuristicDelegate(NavigationNode node, NavigationNode goalNode);
    public delegate float EdgeCostDelegate(GraphEdge edge);

    public class RouteFinder
    {
        public bool ComputePath(NavigationNode startNode, NavigationNode goalNode,
                                ref List<NavigationNode> outPath, HeuristicDelegate heuristic, EdgeCostDelegate costFunc)

        {
            // Create a priority queue to store the open list of nodes
            var openList = new PriorityQueue<float, NavigationNode>();

            // Put the start node in the open list
            startNode.Cost = 0.0f;
            startNode.EstimatedCostToGoal = heuristic(startNode, goalNode);
            openList.Enqueue(startNode.EstimatedCostToGoal, startNode);

            NavigationNode currentNode = startNode;

            // While the open list isn't empty
            while (!openList.IsEmpty)
            {
                // Get the smallest element in the open list
                currentNode = openList.Dequeue();

                // If the current node is the goal node then terminate
                if(currentNode.Equals(goalNode))
                {
                    break;
                }

                // Get all potential successor nodes via edges
                var edges = currentNode.Edges;
                foreach(var edge in edges)
                {
                    var neighbour = (NavigationNode)edge.Head;

                    // Set the cost of the successor to be the cost of the current node
                    // plus the cost to get to the successor node from the current node
                    var fEdgeCost = costFunc(edge);
                    var fNeighbourCost = currentNode.Cost + fEdgeCost;

                    switch (neighbour.State)
                    {
                        // If the successor is in the closed list but the current node is as good
                        // as, or better, then discard the successor and continue
                        case NodeState.closed:
                            if (neighbour.Cost <= fNeighbourCost)   // Skip if no shorter route is found
                            {
                                continue;
                            }
                            // Else, remove the node from the closed list
                            neighbour.State = NodeState.other;
                            break;
                        case NodeState.open:
                            if (neighbour.Cost <= fNeighbourCost)   // Skip if no shorter route is found
                            {
                                //continue;
                            }
                            break;
                        case NodeState.unvisited:    // An unvisited node
                            // Use heuristic function to calculate H value of neighbour
                            neighbour.Cost = fNeighbourCost;
                            neighbour.Predecessor = currentNode;
                            neighbour.EstimatedCostToGoal = (fNeighbourCost + heuristic(neighbour, goalNode));

                            // Add the node to the open list
                            neighbour.State = NodeState.open;
                            openList.Enqueue(neighbour.EstimatedCostToGoal, neighbour);
                            break;
                        default:
                            break;
                    }
                }

                // The current node has already been removed from the open list
                // but still needs a status change
                currentNode.State = NodeState.closed;
            }

            // We are at this point either when the goal node has been found or there's no solution
            // Determine which:
            if(!currentNode.Equals(goalNode))
            {
                return false;
            }

            // Work along the path, accumulating nodes
            while(!currentNode.Equals(startNode))
            {
                outPath.Add(currentNode);
                currentNode = currentNode.Predecessor;
            }

            // Include the start node
            outPath.Add(startNode);

            // The path is in reverse order at this point (start is last)
            // Use the reverse function:
            outPath.Reverse();

            return true;
        }
    }
}

Priority Queue:

using System.Collections.Generic;
using System.Linq;

namespace Route
{
    public class PriorityQueue<P, V>
    {
        private SortedDictionary<P, Queue<V>> _list = new SortedDictionary<P, Queue<V>>();

        /// <summary>
        /// Enqueue adds elements to the priority queue. First it determines whether or not
        /// _list already contains a matching priority entry --> if it does then the sub-queue
        /// will have the value added to it.
        /// --> Otherwise a new sub-queue will be created to store the value
        /// </summary>
        /// <param name="priority"></param>
        /// <param name="value"></param>
        public void Enqueue(P priority, V value)
        {
            Queue<V> q;
            if (!_list.TryGetValue(priority, out q))
            {
                q = new Queue<V>();
                _list.Add(priority, q);
            }

            q.Enqueue(value);
        }

        /// <summary>
        /// Dequeue removes elements from _list by grabbing the first element and calling
        /// the sub-queue's Dequeue function. If at that point there are no more elements
        /// in the sub-queue, then the sub-queue is completely removed, otherwise
        /// it will be left at the top so the the other elements (with matching priority)
        /// are dequeued next time the function is called.
        /// </summary>
        /// <returns>Returns the highest priority value</returns>
        public V Dequeue()
        {
            // Will throw if there isn't a first element!
            var pair = _list.First();
            var v = pair.Value.Dequeue();
            if (pair.Value.Count == 0) // Nothing left of the top priority (the queue on top)
            {
                _list.Remove(pair.Key);
            }

            return v;
        }

        /// <summary>
        /// Determines whether list contains any elements. True if empty, false otherwise
        /// </summary>
        public bool IsEmpty
        {
            get { return !_list.Any(); }
        }
    }
}

I don't know why I didn't provide any code before o_O

A bit different than how I would do this as it only works on graphs (not everything is stored as a graph :)) and I wouldn't have used a SortedDictionary storing Queues. Let me play with this a bit to see what I can come up with.

I should probably mention that I don't mind suggestions on data structure alterations etc. if it improves performance. For example, if you have a more efficient implementation for a PriorityQueue then I would like to see it :) -- Calls to dequeue in my implementation are slightly more costly than I would like

So I wrote a PriorityQueue (which still might be improved) and ran some timing tests against the one you gave above. The three tests are enqueue x items, dequeue x items, enqueue then dequeue x items (which is probably closer to what an A* would be doing)

Timings:
Momerath enqueue = 852 milliseconds for 1,000,000 items
Momerath dequeue = 6357 milliseconds for 1,000,000 items
Momerath en/dequeue = 292 milliseconds for 1,000,000 items
spuriousgeek enqueue = 8535 milliseconds for 1,000,000 items
spuriousgeek dequeue = 5098 milliseconds for 1,000,000 items
spuriousgeek en/dequeue = 966 milliseconds for 1,000,000 items

using System;

namespace rwhittle.Collections.Generic {
    public class PriorityQueue<P, D> where P : IComparable {
        Node[] nodes = new Node[1];

        int position = 1;
        public Boolean IsEmpty {
            get {
                return position <= 1;
            }
        }

        public void Enqueue(P priority, D data) {
            Node node = new Node(priority, data);
            if (position >= nodes.Length) {
                Array.Resize(ref nodes, nodes.Length * 2);
            }
            nodes[position] = node;
            Swim(position);
            position++;
        }

        public D Dequeue() {
            if (position <= 1) {
                throw new InvalidOperationException("Priority Queue is empty");
            }

            D value = nodes[1].data;
            position--;
            nodes[1] = nodes[position];
            Sink(1);

            return value;
        }

        private void Sink(int n) {
            int t = n << 1;
            t = MinNode(t, t + 1);
            while (t < position && nodes[n] > nodes[t]) {
                Node temp = nodes[n];
                nodes[n] = nodes[t];
                nodes[t] = temp;
                n = t;
                t = n << 1;
                t = MinNode(t, t + 1);
            }
        }

        private void Swim(int n) {
            int t = n >> 1;
            while (n > 1 && nodes[n] < nodes[t]) {
                Node temp = nodes[n];
                nodes[n] = nodes[t];
                nodes[t] = temp;
                n = t;
                t = n >> 1;
            }
        }

        private int MinNode(int a, int b) {
            return b < position ? nodes[a] < nodes[b] ? a : b : a < position ? a : position;
        }

        internal struct Node {
            internal P priority;
            internal D data;

            public Node(P p, D d) {
                priority = p;
                data = d;
            }

            public static Boolean operator <(Node a, Node b) {
                return a.priority.CompareTo(b.priority) < 0;
            }

            public static Boolean operator >(Node a, Node b) {
                return a.priority.CompareTo(b.priority) >= 0;
            }
        }
    }
}

Well the performance timings definitely seem promising. I will look into implementing this and get back to you when I've had chance to get it done.

I've just done some testing within my application and I have to say the performance has been improved significantly :)
For now I will mark this thread as solved, however if you think of any further optimisations then please feel free to pm me.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.