Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of Enumerable.Range vs for loop

I wondered what the performance overhead is of using Enumerable.Range was against using a foreach loop. For example:

var stringArray = Enumerable.Range(0, 4).Select(i => string.Empty).ToArray();

VS.

var stringArray = new string[4];
for (int i = 0; i < formatted.Length; i++)
{
    stringArray[i] = string.Empty;
}

I spotted these question:

  1. Why is Enumerable.Range faster than a direct yield loop?

  2. Enumerable.Range implementation

  3. Thoughts on foreach with Enumerable.Range vs traditional for loop

But I fear with the Select at the end then I might be, in effect, loop twice. However I like the elegance of using the Range option.

like image 346
jolySoft Avatar asked Oct 19 '16 11:10

jolySoft


2 Answers

From the following test the for is more efficient: (in milliseconds it is +-3 ms difference - which is insignificant..)

var watch = System.Diagnostics.Stopwatch.StartNew();
var stringArra1y = Enumerable.Range(0, 4).Select(i => string.Empty).ToArray();
watch.Stop();
Console.WriteLine(watch.ElapsedTicks); //3305

watch = System.Diagnostics.Stopwatch.StartNew();
var stringArray2 = new string[4];
for (int i = 0; i < stringArray2.Length; i++)
{
    stringArray2[i] = string.Empty;
}
watch.Stop();
Console.WriteLine(watch.ElapsedTicks); //1

But you can instead of using Enumerable.Range().Select use .Repeat:

var watch = System.Diagnostics.Stopwatch.StartNew();
var stringArra1y = Enumerable.Repeat(string.Empty, 4).ToArray();
watch.Stop();
Console.WriteLine(watch.ElapsedTicks); //391

After saying the above notice that you are talking here about very small collections (4 items). In larger collections, and especially if you remove the .ToArray() it doesn't behave the same:

var watch = System.Diagnostics.Stopwatch.StartNew();
var stringArra1y = Enumerable.Repeat(string.Empty, 100000);
watch.Stop();
Console.WriteLine(watch.ElapsedTicks); //360


watch = System.Diagnostics.Stopwatch.StartNew();
var stringArray2 = new string[100000];
for (int i = 0; i < stringArray2.Length; i++)
{
    stringArray2[i] = string.Empty;
}
watch.Stop();
Console.WriteLine(watch.ElapsedTicks); //1335

But I fear with the Select at the end then I might be, in effect, loop twice

Looking though the Reference Source both the .Range and Repeat are implemented with a yield return:

static IEnumerable<int> RangeIterator(int start, int count) {
     for (int i = 0; i < count; i++) yield return start + i;
}

So it too id deffered executed, just like the .Select meaning it does not loop twice.

Not that the use of the Stopwatch returns different results each run but the overall idea is as presented above

IMO, especially in the case of small collections go for readability over hese minor performance improvements. When you already hit performance issues, only after getting the bigger fish (for instance nested for loops on List<> instead of using a HashSet<>), deal with stuff like this.

like image 193
Gilad Green Avatar answered Sep 18 '22 12:09

Gilad Green


In my very simple test it looks like for loop is faster by 38ms for 1 000 000 strings.

static void Main(string[] args)
        {
            var start = DateTime.Now;
            EnTest();
            var end = DateTime.Now;

            var firstResult = end - start;
            Console.WriteLine("Difference for Enumerable: {0}ms", firstResult.Milliseconds);

            GC.Collect();
            Thread.Sleep(2000);

            var secondStart = DateTime.Now;
            ArTest();
            var secondEnd = DateTime.Now;

            var secondResult = secondEnd - secondStart;
            Console.WriteLine("Difference for loop: {0}ms", secondResult.Milliseconds);

            var globalResult = firstResult - secondResult;
            Console.WriteLine("Difference between tests: {0}ms", globalResult.Milliseconds);

            Console.ReadKey();
        }

        public static void EnTest()
        {
            var stringArray = Enumerable.Range(0, 1000000).Select(i => string.Empty).ToArray();
        }

        public static void ArTest()
        {
            var stringArray = new string[1000000];
            for (int i = 0; i < stringArray.Length; i++)
            {
                stringArray[i] = string.Empty;
            }
        }
like image 42
FCin Avatar answered Sep 18 '22 12:09

FCin