0

I have N-queen problem written in Java and C#. You can find out more about 8-queen problem here.

Here is Java code:

package nqueens;
import java.util.Arrays;
public class NQueens {
  public static void main(String args[]) {
    int n = 13;
    int[] ploca = new int[n];
    postaviKraljicuNaPlocu(0, ploca);
  }
  private static void postaviKraljicuNaPlocu(int Ki, int[] ploca) {
    int n = ploca.length;
    if (Ki == n) {
      System.out.println(Arrays.toString(ploca));
    } else {
      for (int kolona = 0; kolona < n; kolona++) {
        if (jeLiSigurnoMjesto(kolona, Ki, ploca)) {
          ploca[Ki] = kolona;
          postaviKraljicuNaPlocu(Ki + 1, ploca);
          ploca[Ki] = -1;
        }
      }
    }
  }
  private static boolean jeLiSigurnoMjesto(int kolona, int Ki, int[] ploca) {
    for (int i = 0; i < Ki; i++) {
      if (ploca[i] == kolona) { 
        return false;
      }
      if (Math.abs(ploca[i] - kolona) == Math.abs(i - Ki)) {
        return false;
      }
    }
    return true;
  }
}

Here is C# code:

using System;
namespace nkraljica
{
    public class NKraljica
    {
        public static void Main(string[] args)
        {
            int n = 13;
            int[] ploca = new int[n];
            postaviKraljicuNaPlocu(0, ploca);
        }
        private static void postaviKraljicuNaPlocu(int Ki, int[] ploca)
        {
            int n = ploca.Length;
            if (Ki == n)
            {
                Console.WriteLine(string.Join("", ploca));
            }
            else
            {
                for (int kolona = 0; kolona < n; kolona++)
                {
                    if (jeLiSigurnoMjesto(kolona, Ki, ploca))
                    {
                        ploca[Ki] = kolona;
                        postaviKraljicuNaPlocu(Ki + 1, ploca);
                        ploca[Ki] = -1;
                    }
                }
            }
        }
        private static bool jeLiSigurnoMjesto(int kolona, int Ki, int[] ploca)
        {
            for (int i = 0; i < Ki; i++)
            {
                if (ploca[i] == kolona)
                {
                    return false;
                }
                if (Math.Abs(ploca[i] - kolona) == Math.Abs(i - Ki))
                {
                    return false;
                }
            }
            return true;
        }
    }
}

For N=13, Java code finishes in 1,7 second, but C# in 17,3 seconds. Why is difference so big?

helloflash
  • 2,457
  • 2
  • 15
  • 19
MrD
  • 2,423
  • 3
  • 33
  • 57
  • This question appears to be off-topic because it is about language performance of an algorithm that does not fall under the category of scientific computing. Should be migrated to stackoverflow. –  Sep 11 '14 at 17:15
  • How long do they each take if you comment out the WriteLine/println statements? Are you compiling the C# code in RELEASE mode? – StriplingWarrior Sep 11 '14 at 17:53
  • How did you measured the times? Did you used some micro-benchmark? How many times you have repeated the experiment? – Maciej Dobrowolski Sep 11 '14 at 18:32
  • Interesting, how did you time this? It may be the fact that it is so small and the .net version is going to compile it down to machine code while the VM will interpret the byte code. This is a benefit on bigger programs, but a detriment on small things like this. Try using NGEN.exe to compile the .net version before running it. – Phillip Scott Givens Sep 11 '14 at 18:55
  • You said the benchmark run 1.7 to 17.3 seconds. That's too short of a run-time to evaluate the performance of the your algorithm under the two environments. Ideally, a benchmark should run for a hour or more. Short benchmarks tend to be overshadowed by startup time of application and computer memory cache behavior. Also, I prefer to run the benchmark at least 5 times, and throw out the first run. – KC-NH Sep 11 '14 at 20:06
  • How are you timing the C# code? Also, are you running it under the debugger, or are you running it without the debugger attached (i.e. Run without Debugging)? If you're running a debug build or if you have a debugger attached, your timings will be unreliable. – Jim Mischel Sep 11 '14 at 21:30

1 Answers1

6

To really get to the bottom of this, you need to profile the c# code to determine where it is spending time. In the past, I have used the dotTrace profiler successfully for tasks like this; Visual Studio also has a built-in profiler; and more recommendations can be found here.

For us to give much of an opinion on the time difference, you need to describe your build and run environments with some detail. Are you building debug or (hopefully) release? What versions of .Net and Visual Studio are you using? What version of Java, and what build tools?

That being said, I experimented with your c# code a bit in VS 2008 / .Net 3.5 / Release build, and found the following:

  • Running with Visual Studio attached:
    • Total time to run your code is roughly 10.7 seconds, repeatably.
    • If I replace your calls to Console.WriteLine() with calls to save the results in a StringBuilder, the time is reduced to 4.9 seconds. Apparently Console.WriteLine is very slow!
    • If I simply eliminate the calls to Console.WriteLine() and replace them with a dummy, the time is barely changed. So StringBuilder is pretty fast.
    • If I replace the calls to Math.Abs() with an inline check, the time is reduced to 4.0 seconds. Possibly .Net is not optimizing or inlining the calls to Math.Abs() while Java does do so.
  • Running from the command line, or otherwise without Visual Studio attached:
    • Total time using Console.WriteLine(): 9-10 seconds, repeatably.
    • Total time using StringBuilder logging: 3.0 seconds.
    • Total time using dummy logging: 3.0 seconds.
    • Total time with an inline version of Math.Abs(): 1.6 seconds.

From this, I conclude:

  1. Running with Visual Studio attached can significantly slow things down even in release mode. (See also here.) Be sure to run both your Java and c# executables from the command line when timing performance.

  2. Console.WriteLine() is slow -- slow enough that it dominates the time taken by your algorithm. The Java equivalent might be faster, and if so that could explain some of the difference.

  3. Possibly Java is doing a better job at inlining or optimizing the calls to Math.Abs(). The improvement from manually inlining the calls is significant.

I don't have Java installed on my computer to run the comparisons myself.

Here's the code I used to do the testing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Diagnostics;

namespace nkraljica
{
    public class NKraljicaMain
    {
        public static void Main(string[] args)
        {
            int n = 13;

            var stopwatch = new Stopwatch();

            stopwatch.Reset();
            stopwatch.Start();
            Abs.NKraljica.postaviKraljicuNaPlocu(n, Console.WriteLine);
            stopwatch.Stop();
            var consoleElapsed = stopwatch.Elapsed;

            stopwatch.Reset();
            stopwatch.Start();
            StringBuilder sb1 = new StringBuilder();
            Abs.NKraljica.postaviKraljicuNaPlocu(n, s => sb1.AppendLine(s));
            stopwatch.Stop();

            var sbElapsed = stopwatch.Elapsed;

            stopwatch.Reset();
            stopwatch.Start();
            string lastLine = null;
            Abs.NKraljica.postaviKraljicuNaPlocu(n, s => lastLine = s);
            stopwatch.Stop();

            var nologElapsed = stopwatch.Elapsed;

            stopwatch.Reset();
            stopwatch.Start();
            StringBuilder sb2 = new StringBuilder();
            Inline.NKraljica.postaviKraljicuNaPlocu(n, s => sb2.AppendLine(s));
            stopwatch.Stop();

            var inlineElapsed = stopwatch.Elapsed;

            Debug.Assert(sb1.ToString() == sb2.ToString());

            Console.WriteLine(string.Format("Console logging time = {0}", consoleElapsed));
            Console.WriteLine(string.Format("StringBuilder logging time = {0}", sbElapsed));
            Console.WriteLine(string.Format("Dummy logging time = {0}", nologElapsed));
            Console.WriteLine(string.Format("Inline Abs + StringBuilder logging time = {0}", inlineElapsed));

            Console.ReadLine();
        }
    }
}

namespace nkraljica.Abs
{
    public class NKraljica
    {
        public static void postaviKraljicuNaPlocu(int n, Action<string> reportFunc)
        {
            int[] ploca = new int[n];
            postaviKraljicuNaPlocu(0, ploca, reportFunc);
        }

        private static void postaviKraljicuNaPlocu(int Ki, int[] ploca, Action<string> reportFunc)
        {
            int n = ploca.Length;
            if (Ki == n)
            {
                reportFunc(ploca.Aggregate(new StringBuilder(), (sb, i) => sb.Append(i)).ToString());
            }
            else
            {
                for (int kolona = 0; kolona < n; kolona++)
                {
                    if (jeLiSigurnoMjesto(kolona, Ki, ploca))
                    {
                        ploca[Ki] = kolona;
                        postaviKraljicuNaPlocu(Ki + 1, ploca, reportFunc);
                        ploca[Ki] = -1;
                    }
                }
            }
        }
        private static bool jeLiSigurnoMjesto(int kolona, int Ki, int[] ploca)
        {
            for (int i = 0; i < Ki; i++)
            {
                if (ploca[i] == kolona)
                {
                    return false;
                }
                if (Math.Abs(ploca[i] - kolona) == Math.Abs(i - Ki))
                {
                    return false;
                }
            }
            return true;
        }
    }
}

namespace nkraljica.Inline
{
    public class NKraljica
    {
        public static void postaviKraljicuNaPlocu(int n, Action<string> reportFunc)
        {
            int[] ploca = new int[n];
            postaviKraljicuNaPlocu(0, ploca, reportFunc);
        }

        private static void postaviKraljicuNaPlocu(int Ki, int[] ploca, Action<string> reportFunc)
        {
            int n = ploca.Length;
            if (Ki == n)
            {
                reportFunc(ploca.Aggregate(new StringBuilder(), (sb, i) => sb.Append(i)).ToString());
            }
            else
            {
                for (int kolona = 0; kolona < n; kolona++)
                {
                    if (jeLiSigurnoMjesto(kolona, Ki, ploca))
                    {
                        ploca[Ki] = kolona;
                        postaviKraljicuNaPlocu(Ki + 1, ploca, reportFunc);
                        ploca[Ki] = -1;
                    }
                }
            }
        }
        private static bool jeLiSigurnoMjesto(int kolona, int Ki, int[] ploca)
        {
            for (int i = 0; i < Ki; i++)
            {
                if (ploca[i] == kolona)
                {
                    return false;
                }
                int diff1 = ploca[i] - kolona;
                int diff2 = i - Ki;
                if (diff1 == diff2 || diff1 == checked(-diff2))
                {
                    return false;
                }
            }
            return true;
        }
    }
}
Community
  • 1
  • 1
dbc
  • 104,963
  • 20
  • 228
  • 340
  • 2
    Good work. One comment: it's not running in Visual Studio that's the slowdown, but running with the debugger attached. If in Visual Studio you select Debug->Run without Debugging (or press Ctrl+F5), the program will run at the same speed as it runs from the command line. – Jim Mischel Sep 12 '14 at 13:46