DEV Community

Cover image for Dataverse LowCode Plugins Performance Benchmark
Riccardo Gregori
Riccardo Gregori

Posted on

Dataverse LowCode Plugins Performance Benchmark

In the ever-evolving world of business applications, speed, flexibility, and efficiency are key. For years, C# plugins have been the go-to solution for extending Dataverse functionality, offering unparalleled power and control to developers. However, with the rise of low-code platforms, a new generation of Dataverse plugins is emerging, designed to empower citizen developers and streamline development.

But how do these low-code plugins measure up against their tried-and-true C# counterparts? In this post, we'll dive deep into a performance comparison between traditional C# plugins and the new low-code approach, uncovering the strengths, limitations, and practical use cases of each.


Setting the expectations

There are a lot of really interesting articles out there describing the internals of Low Code plugins. One key point is they are built on top of old C# plugins, the infrastructure down below it's the same, so it's a reasonable expectation that

Performance(Low Code Plugin) = Performance(C# Plugin) + overhead
Enter fullscreen mode Exit fullscreen mode

Where the overhead may be generated by:

  • Language parsing latency
  • Sub-optimal command execution strategies

The second bullet is releted to what Richard Anderson stated in his article:

Performance
While basic operations on the same function are relatively performant, Power Fx expressions that utilise a Dataverse connection to interact with other entities are not. Operations such as LookUp, CountIf, SumIf, etc. utilising a Dataverse connection with a condition inefficiently retrieve records and loop over the results, rather than letting Dataverse query out the ineligible records.

Power Fx low-code functions also complete another submission back to Dataverse to update or create data where configured, instead of using the target object to avoid the extra call, this could be concern for anyone on licenses where users are limited to the number of API operations per day.

That said, my intention here is to determine the size of this overhead, with basic, simple scenarios.


📐 Let's set up the benchmark

To perform a comparison i created 3 custom tables:

  • ava_test01
  • ava_test02
  • ava_test03

All of them with primary field called ava_name, and another custom field called ava_description. The test harness would be a console application built with BenchmarkDotNet, a really powerful library for this kind of scenarios.

BenchmarkDotNet

ava_test01 will be used as baseline for our benchmark. I'll run against it basic create operation, no plugins attached.

ava_test02 will have a plain old C# plugin attached doing some stuff.

ava_test03 will have a low code plugin attached doing the same stuff of the C# plugin, but in PowerFX.

I'm gonna make 2 different tests:

  1. In pre create, set the description of the newly created record
  2. In post create, *create a contact record *

Each test will then be run in 3 batches:

  1. First batch will measure 1 single create operation
  2. Second batch will measure 10 create operations executed sequentially
  3. Third batch will measure 100 create operations executed sequentially

Let's see how it behaves!


🧪 1st test - Setting the "Description" of the record in Pre Operation

This is the plain old plugin code that will run on ava_test02:

public class PreOperationPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

        var target = context.InputParameterOrDefault<Entity>("Target");
        target["ava_description"] = "Description added by PreOperationPlugin";
    }
}
Enter fullscreen mode Exit fullscreen mode

No overdesigned structures, no BasePlugin, just the bare minimum.

While the Automated Plugin of ava_test03 is:

Automated Plugin

Quite simple. Down below the benchmark results.

Benchmark results

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4169/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-1270P, 1 CPU, 16 logical and 12 physical cores
.NET SDK 8.0.400
  [Host]   : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2 [AttachedDebugger]
  .NET 8.0 : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2

Job=.NET 8.0  Runtime=.NET 8.0  
Enter fullscreen mode Exit fullscreen mode
Method RecordCount Mean Error StdDev Median Max Min Ratio RatioSD
NoPlugin 1 378.2 ms 8.08 ms 23.07 ms 380.0 ms 434.1 ms 330.6 ms 1.00 0.09
OldPlugin 1 372.5 ms 8.61 ms 23.99 ms 372.6 ms 432.5 ms 320.0 ms 0.99 0.09
PowerFXPlugin 1 380.0 ms 8.03 ms 22.12 ms 377.3 ms 452.2 ms 335.7 ms 1.01 0.08
NoPlugin 10 1,095.2 ms 26.17 ms 71.65 ms 1,087.1 ms 1,340.7 ms 975.7 ms 1.00 0.09
OldPlugin 10 1,250.6 ms 24.89 ms 65.56 ms 1,236.9 ms 1,431.0 ms 1,128.3 ms 1.15 0.09
PowerFXPlugin 10 1,449.3 ms 37.27 ms 106.94 ms 1,414.4 ms 1,757.8 ms 1,287.9 ms 1.33 0.13
NoPlugin 100 8,367.7 ms 167.33 ms 330.30 ms 8,340.6 ms 9,099.4 ms 7,641.4 ms 1.00 0.06
OldPlugin 100 10,054.3 ms 199.27 ms 581.27 ms 9,958.3 ms 11,442.1 ms 8,854.3 ms 1.20 0.08
PowerFXPlugin 100 11,888.0 ms 221.80 ms 477.45 ms 11,958.6 ms 12,926.1 ms 10,773.7 ms 1.42 0.08

The Ratio column in the table above shows the overhead of both plugins compared to the baseline execution without plugins. The presence of plugins accounts for a +20% (for the C# plugin) or +42% (for the PowerFX plugin) of the average execution time.

The computed overhead in the formula above can be calculated as 1.33/1.15 ≃ 1.42/1.20 ≃ 20%.

Let's try a slightly more complex scenario.


🧪 2nd test - Creating a contact in Post Operation

This is the plain old plugin code that will run on ava_test02:

public class PostOperationPlugin : IPlugin
{
    public void Execute(IServiceProvider serviceProvider)
    {
        var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
        var serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
        var service = serviceFactory.CreateOrganizationService(context.UserId);

        var clone = new Entity("contact");
        clone["firstname"] = "Luca";
        clone["lastname"] = "Gregori";
        service.Create(clone);

    }
}
Enter fullscreen mode Exit fullscreen mode

While the matching Automated Plugin looks like this:

Automated Plugin

Please note: To run this test properly I had to remove the previous plugin steps... but while you can simply disable a classic Plugin step, you cannot simply disable an Automated Plugin... I had to delete it physically 😒.
Who works with Power Platform in enterprise scenarios knows that physical deletion of solution components when you have ALM in place, and lots of customization in your solution, can be really painful...
The capability is still in preview, so I hope that in the final release there will be a capability to simply deactivate the automated plugin without having to delete it.

Benchmark results

BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4169/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-1270P, 1 CPU, 16 logical and 12 physical cores
.NET SDK 8.0.400
  [Host]   : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2 [AttachedDebugger]
  .NET 8.0 : .NET 8.0.8 (8.0.824.36612), X64 RyuJIT AVX2

Job=.NET 8.0  Runtime=.NET 8.0  
Enter fullscreen mode Exit fullscreen mode
Method RecordCount Mean Error StdDev Median Max Min Ratio RatioSD
NoPlugin 1 75.25 ms 1.457 ms 2.514 ms 74.58 ms 81.34 ms 71.08 ms 1.00 0.05
OldPlugin 1 219.48 ms 7.239 ms 19.817 ms 216.34 ms 279.94 ms 181.40 ms 2.92 0.28
PowerFXPlugin 1 272.77 ms 11.900 ms 34.524 ms 268.49 ms 365.67 ms 218.04 ms 3.63 0.47
NoPlugin 10 819.59 ms 29.180 ms 83.723 ms 788.89 ms 1,053.18 ms 729.54 ms 1.01 0.14
OldPlugin 10 2,189.04 ms 58.002 ms 169.194 ms 2,151.62 ms 2,639.01 ms 1,881.12 ms 2.70 0.32
PowerFXPlugin 10 2,562.66 ms 82.962 ms 239.364 ms 2,531.42 ms 3,196.66 ms 2,193.59 ms 3.16 0.41
NoPlugin 100 7,720.34 ms 152.514 ms 314.969 ms 7,729.09 ms 8,519.10 ms 7,110.76 ms 1.00 0.06
OldPlugin 100 21,934.29 ms 437.219 ms 1,136.390 ms 21,786.87 ms 24,703.31 ms 19,979.68 ms 2.85 0.19
PowerFXPlugin 100 29,148.69 ms 1,102.034 ms 3,108.306 ms 28,196.23 ms 37,090.00 ms 23,872.81 ms 3.78 0.43

First of all we should notice that the execution with 1 single record is quite meaningless, because it can be affected by transient external factors we don't have control on (it's quite stunning that the comparison between the first line of the two tests is really different, even if the operation performed in the "No Plugin" scenario is the same).

Let's compare again the Ratios in the different scenarios.

The computed overhead in the formula above can be calculated as 3.16/2.70 ≃ 2.85/3.78 ≃ 20% - 25%.


🎯 Conclusions

As expected, ease of use comes with a price: the PowerFX version is slower than the old C#, and the slowness can be accounted for a ⁓20% of the overall execution. A factor that may not be relevant in many scenarios, but must be carefully took into account while designing enterprise scale solutions.

One key aspect is that I've not included in the benchmark scenarios where the plugin should perform queries against the Dataverse. My guess is that, considering the limitations highlighted in Richard's article, the overhead in those cases may be higher.

That being said, performance isn't everything. Despite the lag, the new Automated Plugins powered by PowerFX are truly a game changer. What sets them apart is their ability to leverage Power Platform connectors, unlocking an entire ecosystem of data sources and services that traditional C# plugins hardly tap into. This feature alone is a killer advantage, allowing both citizen developers and seasoned pros to create powerful, integrated solutions with ease.

For citizen developers, it opens the door to automation without needing deep coding expertise. For pro developers, it accelerates prototyping and enhances integration capabilities, empowering them to focus on the bigger picture. In short, while you might trade off some performance, the flexibility of the PowerFX-powered approach can supercharge your solution-building process, making it a powerful tool in any developer's arsenal.

What do you think about it? Drop a comment below if you want to learn more about this topic!

Top comments (1)

Collapse
 
olasan profile image
Ola Sandness

Thanks for this investigation! Not surprisingly, there are some overhead, but putting a number to it is great!