TheDeveloperBlog.com


C# Arithmetic Expression Optimization

Arithmetic optimization. The performance of an arithmetic expression can be improved. Often code will compute arithmetic results using a series of local variables. But using a compound expression and storing it in a single local variable is typically faster.


Example. First, we see a benchmarking program. This program tests two versions of a method that performs a load, multiply, load, multiply, addition, subtraction, and another multiply operation.

Benchmark

Method1: The first version, Method1, uses five local variables to store the intermediate results of this computation.

Method2: This combines the arithmetic into a single expression. The results of the program show that Method2 is somewhat faster.

C# program that measures performance

using System;
using System.Diagnostics;

class Program
{
    static int _temp1 = 5;
    static int _temp2 = 10;

    static int Method1()
    {
	// Use local variables to perform computation.
	int a = _temp1;
	int b = a * 10;
	int c = b * _temp2;
	int d = c + 5;
	int e = d - _temp2;
	return e * 2;
    }

    static int Method2()
    {
	// Use expression to perform computation.
	return ((_temp1 * 10 * _temp2) + 5 - _temp2) * 2;
    }

    const int _max = 300000000;
    static void Main()
    {
	// Test.
	Console.WriteLine(Method1());
	Console.WriteLine(Method2());
	// Clean up environment.
	GC.Collect();
	System.Threading.Thread.Sleep(10);

	for (int a = 0; a < 10; a++)
	{
	    var s1 = Stopwatch.StartNew();
	    for (int i = 0; i < _max; i++)
	    {
		Method1();
	    }
	    s1.Stop();
	    var s2 = Stopwatch.StartNew();
	    for (int i = 0; i < _max; i++)
	    {
		Method2();
	    }
	    s2.Stop();
	    Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000 * 1000) /
		_max).ToString("0.00") + " ns");
	    Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000 * 1000) /
		_max).ToString("0.00") + " ns");
	}
	Console.Read();
    }
}

Output

990
990
2.32 ns
1.92 ns
2.53 ns
2.10 ns
2.40 ns
1.91 ns
2.31 ns
1.91 ns
2.31 ns
1.91 ns
2.31 ns
1.92 ns
2.32 ns
1.92 ns
2.31 ns
1.91 ns
2.32 ns
1.91 ns
2.36 ns
1.91 ns

Compiled. As the C# language is a high-level language, it is compiled to an intermediate form. The intermediate language in Method1 uses several local variables. These must be allocated on the activation records during execution.

Activation Record

However: Method2 uses the evaluation stack more directly. It does not allocate local variable memory and store values in those slots.

Compiler

This optimization is difficult to elicit from a benchmark program. This is because the JIT (just-in-time) compiler can solve most of the inefficiencies on its own. Flow analysis or even peephole optimization could achieve this effect.

JIT

Note: Please retest this on different versions of the .NET Framework if you are in doubt.


Summary. We can rewrite arithmetic operations to require less execution time. To do this, we rewrote a series of local variable assignments into a single expression. This resulted in more efficient lower-level representations.

Optimizations