C# variables vs fields, output keeps growing at each iterations

Hey !

I’m forcing myself to C# but I’m a fairly decent noob, beware !

I’d like to do something like this:

  private void RunScript(List<int> x, ref object A)
  {
    for(int i = 0; i < x.Count - 1 ; i++){
      if (Math.Abs(x[i] - x[i + 1]) < 2){
        b++;
      }
      else{
        A.Add(b);  
      }
    }
  }

  // <Custom additional code> 
  int b = 1;
  // </Custom additional code> 
}

But A isn’t of type List… So component screams that I cannot use “Add” on type “object” (fair enough).
So I tried to use an intermediate B list and assign it to A later… :

  private void RunScript(List<int> x, ref object A)
  {
    for(int i = 0; i < x.Count - 1 ; i++){
      if (Math.Abs(x[i] - x[i + 1]) < 2){
        b++;
      }
      else{
        B.Add(b);  
      }
    }
    A = B;
  }

  // <Custom additional code> 
  List<int> B = new List<int>();
  int b = 1;
  // </Custom additional code> 
}

Terrible idea !
Everytime I recompute Grasshopper, the output A of the c# component keeps growing (the list basically duplicates itself at each recompute).

I feel like I’m not getting the “ref object A” correctly, but it seems I can’t figure this out… Anyone spotted the problem?

Cheers,

Anything between “Custom additional code” is preserved during the entire solution (like “sticky” in Python). You need to declare B inside RunScript as a local variable.

Oops
Therefore I also need b in RunScript in this case !

Thank you very much for the quick answer and description ! :slight_smile:

See attached (and for the shake of it … add … er … things)

Lists_Numbers_V1.gh (9.9 KB)

Thanks for the example, learned couple of things !

Ended up like this

  private void RunScript(List<int> x, ref object A)
  {
    List<int> B = new List<int>();
    DoSomething(x, B);
    
    A = B;
  }

  // <Custom additional code> 
  public List<int> DoSomething(List<int> aList,List<int> newList){
    // Find rich girl ASAP
    
    int counter = 1;
    for(int i = 1; i < aList.Count ; i++){
      if (Math.Abs(aList[i - 1] - aList[i]) < 2) counter++;
      else{
        newList.Add(counter);
        counter = 1;
      }
    }
    return newList;
  }

Although I have a quick (delusional amateur) question since my hope of speed are being shaken right now…
What is this? :melting_face:


image

Is it a (not) compiled thing? Meaning it would go faster if compiled like a “true” component? I shall test that !
Or is it more specific to the algorithm inside?

crushed_hopes.gh (13.2 KB)

iirc C# and Python components handle list outputs differently, which is an issue if the output is huge. I re-implemented your code in a native component which is much faster.

1 Like

I see !

Thanks for the explanations :slight_smile:

On macOS, things seem to work differently?

Writing the Python code a little more prudently, shaves off more than half of the processing time. And I’m sure more optimizations are still possible.

def evaluate(t):
    b = 1
    diff = t[0] - t[1]
    if diff < 2 and diff > -2:
        b += 1
    return b

a = list(map(evaluate, zip(x[:-1], x[1:])))

Most of the performance is lost by funnelling the processed data through the output. There must be a lot of shenanigans (type casting, etc.) going on there, making it such a performance drain?

Within the component the Python code takes only 23.7350ms to be run and even way less in a terminal with CPython.

Screenshot 2022-08-05 at 11.25.25

1 Like

If you want speed, sell the Toyota and get the proper thing (a good life insurance is a must, mind).

1 Like

Won’t the insurance policy get invalidated because you’re engaged in dangerous activities?

Was not my fault [at all] officer: some Aliens shoot the road with a laser … blah, blah.

Moral: sell the Toyota (ASAP).

1 Like

Here are the times for same data set and Python code as above run in a terminal five times:

0.00125ms
0.00054ms
0.00033ms
0.00025ms
0.00021ms

There’s something to be said for having no overhead.

2 Likes

LOL, here is an updated version of my code that actually does what yours does. :wink:

def evaluate(x):
    global b
    for d in (a-b for a, b in zip(x[:-1], x[1:])):
        if d < 2 and d > -2:
            b += 1
            continue
        bb = b
        b = 1
        yield bb


if __name__ == "__main__":
    b = 1
    a = evaluate(x)

It runs in about 120ms on my computer, so still considerable faster than your first version.

1 Like

This thread is incredible hard to follow due to incredible bad naming. I know it’s already solved. (Having an issue because of mixing local and class global variables (=fields)).

But it took me 10 minutes to even figure out what you are actually trying to do (better).
There are naming conventions for a very good reason! But even with violating them, at least proper naming of members is helpful. Humans are not robots, so unless we obfuscate on purpose, it is terrible practice to name variables “B” or “bb” or “a, b, c, d” and functions after “DoSomething”, “evaluate”. And even if there is this 1% rare case where this would make sense, then please put a comment somewhere explaining in 3 sentences what this is all about. You can find so many bugs just by figuring out a logical error by reading a proper sentence. Without having readable code, it makes no sense to optimize for performance, especially if you violate ‘Single-Responsiblity’ all over the place.

You’re right, I renamed it (although a noob like me would have probably typed what I initially named it as)

The original post had nothing to do with code optimization, but simply where variables had to be declared in grasshopper 's c# component. Although I totally agree I should have added a note on the OP to explain what the code was doing.

The second question (I didn’t know would spark much interest) was simply about python vs c# vs native c# speeds. So no one asked for optimization

Naming wasn’t really relevant, and since I made this as part of an answer for another forum post, I didn’t spend much time in the code, but explained outside of it. Especially because the script was part of a much bigger definition. The naming convention I used mainly came from the default variable names Grasshopper gives us when we instantiate a c# component :melting_face:

For the “DoSomething”, it’s a wink to Peter’s code but once again, not relevant for this topic IMO.

Differences between local variables and class members, and why C# component is slow at outputting a large list?

1 Like

Don’t worry, I was just meant this in a more general way, not addressing you here.

The reason why performance is not comparable from Iron(!)Python - and C# script component is manifold. This example shows again that it’s important to measure the right thing! If you use the same algorithm, encapsulate it in one function, measuring only that one function (more than once, SRP), the performance would be quite similar. This is because it likely creates the same IL code.

What you actually measure with the GH Profiler is more than this, and of course as already mentioned, the difference is for the most part up to the ‘Input’ and ‘Output’ of the data and on-the-fly compiling. There are tricks to bypass this in a C# script component. E.g. you can feed in as one object, casting it to the right collection. This prevents that the data is actually copied (for safety and mapping reasons).

1 Like

No, the initial question was why the list is increasing in size for each computation. Of course, if you accidentally cache the list, by not using a local variable…

Yes, I think I’ve seen a post addressing that recently.
Interesting, but seems out of reach for me for now, and overkill for this component ! Thanks for the explanation :slight_smile: