3

Firstly I'm not sure if this is the right place to post this question, so if I am wrong, please, move it. Thanks.

I had an assignment to compare same algorithm performance in Java and C#. The algorithm is supposed to be A* search, but I think I made it more like flooding, but it works well and I'm not here to fix it. Firstly I'll post the code I was using in Java and C# and then explain what I got.

As body of question is limited to 30000 characters and I entered more, I had to delete functions readFile() from Java and C# to make it fit.

UPDATED

After Jim Mischel has pointed out I updated hash function in C# version to be same as in Java which resulted in better performance.

Also thanks to Matt Timmermans I realized that all this time I was running C# in debug (Result of not thinking it through) and changing to release increased performance even more.

C# version:

File: Program.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;

namespace A_Star_Compare
{
    class Program
    {
        static int StartX = 0;
        static int StartY = 0;
        static int TargetX = 0;
        static int TargetY = 0;
        static int Width = 0;
        static int Height = 0;
        static TimeSpan TotalTime = TimeSpan.Zero;
        static double[] TrialTimes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        static Dictionary<int, List<int>> Obstacles = new Dictionary<int, List<int>>();

        static void Main(string[] args)
        {
            for (int z = 0; z < 10; z++)
            {
                int Index = 0;
                Console.WriteLine("z: " + z);
                for (int x = 0; x < 14; x++)
                {
                    if (x < 10)
                        Index += 10;
                    else
                        Index += 100;

                    string Line = string.Empty;
                    string FileName = "Maps-" + Index + ".txt";
                    TotalTime = TimeSpan.Zero;
                    readFile(FileName);
                    TrialTimes[x] += (double)TotalTime.TotalSeconds / 100;
                }
            }

            int Index0 = 0;

            for (int i = 0; i < 14; i++)
            {
                if (i < 10)
                    Index0 += 10;
                else
                    Index0 += 100;

                string FileName = "Maps-" + Index0 + ".txt";

                Console.WriteLine("{0} Map size: {1}*{2}. On average map solved in: {3}", FileName, Index0, Index0, (double)TrialTimes[i] / 10);
            }
        }

        static void measureTime()
        {
            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

            Algorithm Solve = new Algorithm(StartX, StartY, TargetX, TargetY, Width, Height, Obstacles);
            Solve.FullSolve();

            stopwatch.Stop();

            TotalTime += stopwatch.Elapsed;
        } 
    }
}

File: Algorithm.cs

using System.Collections.Generic;

namespace A_Star_Compare
{
    public class Node
    {
        public int X { get; set; }
        public int Y { get; set; }
        public int G { get; set; }
        public int F { get; set; }
        public int H { get; set; }
        public Node PointsTo { get; set; }

        public Node(int x, int y, int g, int f, int h, Node point)
        {
            this.X = x;
            this.Y = y;
            this.G = g;
            this.F = f;
            this.H = h;
            this.PointsTo = point;
        }

        public override bool Equals(object obj)
        {
            Node rhs = obj as Node;

            return rhs.X == this.X && rhs.Y == this.Y;
        }

        public override int GetHashCode()
        {
            int hash = 7;
            hash = 83 * hash + this.X;
            hash = 83 * hash + this.Y;
            return hash;
        }
    }

    class Algorithm
    {
        private Dictionary<int, List<int>> Obstacles { get; set; }
        public HashSet<Node> OpenList { get; set; }
        public HashSet<Node> ClosedList { get; set; }
        private Node Parent { get; set; }
        private Node LowestCost { get; set; }
        private int StartX { get; set; }
        private int StartY { get; set; }
        private int TargetX { get; set; }
        private int TargetY { get; set; }
        private int Width { get; set; }
        private int Height { get; set; }
        private bool FirstIter = true;

        public Algorithm(int stX, int stY, int tgX, int tgY, int wid, int hei, Dictionary<int, List<int>> obs)
        {
            this.StartX = stX;
            this.StartY = stY;
            this.TargetX = tgX;
            this.TargetY = tgY;
            this.Width = wid - 1;
            this.Height = hei - 1;
            this.Obstacles = new Dictionary<int, List<int>>(obs);
            this.Parent = new Node(StartX, StartY, 0, 0, 0, null);
            this.LowestCost = new Node(int.MaxValue, int.MaxValue, 0, int.MaxValue, 0, null);
            this.ClosedList = new HashSet<Node>();
            this.OpenList = new HashSet<Node>();
        }

        private bool IsBlockObstacle(int X, int Y)
        {
            if (Obstacles.ContainsKey(X) == false || (Obstacles.ContainsKey(X) == true && Obstacles[X].Contains(Y) == false))
                return false;

            return true;
        }

        private void Calculate(ref int H, int G, ref int F, int MovedX, int MovedY, Node AddToList)
        {
            int H1 = 0;

            H = (TargetX - MovedX) * 10;

            if (H < 0)
                H *= -1;

            H1 = (TargetY - MovedY) * 10;

            if (H1 < 0)
                H1 *= -1;

            H += H1;
            F = G + H;

            AddToList.F = F;
            AddToList.H = H;
            AddToList.PointsTo = Parent;
        }

        private Node GetNodeFromOpen(Node Find)
        {
            Node Ret = null;

            foreach (Node Nfo in OpenList)
            {
                if (Nfo.Equals(Find))
                {
                    Ret = Nfo;

                    break;
                }
            }

            return Ret;
        }

        private bool CheckNode(Node AddToList, int G)
        {
            if (!OpenList.Contains(AddToList))
            {
                OpenList.Add(AddToList);

                return true;
            }
            else
            {
                Node Check = GetNodeFromOpen(AddToList);

                if (Parent.G + G < Check.G)
                {
                    int Offset = Check.G - Parent.G - G;

                    Check.G -= Offset;
                    Check.F -= Offset;
                    Check.PointsTo = Parent;
                }
            }

            return false;
        }

        private void ChooseNode()
        {
            foreach (Node Nfo in OpenList)
            {
                if (Nfo.X == TargetX && Nfo.Y == TargetY)
                {
                    LowestCost = Nfo;

                    break;
                }

                if (Nfo.F < LowestCost.F)
                    LowestCost = Nfo;
            }
        }

        private void CountCost()
        {
            int[] Directions = { 1, -1 };
            int[] Diagnoly = { 1, 1, -1, 1, 1, -1, -1, -1 };
            int ParentX = Parent.X;
            int ParentY = Parent.Y;
            int MovedX = 0;
            int MovedY = 0;
            int H = 0;
            int F = 0;
            Node AddToList = null;

            //Left and right
            for (int i = 0; i < 2; i++)
            {
                //Check if it is possible to move right or left
                if (ParentX + Directions[i] <= Width && ParentX + Directions[i] >= 0)
                {
                    //Check if blocks to the right and left of parent aren't obstacles                   
                    if (!IsBlockObstacle(ParentX + Directions[i], ParentY))
                    {
                        AddToList = new Node(ParentX + Directions[i], ParentY, Parent.G + 10, 0, 0, null);

                        //Check if it is not on closed list
                        if (!ClosedList.Contains(AddToList))
                        {
                            MovedX = AddToList.X;
                            MovedY = AddToList.Y;

                            Calculate(ref H, AddToList.G, ref F, MovedX, MovedY, AddToList);
                            CheckNode(AddToList, 10);
                        }
                    }
                }
            }

            //Up and down
            for (int i = 0; i < 2; i++)
            {
                //Check if possible to move up or down
                if (ParentY + Directions[i] <= Height && ParentY + Directions[i] >= 0)
                {
                    //Check if higher and lower block of parent aren't obstacles 
                    if (!IsBlockObstacle(ParentX, ParentY + Directions[i]))
                    {
                        AddToList = new Node(ParentX, ParentY + Directions[i], Parent.G + 10, 0, 0, null);

                        if (!ClosedList.Contains(AddToList))
                        {
                            MovedX = ParentX;
                            MovedY = ParentY + Directions[i];

                            Calculate(ref H, AddToList.G, ref F, MovedX, MovedY, AddToList);
                            CheckNode(AddToList, 10);
                        }
                    }
                }
            }

            //Diagnoly
            for (int i = 0; i < 8; i += 2)
            {
                if (ParentX + Diagnoly[i] <= Width && ParentX + Diagnoly[i] >= 0 && ParentY + Diagnoly[i + 1] <= Height && ParentY + Diagnoly[i + 1] >= 0)
                {
                    if (!IsBlockObstacle(ParentX + Diagnoly[i], ParentY + Diagnoly[i + 1]))
                    {
                        AddToList = new Node(ParentX + Diagnoly[i], ParentY + Diagnoly[i + 1], Parent.G + 14, 0, 0, null);

                        if (!ClosedList.Contains(AddToList))
                        {
                            MovedX = ParentX + Diagnoly[i];
                            MovedY = ParentY + Diagnoly[i + 1];

                            Calculate(ref H, AddToList.G, ref F, MovedX, MovedY, AddToList);
                            CheckNode(AddToList, 14);
                        }
                    }
                }
            }
        }
        public void FullSolve()
        {
            Node Final = null;

            if (FirstIter)
            {
                CountCost();
                ChooseNode();
                OpenList.Remove(Parent);
                ClosedList.Add(Parent);
                Parent = LowestCost;
                OpenList.Remove(Parent);
                ClosedList.Add(Parent);
                FirstIter = false;

                FullSolve();
            }
            else
            {
                while (true)
                {
                    if (OpenList.Count == 0)
                        break;

                    CountCost();

                    HashSet<Node> Copy = new HashSet<Node>(OpenList);

                    foreach (Node Nfo in Copy)
                    {
                        Parent = Nfo;
                        CountCost();
                        ClosedList.Add(Parent);
                        OpenList.Remove(Parent);

                        if (Parent.X == TargetX && Parent.Y == TargetY)
                        {
                            Final = Parent;
                            break;
                        }
                    }

                    ChooseNode();
                    OpenList.Remove(Parent);
                    ClosedList.Add(Parent);
                    Parent = LowestCost;

                    LowestCost.F = int.MaxValue;

                    if (Parent.X == TargetX && Parent.Y == TargetY)
                    {
                        Final = Parent;
                        break;
                    }
                }
            }
        }
    }
}

Java version:

File: AStar_Compare.java

package a.star_compare;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang3.time.StopWatch;

public class AStar_Compare {

    static double totalTime;
    static double[] trialTimes = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    static int startX;
    static int startY;
    static int targetX;
    static int targetY;
    static int width;
    static int heigth;
    static HashMap<Integer, List<Integer>> obstacles = new HashMap<>();
    static NumberFormat formatter = new DecimalFormat("#0.000000000");

    public static void main(String[] args) throws FileNotFoundException, IOException {

        for (int z = 0; z < 10; z++) {
            int Index = 0;
            System.out.println("z: " + z);
            for (int x = 0; x < 5; x++) {
                if (x < 10) {
                    Index += 10;
                } else {
                    Index += 100;
                }

                String fileName = "Maps-" + Index + ".txt";

                totalTime = 0;
                readFile(fileName);
                trialTimes[x] += totalTime / 1E9 / 100;
            }
        }

        int index0 = 0;

        for (int i = 0; i < 14; i++) {
            if (i < 10) {
                index0 += 10;
            } else {
                index0 += 100;
            }
            trialTimes[i] /= 10;
            String fileName = "Maps-" + index0 + ".txt";

            System.out.println(fileName + " Map size: " + index0 + "*" + index0 + ". On average map solved in: " + formatter.format(trialTimes[i]));
        }
    }

    static void measureTime() {
        StopWatch time = new StopWatch();

        time.start();

        Algorithm solve = new Algorithm(obstacles, startX, startY, targetX, targetY, width, heigth);
        solve.FullSolve();

        time.stop();

        totalTime += time.getNanoTime();
    }

}

File: Node.java

package a.star_compare;

public class Node {
    public int x;
    public int y;
    public int g;
    public int h;
    public int f;

    public Node pointsTo;

    public Node(int gx, int gy, int gg, int gh, int gf, Node point){
        this.x = gx;
        this.y = gy;
        this.g = gg;
        this.h = gh;
        this.f = gf;
        this.pointsTo = point;
    }

    @Override
    public boolean equals(Object other){
        if(other == null) return false;
        if(other == this) return true;
        if(!(other instanceof Node)) return false;

        Node rhs = (Node)other;

        return this.x == rhs.x && this.y == rhs.y;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 83 * hash + this.x;
        hash = 83 * hash + this.y;
        return hash;
    }
}

File: Algorithm.java

package a.star_compare;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

public class Algorithm {

    private final HashMap<Integer, List<Integer>> obstacles;
    private final HashSet<Node> closedList;
    private final HashSet<Node> openList;
    private Node parent;
    private Node lowestCost;
    private final int startX;
    private final int startY;
    private final int targetX;
    private final int targetY;
    private final int width;
    private final int height;
    private boolean firstIter = true;

    public Algorithm(HashMap<Integer, List<Integer>> obs, int stX, int stY, int tgX, int tgY, int wid, int hei) {
        this.obstacles = new HashMap(obs);
        this.startX = stX;
        this.startY = stY;
        this.targetX = tgX;
        this.targetY = tgY;
        this.width = wid - 1;
        this.height = hei - 1;
        this.parent = new Node(startX, startY, 0, 0, 0, null);
        this.lowestCost = new Node(Integer.MAX_VALUE, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, 0, null);
        this.closedList = new HashSet<>();
        this.openList = new HashSet<>();
    }

    private boolean isBlockObstacle(Integer x, Integer y) {
        if (obstacles.containsKey(x) == false || (obstacles.containsKey(x) == true && obstacles.get(x).contains(y) == false)) {
            return false;
        }

        return true;
    }

    private void calculate(int h, int g, int f, int movedX, int movedY, Node addToList) {
        int h1 = 0;

        h = (targetX - movedX) * 10;

        if (h < 0) {
            h *= -1;
        }

        h1 = (targetY - movedY) * 10;

        if (h1 < 0) {
            h1 *= -1;
        }

        h += h1;
        f = g + h;

        addToList.f = f;
        addToList.h = h;
        addToList.pointsTo = parent;
    }

    private Node getNodeFromOpen(Node find) {
        Node ret = null;

        for (Node nfo : openList) {

            if (nfo.equals(find)) {
                ret = nfo;
                break;
            }

        }

        return ret;
    }

    private boolean checkNode(Node addToList, int g) {
        if (!openList.contains(addToList)) {
            openList.add(addToList);
            return true;
        } else {
            Node check = getNodeFromOpen(addToList);

            if (parent.g + g < check.g) {
                int offset = check.g - parent.g - g;

                check.g -= offset;
                check.f -= offset;
                check.pointsTo = parent;
            }
        }

        return false;
    }

    private void chooseNode() {
        for (Node nfo : openList) {
            if (nfo.x == targetX && nfo.y == targetY) {
                lowestCost = nfo;
                break;
            }

            if (nfo.f < lowestCost.f) {
                lowestCost = nfo;
            }
        }
    }

    private void countCost() {
        int[] directions = {1, -1};
        int[] diagnoly = {1, 1, -1, 1, 1, -1, -1, -1};
        int parentX = parent.x;
        int parentY = parent.y;
        int movedX = 0;
        int movedY = 0;
        int h = 0;
        int f = 0;
        Node addToList = null;

        //Left and right
        for (int i = 0; i < 2; i++) {
            //Check if it is possible to move right or left
            if (parentX + directions[i] <= width && parentX + directions[i] >= 0) {
                //Check if blocks to the right and left of parent aren't obstacles                   
                if (!isBlockObstacle(parentX + directions[i], parentY)) {
                    addToList = new Node(parentX + directions[i], parentY, parent.g + 10, 0, 0, null);

                    //Check if it is not on closed list
                    if (!closedList.contains(addToList)) {
                        movedX = addToList.x;
                        movedY = addToList.y;

                        calculate(h, addToList.g, f, movedX, movedY, addToList);
                        checkNode(addToList, 10);
                    }
                }
            }
        }

        //Up and down
        for (int i = 0; i < 2; i++) {
            //Check if possible to move up or down
            if (parentY + directions[i] <= height && parentY + directions[i] >= 0) {
                //Check if higher and lower block of parent aren't obstacles 
                if (!isBlockObstacle(parentX, parentY + directions[i])) {
                    addToList = new Node(parentX, parentY + directions[i], parent.g + 10, 0, 0, null);

                    if (!closedList.contains(addToList)) {
                        movedX = parentX;
                        movedY = parentY + directions[i];

                        calculate(h, addToList.g, f, movedX, movedY, addToList);
                        checkNode(addToList, 10);
                    }
                }
            }
        }

        //diagnoly
        for (int i = 0; i < 8; i += 2) {
            if (parentX + diagnoly[i] <= width && parentX + diagnoly[i] >= 0 && parentY + diagnoly[i + 1] <= height && parentY + diagnoly[i + 1] >= 0) {
                if (!isBlockObstacle(parentX + diagnoly[i], parentY + diagnoly[i + 1])) {
                    addToList = new Node(parentX + diagnoly[i], parentY + diagnoly[i + 1], parent.g + 14, 0, 0, null);

                    if (!closedList.contains(addToList)) {
                        movedX = parentX + diagnoly[i];
                        movedY = parentY + diagnoly[i + 1];

                        calculate(h, addToList.g, f, movedX, movedY, addToList);
                        checkNode(addToList, 14);
                    }
                }
            }
        }
    }

    public void FullSolve() {
        Node finalPath = null;

        if (firstIter) {
            countCost();
            chooseNode();
            openList.remove(parent);
            closedList.add(parent);
            parent = lowestCost;
            openList.remove(parent);
            closedList.add(parent);
            firstIter = false;

            FullSolve();
        } else {
            while (true) {
                if (openList.isEmpty()) {
                    break;
                }

                countCost();

                HashSet<Node> copy = new HashSet<>(openList);

                for (Node nfo : copy) {
                    parent = nfo;
                    countCost();
                    closedList.add(parent);
                    openList.remove(parent);

                    if (parent.x == targetX && parent.y == targetY) {
                        finalPath = parent;
                        break;
                    }
                }

                chooseNode();
                openList.remove(parent);
                closedList.add(parent);
                parent = lowestCost;

                lowestCost.f = Integer.MAX_VALUE;

                if (parent.x == targetX && parent.y == targetY) {
                    finalPath = parent;
                    break;
                }
            }        
        }
    }
}

The testing was done with pregenerated map files. I have 14 map files each of them contains a 100 maps with specific size. With lowest one being map by 10 * 10 and highest being by 500 * 500.

Also note that if each map has 100 examples it means that algorithm was tested 100 times to work with one specific size, furthermore I wanted to increase accuracy even more so I repeat whole process 10 times. Which gives me 1000 test with one map. I of course average those times.

I'm not really familiar with high accuracy time measuring methods so I used StopWatch() in both Java and C# (To use it in Java I downloaded it from apache commons). What I did was after reading one map information I called function measureTime() and started StopWatch() then call Algorithm class and make it solve puzzle after that I'd stop StopWatch() and take time.

Here are the results I got:
I'm posting image because I'm not sure how to make a table here. Times are in second, how much it took to solve one map in average.

Note after "-" symbol there is map size. (Maps-20.txt means map by 20 * 20 and so on)

table

Also a graph:

graph

These results really surprised me, I was expecting one language having a bit of an advantage, but not like this. After update C# graph looks similar to Java graph, but has steeper growth rate. First I thought that I made some mistake while copying algorithm to Java (Firstly I wrote in C#), but I couldn't find any. So assuming that I didn't make some silly mistake.

How can I improve C# performance even more?

Also one thing I thought about getting these results that in Dictionary<int, List<int>> instead of using List<int> I could use HashSet<int> since I only need to confirm if element exists or not. But as I am not dealing with thousands of elements I don't think that it could be major factor.

Thanks.

iCoRe
  • 177
  • 1
  • 6
  • http://blog.bodurov.com/Performance-SortedList-SortedDictionary-Dictionary-Hashtable/ Seems that dictionnary are more performante with huge of data. You can use HashTable here for your test but is deprecated since Net 2.0 – OrcusZ Dec 21 '16 at 15:16
  • Where did you read that Java makes it's applications multi-threaded? – Jorn Vernee Dec 21 '16 at 15:17
  • 1
    @JornVernee http://stackoverflow.com/questions/1049004/java-vs-c-are-there-any-studies-that-compare-their-execution-speed – iCoRe Dec 21 '16 at 15:19
  • Thanks for the link – OrcusZ Dec 21 '16 at 15:20
  • 1
    A Java program isn't magically multi-threaded. You need to make the program multi-threaded yourself to make usage of multiple cores. – EpicSam Dec 21 '16 at 15:24
  • If you want to find out what is causing your C# version so take so much more time then profile it and check where all that extra time is being wasted. – EpicSam Dec 21 '16 at 15:29
  • I looked at the link and it's talking about [a specific benchmark](http://benchmarksgame.alioth.debian.org/u64q/csharp.html), saying that _"but note that for some reason the Java implementations are multithreaded"_, which doesn't mean that is something the compiler just does. I don't see how it could. – Jorn Vernee Dec 21 '16 at 15:29
  • @JornVernee Yes after re-reading it I can see that, but that's exactly why I'm asking if I am wrong. And I want some comments about that huge performance difference. Even if it is some mistake on my side then I was coding, I'd like to know it. – iCoRe Dec 21 '16 at 15:32
  • 2
    Change your C# code so that the `GetHashCode` method of `Node` uses the same hash generation algorithm that the Java code does. I suspect that'll make a big difference. – Jim Mischel Dec 21 '16 at 15:33
  • One thing is that Java uses adaptive optimization while C# just JITs everything on startup. I.e. the Java JIT _could_ be emiting code specifically tailored to the data that it is seeing. You would have to check the assembler output to confirm, which is not a nice job. – Jorn Vernee Dec 21 '16 at 15:34
  • @JimMischel I'll try to do it. Both of the HashCodes were auto generated by Visual Studio and NetBeans. – iCoRe Dec 21 '16 at 15:37
  • @JornVernee: Seems unlikely that adaptive optimization could make that much of a difference, though. – Jim Mischel Dec 21 '16 at 15:41
  • 2
    Are you sure you're running the Release (optimized) build of the C# version? – Matt Timmermans Dec 21 '16 at 15:45
  • 1
    Try using the same hash function in C# that you do in Java. If C#'s hash function is the identity on small integers, then XORing two hashes is a bad time, causing collisions and the perhaps quadratic behavior that you're seeing. – David Eisenstat Dec 21 '16 at 15:49
  • @DavidEisenstat I changed hash function as suggested by Jim Mischel and that gave better results. So if I understood correctly I should use XOR only then dealing with large numbers? – iCoRe Dec 21 '16 at 15:52
  • @MattTimmermans Thanks for that. Somehow I didn't realize that all this time I was running it on debug. Changing it to release improved results furthermore to ~0.88s which is still almost twice slower than Java. – iCoRe Dec 21 '16 at 15:55
  • @iCoRe Certainly you shouldn't use XOR on small numbers. Whether it's better on big numbers depends on the big numbers. – David Eisenstat Dec 21 '16 at 15:56
  • @MattTimmermans Somehow I can't edit my answers. What I forgot to say was that that improvement was on largest map 500 * 500 size. – iCoRe Dec 21 '16 at 15:56
  • Also this.X.GetHashCode() ^ this.Y.GetHashCode(); ist like 10 times slower than, this.X ^ this.Y; – Fabian S. Dec 21 '16 at 15:56
  • @DavidEisenstat Seems like I really need to learn more about effective ways to override hashes. Could you link me to some useful information or give a pointer where to look? – iCoRe Dec 21 '16 at 15:58
  • Why does the C# version of calculate take refs? – Matt Timmermans Dec 21 '16 at 16:36
  • @MattTimmermans that's something I forgot to change. I was using bit different code in the beginning. – iCoRe Dec 21 '16 at 16:45
  • If you're running the C# in Visual Studio, be sure to run without debugging (i.e. Ctrl+F5) rather than F5. The performance difference will be quite striking. That is, do a release build and then run without debugging. Or, run from the command line. – Jim Mischel Dec 21 '16 at 17:24
  • @JimMischel Yes I already did that and updated my post, new graph and table. I had to change to release build to get those results. – iCoRe Dec 21 '16 at 17:27
  • Also consider implementing [IEquatable](https://msdn.microsoft.com/en-us/library/ms131190(v=vs.110).aspx) on your `Node` class. That could potentially be faster than the call to `Equals(object)`. – Jim Mischel Dec 21 '16 at 17:28
  • Okay, as long as when you run you're doing Ctrl+F5. Running a release build with F5 will be almost as slow as running a debug build with F5. – Jim Mischel Dec 21 '16 at 17:29

0 Answers0