0

I've got the following:

[HttpPost]
public async Task<IEnumerable<PlotAutocompleteModel>> Get()
{
     IEnumerable<PlotDomain> plots = await plotService.RetrieveAllPlots();
     var concurrent = ConcurrentQueue<PlotAutoCompleteModel>();
     Parallel.ForEach(plots, (plot) =>
     {
          concurrent.Enqueue(new PlotAutocompleteModel(plot);
     });

     return concurrent;
}

With this usage, it takes about two seconds. Compared to: return plots.Select(plot => new PlotsAutocompleteModel(plot)).ToList(); which takes about four and a half seconds.

But I've always been told that for a simple transformation of a domain model into a view model, a Parallel.ForEach isn't ideal, mostly because it should be for more com-putative code. Which my usage clearly doesn't do.

Clarification: Where you would use significantly more resources, for instance you have a bitmap, a large quantity, which you have to rasterize and create new images from.

Is this the proper option for this code? I clearly see a performance gain due to the raw amount of records I'm iterating then transforming. Does a better approach and exist?

Update:

public class ProductAutocompleteModel
{
     private readonly PlotDomain plot;
     public ProductAutocompleteModel(PlotDomain plot)
     {
          this.plot = plot;
     }

     public string ProductName => plot.Project.Name;
     // Another fourteen exist.
}
5
  • What does "computative code" mean? Commented Apr 28, 2017 at 14:41
  • 1
    @stuartd An example something that would use more resources, like taking a bitmap and modifying it to a new image. Commented Apr 28, 2017 at 14:42
  • 1
    Ah, compute-intensive. Commented Apr 28, 2017 at 14:43
  • Have you profiled this code? At least, have you determined if the time is spent in the retrieval vs the transformation? Maybe it's your PlotAutocompleteModel constructor that is eating up time? Hopefully you're not doing any I/O in that constructor... Also, wy are you fully materializing the enumerable here anyway? ConcurrentQueue seems overkill, and even in your other example, why call ToList at all? It would help if you could fill in some details to make a true MCVE, and the code here is also missing some minor syntax. Commented Apr 28, 2017 at 15:04
  • Lol - it looks like Stephen got most of what I asked here in his response below while I was typing. Take a look through his suggestions. :) Commented Apr 28, 2017 at 15:05

1 Answer 1

3

With this usage, it takes about two seconds. Compared to... about four and a half seconds.

But I've always been told that for a simple transformation of a domain model into a view model, a Parallel.ForEach isn't ideal, mostly because it should be for more com-putative code.

Yeah, um... there's no way - absolutely no way - that a "simple transformation of a domain model into a view model" should take four and a half seconds. There is something seriously wrong there. It should take maybe half a millisecond or so. So, your PlotAutocompleteModel constructor is doing something like 10,000 times the amount of work that is normal.

Is this the proper option for this code? I clearly see a performance gain due to the raw amount of records I'm iterating then transforming.

Probably not, because you're hosting on ASP.NET. If you use parallelism on ASP.NET, you will see individual requests complete faster, but it will negatively impact the scalability of your web server as a whole. For this reason, I never recommend parallelism in ASP.NET handlers. (There are specific situations where it would be acceptable - such as a non-public server where you know you have a hard upper limit on the number of simultaneous users - but as a general rule, it's not a good idea).

Since your PlotAutocompleteModel constructor is taking several orders of magnitude longer than expected, I suspect that it's doing blocking I/O as part of its work. The best solution here is to change the blocking I/O to asynchronous I/O, and then use concurrent asynchrony, something like this:

class PlotAutocompleteModel
{
  public static async Task<PlotAutocompleteModel> CreateAsync(PlotDomain plot)
  {
    ... // do asynchronous I/O to create a PlotAutocompleteModel.
  }
}

[HttpPost]
public async Task<IEnumerable<PlotAutocompleteModel>> Get()
{
  IEnumerable<PlotDomain> plots = await plotService.RetrieveAllPlots();
  var tasks = plots.Select(plot => PlotAutocompleteModel.CreateAsync(plot));
  return await Task.WhenAll(tasks);
}
Sign up to request clarification or add additional context in comments.

5 Comments

Not related, but cannot not to "refactor" plots.Select(plot => PlotAutocompleteModel.CreateAsync(plot)); to plots.Select(PlotAutocompleteModel.CreateAsync); :)
@Stephen Cleary No I/O is being performed though. Also the time is measured from Visual Studio click to start the IIS Express instance and debug and start profiling. So I go off what the diagnostic tool says from start to execution, so there is some time in there where it is application startup vs actual execution.
@Greg: Multiple seconds is a ton of time, unless you're doing something like returning hundreds of thousands of results. If there's no I/O, then you'll have to make the call of whether to use parallel or not. I cannot recommend it if your server is public-facing.
@StephenCleary There are about 1.5 million records to be returned. It isn't forward facing, all internal. But I really want to learn and do it right.
@Greg: APIs don't normally return 1.5 million results. I recommend taking a step back and seeing whether you could restructure this in some way.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.