Rhino Python 3.10 - Please support it!

Still don’t know what your point is…

For users, since RhinoCommon’s API is available in both Python component, then performance should be similar, if not using any CPython libs.

If not, then something is not done right.

My point here is that McNeel invest a lot of effort in making the Python3 component, but still not working as good, and seems there’s a long way to go.

So, why not keep the IronPyhon part, and invest Cython externally?

Share the code as a minimal example and we can profile it.

The goal is that besides speed, which is probably a temporary issue, there are many more workflow related points that are important. It is a very good and long waited decision for Rhino to support CPython wanted, by many people for many years.

IronPython and Python both have ‘Python’ in their names but they are completely different animals. They both Implement the Python Language Specification (albeit different versions of this specification).

IronPython is written in C#, and runs on dotnet runtime. Python 3 (CPython) is written in C, is the reference implementation, and is by far the most popular. There are also other Python implementations e.g. PyPy

Here is what this means for us as Rhino users:

.NET Interop and Python Performance

When dealing with dotnet types e.g. RhinoCommon functionality, IronPython is incredibly fast. Almost as fast as C# since it is running on the same runtime and compiles the python code into the intermediate representation used by dotnet. So an int or a List[int] in IronPython is virtually the same as int and List<int> in C# respectively. When you add two Point3ds together in IronPython, the addition is incredibly fast since it knows how to handle the operator overloads defined on the Point3d type in RhinoCommon.

Python 3 (py3 hereon) on the other hand is written in C, and uses the Unmanaged memory. This means it is completely outside of the dotnet runtime. So an int or List[int] in python 3, do not directly translate to same data types in C#, and a conversion is necessary.

Take this class and method for example:

class Base
{
  public int Foo(out Brep brep);
}

Consider this python code that uses the class above:

b = Base()
r, brep = b.Foo()

When IronPython parses this code, it creates an explicit call to Base.Foo. This means when the code is running, IronPython does not need to spend a lot of time looking up where Foo is. When it wants to pass the returned int from this method to the rest of python code, it does not need to do any conversion. It is the same int.

When Python 3 parses this code, it needs to wrap the Base type in a dynamically generated py3 type with the same name. Otherwise the python runtime would not be able to understand what Base is (since it is in dotnet memory space). This wrapper type effectively translates between py3 runtime and dotnet runtime.

Now when b.Foo() is called, py3 needs to look at all the members of the Base class to find all the many overloads of Foo(). Then it needs to look at all the input arguments, look at the type of each and perform a complicated distance computation to find which overload of Foo() is most appropriate for the given arguments. Furthermore, it also needs to Box all the out and ref arguments and generate a tuple on the return containing their captured values. It also needs to convert all these dotnet values into equivalent py3 values so the rest of the script can use them. This takes time; Much longer than what IronPython does because of this double-runtime paradigm.

So if you are adding 1000 points and vectors together in py3, the script is going to be much slower than IronPython.This is obviously something that we will keep improving but there are technical-architectural limitations here. But py3 does:

  • support all the scientific c-python packages
  • support package management
  • have better auto-completion and linting
  • offer modern syntax

Both Python 3 and IronPython 2 are available in Rhino and Grasshopper. Choose the one that makes the most sense for the specific task you are trying to achieve.

Super clear description, thank you.

Do you think there are any plans to bind Rhino C++ to CPython, instead of going through .NET RhinoCommon? After reading your description it looks like this is the main Rhino Architecture limitation.

We’ve talked about it. It is possible, but would also make documentation and support pretty confusing.

Thanks :slight_smile:

Probably then the best is to use as little as possible of RhinoCommon when cpython is used. Never thought about this, good to.know.

Very clear explanation, thank you very much.
This resolves a lot of confusions now.

So to summarize here for new comers for heavy data computation:

  1. If you want to use any of the RhinoCommon class or rhinoscriptsyntax, go with IronPython or C#.
  2. If you want to use Python3, try to avoid the above two usage.
  3. Even better, if the data is really heavy and you need parallel computing or fancy stuff, go outside Rhino and import data back – this will avoid a lot of crashes of Rhino.

It might also be relevant to elaborate on how Python.NET fits into all this on the CPython side of things. I assume this is still what is being used under the hood to interop with .NET?

Edit: On a sidenote, specifically when implementing RhinoCommon/.NET, GhPython is still the most performant Python choice from the last couple of real world tests I’ve done (i.e. where I port a GhPython component to the new IronPython one-to-one). So there’s that.

Does Grasshopper and Rhino Python are different or not? I am wondering if Grasshopper .NET interface has overhead on its own?

That probably depends on what you’re referring to. But there certainly are costs associated with inputting/outputting/casting, but those can be largely negated (at least in GhPython, but I hope those tips also apply for the new editor).

This thread is so interesting and answers some of the questions I had for some time now.
Thanks @eirannejad and @stevebaer for being so honest.

I’m not gonna lie, it’s a bummer that Python3 is slower when dealing with RhinoCommon and thus rhinoscriptsyntax. It’s nice to be able to finally use some external Python3 packages, but this than translating to a performance penality vis-à-vis to IronPython, when geometry comes into play - in a CAD application nonetheless - is a little disappointing.

From my perspective, I like Python for prototyping and C++ for performance and flexibility. C# doesn’t make sense for me to learn.

I’ve made my peace with eventually always having to implement my performance needy geometry experiments in C++ outside of Rhino.
In my opinion, even if one day everything gets optimised to the max, then people will notice that the GUI is still a performance downer, and they’ll end up where I am now. :smiling_face_with_sunglasses:

Python3 comes in at 1.0s vs 259ms for IronPython for the same code from my latest Corona-19 isolation experiment. That’s what a 74.1% performance increase/decrease?

Just out of curiosity/adding data points, how does GhPython perform with the same code?

Do you mean using the Grasshopper Python API, instead of RhinoCommon, or the legacy GHPython component?

I meant running the same code in the legacy GhPython component (also adjusting for input/output cost, if any).

Here you go:

Exactly the same code, the same inputs and outputs, and internal printing turned of.

Wow…

@stevebaer

How impossible would it be to start an open-spource CPython ‘RhinoCommon-like’ project with contributors from outside McNeel? At least to exchange data from Rhino C++ to CPython.

I am quite sure you did pybind11 and nanobind wrappers. It would be good to restart this discussion. I guess MAC C++ SDK is one the key building blocks that is missing but will be available one day.

Exactly what I meant here:

Exactly what I did for IG-Mesh

Though the issue is you cannot support macOS unless you have a Mac Machine…

DIFFERENT TOOLS FOR DIFFERENT DEMANDS

I’m not into Python, I use C#, but I simply don’t understand the thinking here. Large projects is my thing, too, and so I use Visual Studio to keep “big things” together.

But I use the script component all the time, for experimenting, testing ideas, proof of concept, to actually test if an algorithm does what I want it to do and…

All the time. So I use Script Component for almost every major concept. Which then ends up in the “big thing” project in Visual Studio, with only minor changes.

This means, for example, no wasting time for the “turn around” of starting the “big thing”, the whole project, from MS Visual Studio starting Rhino 6 Grasshopper etc.

What I’m saying is that the Script component(s) is absolutely essential - even if doing “big stuff” in other tools more suitable for managing big code bases. A “lab deck” of sorts.

I even use MDriven Framework (former Bold/Eco MDA framework) as the C# backend for my Rhino/Gh projects (and that’s “heavy duty” stuff, I can tell), and still, the Script Component(s) saves me probably hours a day, not having to mess around with a full fledged “production plant/facility” when I only need a hammer and a chisel.

Different tools for different demands.

This fact isn’t in conflict with the need for good support for “big projects”, so the Script Components serves us all very well, when used where it best does it’s thing.

//Rolf

Not impossible at all. Open source or closed source doesn’t really matter; someone still needs to type on the project. We already have rhino3dm which is open source and uses nanobind. That project could be retargeted to use the Rhino C++ SDK as a starting point.