Grasshopper Performance - Scripting Slower Than Native Components?

Hi everyone!

I often build definitions where I’m remapping geometry between two meshes of equivalent topology. I’ll often be passing 100k points through Mesh Closest Point and Mesh Eval in native Grasshopper. I’ve been looking at ways to speed this process up. I thought that scripting these functions through RhinoCommon in Python or C#, especially once multi-threaded would have speeded things up. Though I’m finding that stock Grasshopper components perform faster.

Here are my results for remapping 10k points on a six core CPU:

  • Stock GH components: 75ms (adding the times for all components, none are multithreaded)
  • Python single thread: 360ms
  • Python multi thread through ghpythonlib.parallel: 150ms
  • C# single thread: 265ms
  • C# multi-thread through Parallel.For(): 130ms

While multi-threading is providing a nice speed boost, I’m really surprised GH components still compute faster for this test scenario.

I was hoping to hear thoughts from other users:

  • Any thoughts on how to improve the compute speed for this operation?
  • Is something very wrong with the way I wrote my scripts that is slowing them?
  • Is there a way to profile a script to see where the time is spent?
  • Would a compiled Visual Studio component perform significantly faster?
  • Do you usually see a performance increase by replacing components with scripts?

I’ve attached my test file with the scripts attached. The output isn’t especially useful as I isolated a portion I repeat in a lot of scripts.

Mesh Point Remap - multiThreadTest-minimumViableFile.gh (1.6 MB)

The bottleneck of script components is the data piping (What goes in, what comes out). Its is more complicated and slower, but it makes scripting easier for casual users. You can bypass this like shown in the link below. Furthermore, parallel programming is not always faster. Especially if you don’t correctly identify the bottleneck.

Writing a custom component will always be faster without any hacks. But as I said, you can improve scripts by improving data input and output by doing the following:

See this: Evaluate vs smaller than vs C# component speed difference - #3 by TomTom

1 Like

So in your case applying this already improves a lot (not even the strongest possible improvement):

Mesh Point Remap - multiThreadTest-minimumViableFile_re.gh (1.6 MB)

Edit:
You can improve syntax an performance slightly if you use ParallelLinq:

using System;
using System.Collections;
using System.Collections.Generic;

using Rhino;
using Rhino.Geometry;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;

private void RunScript(List<System.Object> points, Mesh mesh, Mesh mesh2, ref object A)
{
    A = points.AsParallel().AsOrdered().Select(item => {
      Point3d pt = (Point3d) item;
      MeshPoint closestPoint = mesh.ClosestMeshPoint(pt, 0);
      Point3d relocatedPoint = pt + mesh2.PointAt(closestPoint);
      return new GH_Point(-closestPoint.Point + relocatedPoint);
      });
  }

Edit2:

On very large data, it also makes sense to split the mesh and the points involved into smaller pieces. If you filter out before even computing anything this is likely improving performance, assuming that this is feasable.

2 Likes

Well … If the goal is to “map” GeometryBase things (points in this case) “from Box to Box” … I can hardly justify the approach used.

See Elapsed (no // for the moment , using - on purpose - an ancient I5) for 2000*4 = 8K rnd points on equivalent “from” Box sides (red) “to” Box sides (cyan). Is reasonably to expect ~1ms for an I9 K (and maybe a proper thread safe // thingy).

1 Like

Of course, depending on what you are doing, the algorithm is usually the largest bottleneck. But in this case (and many others) it’s rather the processing of the input and output and not necessarily what’s inside the script. So if you only measure ‘your’ code (using the Stopwatch), you probably miss the real bottleneck.

In any case, if you have less than 10000 points it does not matter at all, because having 300 ms or 3 ms is potentially the “same” amount of time for a human being. Things get really interesting at 1 million points but even if you wait for 5 minutes… I mean job done is job done, and you’ll have a chance to drink a coffee/ouzo in between. So that’s not that bad at all. But this is rather a philosophical debate…

In the end keeping things simple is also important and wasting hours on improving some ms is only worth the time, if you have fun with this sort of work.

Furthermore, having 1 million objects involved, and still being able to produce this cost effective is a very rare edge-case. The largest real world project I had, involved 200000 curves, and it failed to be produced, because of the amount of complexity involved…

No it’s not: Imagine some real-life “parametric” AEC thingy where, say, 10 or more C#'s are involved. The outcome could be most propably Coordinate Systems and Instance Definitions for the big AEC BIM boys (say AECOSim). All that before FEA/BID cost analysis and the likes. Or … if you want to stay in Planet Utopia just try to solve a Hamiltonian path in a complex Graph (of Mesh as Graph).

Now … 300*10++ ms … well does not sound “interactive” to me. In fact is a good reason to abandon the concept/idea (anyone can have an idea meaning that having the forest aspect of things in mind is a nothing thing)

But as you said this is rather a philosophical debate…

Other than that how’s things going? Are you still in the automotive sector? Did you manage to get a proper Ducati?

Specifically for GHPython, you can usually achieve considerable speed boosts on large inputs by not using type hints or the implicit Grasshopper cycle functionality (i.e. set the access type to List if you’re operating on a list and implement the loop yourself), and on large outputs by wrapping the data in appropriate Grasshopper types:


Mesh Point Remap - multiThreadTest-minimumViableFile_GHPythonFixes_00.gh (1.6 MB)

See this old thread for further context:

4 Likes

Thanks for chiming in with some great info and examples!

Definitely some great lessons learned about the GH data types and list access.

@PeterFotiadis the mesh box was just a simple test case. The real use case for this are more complicated meshes.

Well … have in mind that // IS NOT what most people believe (nor is the blue pill - as Tom stated rather clearly) . I would strongly advise to spend some time reading what the Master has to say on that matter (having in mind that a “smart” solution beats a “wrong” // in quite a few cases):

http://www.albahari.com/threading/

I skipped the Ducati and directly bought a fighter jet :wink: But I cannot use that one currently. Still in the automotive, but not doing CAD related stuff currently, which is good and bad in the same place :wink:

I mean if you can speed things up with little changes there is nothing speaking against it. My point was rather that low performance is better than no performance. So if you get the job done in almost “real-time” than this is perfect, but even if you need 1 hour, it will still better than doing any tedious manual work…

1 Like

WOW that’s ultra cool. Don’t forget the mandatory Vette (go for the original - the immortal classic [I want, I want, I want]).

finally … someone with taste :wink: :slight_smile: But, you’ll need a black uniform for this beauty. That biobin-green sucks.!