Evaluate vs smaller than vs C# component speed difference

HI,

I noticed there’s some differences in profiling time when it comes to using the Evaluate component vs the c# component to generate a list of true/false booleans values. See screenshot attached.

I also included a smaller than component to test and this is surprisingly even faster, can anyone elaborate as to why this is?

Side note: they all output 13824 values, the list is still small, but I’d imagine the compute time to increase exponentially when working with larger/longer lists.

The difference is in the processing of the inputs, the smaller than has a quick cast to bool (or maybe input data is already bool), C# scripting inputs are a bit special, it has a slower casting I guess, and Evaluate has a parser to convert text to operations so is expected to be much slower and also a generic input which requires casting.

3 Likes

A C# script component copies value-types data into a new list with wrapped GH_Types (before and after processing). There are ways in optimizing this. This is the major bottleneck. The next is the fact that it’s a script, which needs to be compiled. The first execution after changing the text will always be slower. So always compute a second time!

Furthermore there is something called branchless programming. Don’t use if and else if not really necessary.

FastCondition.gh (11.8 KB)

7 Likes

private void RunScript(List<System.Object> x, ref object A)
  {

    var comp = GrasshopperDocument.Objects.First(c => c.NickName == "Random X1") as GH_Component;
    var data = ((GH_Structure<IGH_Goo>) comp.Params.Output[0].VolatileData)[0];

    var valTrue = new GH_Boolean(true);
    var valFalse = new GH_Boolean(false);

    var list = new List<GH_Boolean>(data.Count);
    var cnt = data.Count;

    for(var i = 0;i < cnt;i++)
    {
      var num = ((GH_Number) data[i]).Value;
      if(num < 10.0)
      {
        list.Add(valTrue);
      }else{
        list.Add(valFalse);
      }
    }

    A = list;
  }
6 Likes

Great, you took pressure from memory allocation and the GC by reusing a GH_Boolean. Of course that makes sense!

This is fantastic! There is obviously a lot of understanding here that I didn’t get from doing C# courses. Could you give a novice some pointers about how he might go about learning to code like that?

Can you explain what the first two codes mean?

直接获取电池输出的原始数据。避免 C# 电池对输入进行的转换和重新成列表(由于是引用类型到值类型的转换非常耗时)。不过这个算奇技淫巧,可移植性很差。

To retrieve the output directly and avoid marshalling cost from the C# component. However it’s merely a trick and doesn’t have good portability.

2 Likes

如果得到数据后还需要使用RhinoCommon库里面的函数继续处理,请问该如何转换数据类型,我真是搞的一头雾水 :joy:

GH_xxxx.Value; e.g.

GH_Brep a;
var rhinoBrep = a.Value;

When I really want to squeeze everything out of the code then I tend to embed lists in objects (classes defined in a dll) shared by both ends. Then the profiler time goes down to less than 1 ms (= time not even visible):

My computer is quite slow, but

  • the uppermost component is a for-loop, identical to @gankeyu’s, and
  • the lowest component is the “linq-version”, also by @gankeyu.

Both of these show no time (< 1 ms) if not sending the list to the output. So the very output assignment can be optimized. And so I did.

  • The middle component sends its list (209304 booleans) but it takes less than 1 ms (no profiler time visible, see blue markers)

The list is simply embedded in an object only containing (“embedding”) the list, that’s all. So I pack it and unpack the list. that’s all (see code in text panels).

The dll content of the referenced RILGH_Marshal.dll looks like so:

image

Huge lists can be passed between components in no time. Passing lists is expensive. Very expensive.

FEATURE REQUEST

For debugging reasons I would of course also have an output with “native” lists/types but for this I would like the outputs to simply skip assigning the values to them if no wire is connected to the output!

(I use to code such “output awareness” myself, but it really should be inbuilt in the component ports and updated on connect/disconnect, now I can detect only after refreshing the GH definition = bad :frowning: ).

// Rolf

Attachments:
GH Performance examples.gh (16.5 KB)
RILGH_Marshal.dll.zip (1.7 KB)

2 Likes

I won’t use GH_* if you are passing wrapped list since they are reference types and will put extra pressure on conversion and GC.

2 Likes

Hi Rolf,

that LINQ expression was part of my solution, but anyways.:face_with_monocle::slightly_smiling_face: I wasn’t even happy with that solution, because it breaks too much of the flow of Grasshopper and is barely readable.

If you come up with more than 5 lines for a statement which should have been 1 line, then I’m not so sure if this is already a bad tradeoff. Especially if we speak about 50 ms on lower performing laptops…

In reality I would also question the need for a dataset of 200k+ items .

In the end, all that could be required, but my point here would then be, that Grasshopper itself is the bottleneck and should be optimized out. If you think of, and understand these types of optimizations, I’m pretty sure you can write large parts of that algorithm into one single library and achieve even higher performance and better readability!

Coming back to the initial question, the reason why simple if-statements are that costly in Grasshopper is the data-piping of Grasshopper. This was my initial intention to show here. In practice I was never in need to apply this knowledge within a definition.

Still a very interesting and unusual approach!

1 Like

Ops, sorry for that! I just looked at that one Gh definition below and thought the one above was from the same source. Sloppy of me, :blush:

Agreed.

Absolutely.

However, there’s also the benefit in building solutions step for step (component for component) and do experiments with the part-results (between components), for which Grasshopper is a perfect (lab deck) tool. In my case I often transform very heavy meshes, meaning I often have hundreds of thousands of elements (vertices and/or faces) to shuffle through different processes (which are best achieved with fully coded algorithms), but debugging and experimenting with the part-results is an essential part of the development workflow spotting “corner cases” on complex meshes. So speed can matter also during the development stage, and some process steps are generic and can be reused etc.

In any case, I learn so much by studying coding strategies and different approaches. This forum really is something special! Thanks to all of you!

// Rolf

1 Like

Try benchmarking again with a [int param]or [number param] between the panel and the smaller component.

this will precast the STRING (“10”) into a NUMBER 10 only once and not every time the component runs (209000 times)

I heard David Rutten mention at RhinoWorld that GH2 will be able to use large lists of value types (int, bool, double, etc) directly without being wrapped into GH_Boolean etc, avoiding a lot of the overhead. The overhead will be created if you attach metadata to each item in those lists though.

1 Like