C# component performance speed

Hello community!

I am coming to you with a question regarding the performance speed of c# components in grasshopper. I am creating a calculation-heavy component within the c# component in grasshopper and have stumbled upon something strange when profiling it; while the integrated GH profiler measures an average iteration time of approx 300-500 ms, when I profile the code within with stopwatch it comes out at about 50-100 ms, a drastic decrease. This measurement does not include the “additional code” space at the end of the c# component, where I mostly save certain data into memory. My question is what could be causing this difference in measured speed and could it be because of the last part of the code containing data saved in memory? Additionally, can I expect a performance increase by shifting the code into a custom-made component / plug-in in VS?

Thanks a lot for any help!

Best, Jack

The C# scripting component compiles your code on the fly. Some of its runtime is the time it takes to compile. If you run it a second or third time does it take as long?

There’s also some overhead involved with the component; it does things like converts the native GH object types to their basic equivalents (e.g. GH_Number => double), among other things. A compiled GHA component will have marginally less overhead but it’s rarely a big performance difference (other than the “compile-for-the-first-time” lag).

I am aware of the compiling issue but the performance difference is there in spite of that, the difference is approx the same through all iterations. Also regarding data types, except for data trees I almost exclusively use basic types, so there shouldnt be much time lost in that.

Hi,

this is really hard to answer. Nobody knows your code. I just recently fixed a performance bottleneck caused be accessing a small dictionary each iteration causing a slowdown of factor 15 (on 100k iterations). Optimise your algorithm, use basic datastructures, use the right Datastructure for your job (Arrays, Hashset, Queues, Trees … they all fullfil a special purpose). But its actually quite rare that you need to microoptimise your algorithm like this, usually the algorithm itself is weak.

Yes, I’m aware of the difficulty of blind guessing like this and dont expect any miracles. Basically what boggles my mind the most is the enormous difference between profiling via stopwatch vs the integrated gh profiling, which should suggest that there is someting else at work that is not a part of the bulk of the code? Or is that only my assumption due to lack of knowledge. Thanks for the help!

every time you run a c# script, the component does the following:

  • Gets the script instance, compiling on the fly if necessary
  • retrieves and passes out any compiler errors
  • populates the “out” parameter with any compiler errors
  • loops over the input parameters, getting their values (IGH_Goo types like GH_Number, GH_Boolean, etc.), casting them to lower-level values (e.g. double, bool) and assembling an object[]containing all inputs. Note that the types you work with in the function are not the types that grasshopper is dealing with — a DataTree<bool> you get inside the function has to be converted from a GH_Structure<GH_Boolean> .
  • invokes your compiled RunScript function via reflection, gather the results and populate the output variables

There will always be some overhead involved, even with a compiled component. You can probably expect it to be slightly less with a compiled component, but speaking from experience it is usually a barely noticeable difference between the c# scripting component and a .GHA.

2 Likes

GH profiler measures SolveInstance() and BeforeSolveInstance() and also it deals with the error menssages and computation sync (how many times should I run the comp if this param is list access but was feed with a tree…) so is not the same as measure a code inside RunScript() (or SolveInstance). And I can guess that the scripting components have more stuffs running inside, as the runtime compiler.

Another point to note that has not been mentioned is that the scripting components are heavier for reading and writing the document than the normal components, bc their code is flat text rather than compiled.

1 Like

the scripting components are heavier for reading and writing the document than the normal components, bc their code is flat text rather than compiled.

Unless I’m misunderstanding, I don’t think this is true… the code in C# scripting components gets compiled as well, it is not run as “flat text”
^ scratch that, I see what you mean now — it’s that they take up more space in the GH document because they’re serialized as text rather than as a reference to the assembly.

Yes, if time matters when opening the document, then it is an important thing (relatively, if you’re looking for maximum performance) to consider. For example using ShapeDiver, scripting components should be avoided for better loading of the model.

1 Like

Thanks everyone for the help! The answers got me thinking about the high number of inputs & amount of data the c# component is receiving and when trying to integrate the largest one into the code the speed has already increased by approx 30% so I suspect these are the big culprits. I’ll keep digging and hopefully I can tweak it even more. Thanks again!

Jack

Also, I have just remembered I came across this a while ago;

Tested now and definitely helps with large quantities of inputs.

3 Likes

RunScript is not called by Reflection. It’s called by the dynamically-compiled IGH_ScriptInstance.InvokeRunScript that would wrap parameters.

1 Like

Are you outputting long list outputs/lots of outputs relative to the amount of processing you are doing?

I have had a similar problem to what you describe, a c# script that ran for ~500ms in GH but with a C# stopwatch time of ~100ms, and worked out it must be what GH is doing in the output params of the C# script editor component that was slowing my component. For a test I made a new script that generated the same number and types of outputs randomly. This had a stopwatch time of 1-2ms but a ~400ms GH profiler time.

For me making a custom compiled component (.gha) did the trick and fixed this issue.