Rhino.Geometry.Mesh.Split not threadsafe?

Hi @dale,

is Rhino.Geometry.Mesh.Split a threadsave method ? I have one target mesh which i want to split with other invidual meshes. For each split operation i create a duplicate of the target mesh, then do this simple threaded split:

def MySplitMeshWorker(job):
    rc = Rhino.Geometry.Mesh.Split(job.TargetMesh, [job.CutterMesh])
    job.MeshPieces = rc
        
tasks.Parallel.ForEach(split_job_items, MySplitMeshWorker)

if i run this with 10 jobs, only 6 succeed. If i run it again on the failed 4 ones, 2 work and 2 fail. Splitting individually all works so i asume there must be something wrong.

_
c.

Hi @clement,

I don’t know if Mesh.Split is thread safe or not. I do find it odd that you using the class method as if it were a static function:

rc = Rhino.Geometry.Mesh.Split(job.TargetMesh, [job.CutterMesh])

vs:

rc = job.TargetMesh.Split([job.CutterMesh])

I guess having an example to run here might be helpful?

Thanks,

– Dale

Both of those versions are syntactically the same in python. Usually our autocomplete makes it easier to end up with the first form.

1 Like

Hi @clement,

Mesh.Split in Rhino 7 WIP is thread safe. Please note that there are better and more powerful overloads in Rhino WIP than the ones available on the online help for Rhino 6. Especially, we put a lot of thoughts into tolerance usage. See our new mesh intersection milestones page.

This is probably self-explanatory, but the fact that MeshSplit is thread safe does not mean that you can modify the mesh and at the same time split it, but it means that you can call various splits at the same time if the mesh in unchanged. Please be aware of memory usage piling up, especially if calling this in parallel on large meshes with large splitters.

Hi @all, thank you for all the answers.

@dale and @stevebaer , i’ve tried to use the static method as well as the instance method using Rhino 6 (it sometimes fails when threaded but does not fail when individual splits are done with only 1-2 jobs).

@piac, this is for Rhino 6. Of course i do not modify the target mesh while splitting, (i wrote above that i make duplicates of the target for every split job). Each job is done with one individual cutter.

I’ve been trying to make a copy of the Rhino.Geometry.Mesh.Split method using copy.copy or copy.deepcopy but it does not allow to make a class instance from the method itself. It seems that when used like this:

rc = job.TargetMesh.Split(job.CutterMesh)

it is always the same method which is called. (maybe this is what @stevebaer referred to ?).

@dale, here is an example script and scene, the green mesh is the target, the purple ones are the cutters. In the end i am interested in the circular meshes obtained by the split operation. Btw. in this simple example it works, but if the meshes are more complex like the ones i am not allowed to show in public, it fails.

TestSplit.3dm (172.5 KB)
TestSplit.py (1.2 KB)

I can provide the real meshes via PM if you think there is something i am doing wrong.
_
c.

Hi Clement; I asked, and it seems that in Rhino 6 and previous, Mesh.Split is not guaranteed to be thread safe.
There is no difference in calling the method via instance or like a function.

I hope this information helps, at least a little bit.

EDIT: I still reported RH-55950. There is a discussion to maybe add a lock or provide some fixes in an upcoming service release for Rhino 6 code. Rhino 7 code is inherently state-independent.

1 Like

Thank you Giulio, i keep my fingers crossed so this may get improved for V6.

_
c.

I haven’t been able to reproduce the failure with the models that you attached. Maybe the model is too simple or my computer is too weak.

Hi @stevebaer, i can provide an example model via PM if you like.

_
c.

Probably best; thanks

I am seeing the problem that you are seeing, but there is nothing obvious jumping out at me where the threading issue is located. It does appear to be a threading issue because if I throw a lock on the top level Mesh.Split call everything works. I’ll keep this on my bug list for the time being, but I’m still not sure what can really be done in V6 other than just forcing the entire function to work single threaded via a lock.

I also verified that this does work in V7 as that is completely new code.

Hi @stevebaer, thanks for testing.

that will not make it any faster in V6 i guess (which is why i tried to use threads)
Is Mesh splitting (threaded or not) faster in V7 compared to V6 ?
_
c.

Correct, I would essentially be forcing the entire routine to run as if it were single threaded no matter what.

@piac would know more about this than me. He has been spending a lot of time lately in this area (low level mesh intersection code).

Hi @clement,

thanks for sending your comments. The focus in the new code is reliability; and there is a lot of work to do already just there. :slight_smile:

So, really, the goal now is not to demo how much faster this new code is – a better aim is fixing all bugs of the previous implementation, or at least all the ones that really matter for everyday usage.

While overall performance is very important, there is likely space for optimizations. Again, optimizations are a secondary goal at present. I still took some time to test your model. Here the results. You can test with the same code in today’s WIP.

1. Splitting the mesh with all cutters at the same time, all at once, via command:
V6: 0:00:05.914000
V7 WIP: 0:00:04.312000
Better: V7 took 27% less time.

Tested with this script:
testMeshSplitCommand.py (271 Bytes)

2. In the script you wrote, I added a simple timer and this line:
options.MaxDegreeOfParallelism = 1

With this (negative) change, we can compare the old code and the new one, because at present the old code does not work in parallel.
This is what I get:
V6: 0:00:07.640000
V7 WIP: 0:00:08.214000
Worse: V6 took 7% less time.

3. If we turn on multithreading in your script, we get this:
options.MaxDegreeOfParallelism = -1
V6: 0:00:01.691000 (only 5 out of 11 pieces were split), which would put all splits at 0:00:03.7202.
V7: 0:00:02.190000 (all 11/11 split)
Better: V7 took 41% less time than what it seems V6 would have taken to reach the same result, but this is not really an apples-to-apples comparison.

This is your test script, modified as mentioned:
testMeshSplitLoopNoParallel.py (1.4 KB)

Thanks,

Giulio


Giulio Piacentino
for Robert McNeel & Associates
giulio@mcneel.com

1 Like

Thank you Giulio for taking the time to make those measurements. Imho these are good news if all splits work and the method is threadable. I am looking forward to V7.

_
c.

1 Like

I never said this :slight_smile: The new algorithm seems to be giving much better results, though! :muscle:

There are still two big features to be added that will likely require tweaking and long testing in the WIP – and the first one will be introduced next week: support for cross-intersecting perforations on a single face. This seems to happen a lot in the wild.

1 Like

The new WIP that will come out this Tuesday, here, takes:

1. 0:00:03.355000 for the command
(down from V7 WIP of last week at 0:00:04.312000)

3. The second parallel loop was also brought down to:
0:00:01.575000
(down from 0:00:02.190000). This makes it faster than the non-completing V6 version.

This is a result of a complementary different logic for some types of faces, not a result of optimizations.

1 Like