runtime: AsParallel causes extremely slow 'WorkloadJitting' performance in BenchmarkDotNet benchmark

Description

I managed to write a benchmark that makes the WorkloadJitting phase of BenchmarkDotNet perform extremely slowly.

For a count of 4:

WorkloadJitting  1: 1 op, 11779303800.00 ns, 11.7793 s/op

For a count of 8:

WorkloadJitting  1: 1 op, 29785615800.00 ns, 29.7856 s/op

Those are seconds!

The actual benchmark code runs fast after the WorkloadJitting phase finally completes.

I have included the minimal repro program in the Reproduction Steps section.

Note that removing the call to AsParallel() makes everything fast again.

Reproduction Steps

using System;
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace Test
{
    public class PerfTest
    {
        [Params(4, 8)]
        public int PredicateCount { get; set; }

        [Benchmark]
        public void Test()
        {
            IEnumerable<int> rows = new int[] { 1234 };
            var predicates = new Func<int, bool>[PredicateCount];

            for (int i = 0; i < PredicateCount; i++)
            {
                predicates[i] = x => true;
            }

            foreach (var predicate in predicates)
            {
                rows = rows.AsParallel().Where(predicate);
            }

            var x = rows.ToList();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run(typeof(Program).Assembly);
        }
    }
}

Expected behavior

Should be fast.

Actual behavior

Incredibly slow.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 16 (10 by maintainers)

Most upvoted comments

We could detect and nop the AsParallel in some cases, e.g. this exact repro could be made to “just work”… we’d just add a check to AsParallel() that was like

I took a look, and this would actually be a breaking change. Some query operators, like WithExecutionMode, are only usable once in a query; AsParallel can thus be used to essentially start a new query, but if we made it a nop in these cases, then such uses could start causing those single-use operators to fail.

I’m going to close this as by-design. Thanks for the discussion.

Here is the exact simplified code I used to reproduce this: https://github.com/Treit/misc/tree/master/AsParallelIssue

image