1

I've searched for the differences between the * operator and the Math.BigMul method, and found nothing. So I've decided I would try and test their efficiency against each other. Consider the following code :

public class Program
{
    static void Main()
    {

        Stopwatch MulOperatorWatch = new Stopwatch();
        Stopwatch MulMethodWatch = new Stopwatch();

        MulOperatorWatch.Start();
        // Creates a new MulOperatorClass to perform the start method 100 times.
        for (int i = 0; i < 100; i++)
        {
            MulOperatorClass mOperator = new MulOperatorClass();
            mOperator.start();

        }
        MulOperatorWatch.Stop();

        MulMethodWatch.Start();
        for (int i = 0; i < 100; i++)
        {
            MulMethodClass mMethod = new MulMethodClass();
            mMethod.start();
        }
        MulMethodWatch.Stop();

        Console.WriteLine("Operator = " + MulOperatorWatch.ElapsedMilliseconds.ToString());
        Console.WriteLine("Method = " + MulMethodWatch.ElapsedMilliseconds.ToString());
        Console.ReadLine();
    }

    public class MulOperatorClass
    {
        public void start()
        {
            List<long> MulOperatorList = new List<long>();
            for (int i = 0; i < 15000000; i++) 
            {
                MulOperatorList.Add(i * i);
            }
        }
    }

    public class MulMethodClass
    {
        public void start()
        {
            List<long> MulMethodList = new List<long>();
            for (int i = 0; i < 15000000; i++)
            {
                MulMethodList.Add(Math.BigMul(i,i));
            }
        }
    }
}

To sum it up : I've created two classes - MulMethodClass and MulOperatorClass that performs both the start method, which fills a varible of type List<long with the values of i multiply by i many times. The only difference between these methods are the use of the * operator in the operator class, and the use of the Math.BigMul in the method class.

I'm creating 100 instances of each of these classes, just to prevent and overflow of the lists (I can't create a 1000000000 items list).

I then measure the time it takes for each of the 100 classes to execute. The results are pretty peculiar : I've did this process about 15 times and the average results were (in milliseconds) :

Operator = 20357

Method = 24579

That about 4.5 seconds difference, which I think is a lot. I've looked at the source code of the BigMul method - it uses the * operator, and practically does the same exact thing.

So, for my quesitons :

  • Why such method even exist? It does exactly the same thing.
  • If it does exactly the same thing, why there is a huge efficiency difference between these two?

I'm just curious :)

Community
  • 1
  • 1
Eminem
  • 870
  • 5
  • 20

1 Answers1

2

Microbenchmarking is art. You are right the method is around 10% slower on x86. Same speed on x64. Note that you have to multiply two longs, so ((long)i) * ((long)i), because it is BigMul!

Now, some easy rules if you want to microbenchmark:

A) Don't allocate memory in the benchmarked code... You don't want the GC to run (you are enlarging the List<>)

B) Preallocate the memory outside the timed zone (create the List<> with the right capacity before running the code)

C) Run at least once or twice the methods before benchmarking it.

D) Try to not do anything but what you are benchmarking, but to force the compiler to run your code. For example checking for an always true condition based on the result of the operation, and throwing an exception if it is false is normally good enough to fool the compiler.

static void Main()
{
    // Check x86 or x64
    Console.WriteLine(IntPtr.Size == 4 ? "x86" : "x64");

    // Check Debug/Release
    Console.WriteLine(IsDebug() ? "Debug, USELESS BENCHMARK" : "Release");

    // Check if debugger is attached
    Console.WriteLine(System.Diagnostics.Debugger.IsAttached ? "Debugger attached, USELESS BENCHMARK!" : "Debugger not attached");

    // High priority
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

    Stopwatch MulOperatorWatch = new Stopwatch();
    Stopwatch MulMethodWatch = new Stopwatch();

    // Prerunning of the benchmarked methods
    MulMethodClass.start();
    MulOperatorClass.start();

    {
        // No useless method allocation here
        MulMethodWatch.Start();

        for (int i = 0; i < 100; i++)
        {
            MulMethodClass.start();
        }

        MulMethodWatch.Stop();
    }

    {
        // No useless method allocation here
        MulOperatorWatch.Start();

        for (int i = 0; i < 100; i++)
        {
            MulOperatorClass.start();
        }

        MulOperatorWatch.Stop();
    }


    Console.WriteLine("Operator = " + MulOperatorWatch.ElapsedMilliseconds.ToString());
    Console.WriteLine("Method = " + MulMethodWatch.ElapsedMilliseconds.ToString());
    Console.ReadLine();
}

public class MulOperatorClass
{
    // The method is static. No useless memory allocation
    public static void start()
    {
        for (int i = 2; i < 15000000; i++)
        {
            // This condition will always be false, but the compiler
            // won't be able to remove the code
            if (((long)i) * ((long)i) == ((long)i))
            {
                throw new Exception();
            }
        }
    }
}

public class MulMethodClass
{
    public static void start()
    {
        // The method is static. No useless memory allocation
        for (int i = 2; i < 15000000; i++)
        {
            // This condition will always be false, but the compiler
            // won't be able to remove the code
            if (Math.BigMul(i, i) == i)
            {
                throw new Exception();
            }
        }
    }
}

private static bool IsDebug()
{
    // Taken from http://stackoverflow.com/questions/2104099/c-sharp-if-then-directives-for-debug-vs-release
    object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(DebuggableAttribute), false);

    if ((customAttributes != null) && (customAttributes.Length == 1))
    {
        DebuggableAttribute attribute = customAttributes[0] as DebuggableAttribute;
        return (attribute.IsJITOptimizerDisabled && attribute.IsJITTrackingEnabled);
    }

    return false;
}

E) If you are really sure your code is ok, try changing the order of the tests

F) Put your program in higher priority

but be happy :-)

at least another persons had the same question, and wrote a blog article: http://reflectivecode.com/2008/10/mathbigmul-exposed/

He did the same errors you did.

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • Ahh, nice answer. Never did this kind of `Microbenchmarking` as you call it. Thanks for the useful info and the helpful article. Just some questions - is there a reason you loop from `int i = 2` and not `int i = 1`? I'm not sure I understand why you run the `start` method one time before the main loop? you mentioned it in C, but why? Good day sir :) – Eminem Apr 19 '15 at 06:30
  • Well, I got the `int i = 2` now - to prevent the exception to be thrown :) Nice. – Eminem Apr 19 '15 at 06:40
  • @Eminem You are right on the `int i = 2`. For the pre-running: sometimes the code is lazily compiled by the CLR into machine code at first run. Clearly this "compilation time" isn't something you want to benchmark. You run it once or twice and so you are sure your code is already ready-to-run. – xanatos Apr 19 '15 at 06:44