[python] Usefulness of Multi-Threading

Hi @stevebaer,

Regarding this article.

When is it really useful?

I am asking because I have a case where I’m tring to cut about 40 000 breps. And the difference is just 1 second between serial and prallel.

It is possible that I’m not using it correctly :stuck_out_tongue_winking_eye:

here’s my code (I cannot provide the 3d model):

###################
### trim by box ###
###################
import System
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino
import time
import math
#import uuid
import System.Threading.Tasks as tasks

 
tol = sc.doc.ModelAbsoluteTolerance

def TST(cutter_brep_obj=None,to_be_trimmed=None,parallel=False):
    if cutter_brep_obj == None:
        cutter_brep_obj_id = rs.GetObject("select cutting box: ",rs.filter.polysurface)
        cutter_brep_obj = rs.coercebrep(cutter_brep_obj_id)
        rs.AddLayer(rs.ObjectName(cutter_brep_obj_id))
        if cutter_brep_obj == None:return
    if to_be_trimmed == None:
        to_be_trimmed_ids = rs.GetObjects("select objects to be trimmed: ",rs.filter.surface|rs.filter.polysurface)
        to_be_trimmed = [rs.coercebrep(id) for id in to_be_trimmed_ids if id is not None]
        if to_be_trimmed == None:return
    
    
    
    
    rs.EnableRedraw(False)
    
    #if cutter_brep_obj == None or to_be_trimmed == None: return
    
    iterations_count = len(to_be_trimmed)
    
    def foo(i):
        if cutter_brep_obj.Faces.Count > 1:
            #print obj.Trim.Overloads[Rhino.Geometry.Brep,System.Double](cutter_brep_obj,tol)
            trimmed = to_be_trimmed[i].Trim.Overloads[Rhino.Geometry.Brep,System.Double](cutter_brep_obj,tol)
            for brep in trimmed:
                trimmed_brep_id = sc.doc.Objects.AddBrep(brep)
                #rs.MatchObjectAttributes(trimmed_brep_id,to_be_trimmed_ids[i])
                #rs.ObjectLayer(trimmed_brep_id,rs.ObjectName(cutter_brep_obj_id))
    if parallel:
        tasks.Parallel.ForEach(xrange(iterations_count), foo)
    else:
        for i in range(iterations_count):
            foo(i)
    
    #rs.DeleteObjects(to_be_trimmed_ids)
    rs.EnableRedraw(True)


if __name__ == "__main__":
    
    cutter_brep_obj_id = rs.GetObject("select cutting box: ",rs.filter.polysurface)
    cutter_brep_obj = rs.coercebrep(cutter_brep_obj_id)
    to_be_trimmed_ids = rs.GetObjects("select objects to be trimmed: ",rs.filter.surface|rs.filter.polysurface)
    to_be_trimmed = [rs.coercebrep(id) for id in to_be_trimmed_ids if id is not None]
    
    rs.EnableRedraw(False)
    
    cutter_brep_obj.EnsurePrivateCopy()
    
    ts = time.time()
    TST(cutter_brep_obj,to_be_trimmed,False)
    print "Elapsed time is {:.2f}".format(time.time()-ts)
    ts = time.time()
    TST(cutter_brep_obj,to_be_trimmed,True)
    print "Elapsed time is {:.2f}".format(time.time()-ts)

UPDATE:
Correction, just saw my model consists of 39360 surfaces and 38212 polysurfaces. So close to 80k and 1 second difference.

Hi maybe each task is too small. Is it possible to make a bundle of 1000 breps as one task?
Also, I suspect that putting AddBrep inside paralell.foreach is not a good idea.

1 Like

Thanks for the reply @mikity_kogekoge,

I don’t know about 1000 breps, but I have multiple boxes that cut these breps. Perhaps I can try to run each cutting box in a separate thread.

Regarding my syntax, do you think its usage is correct?

It should be something like this…

def foo(task):
    for i in task[0]:
        if cutter_brep_obj.Faces.Count > 1:
            trimmed = to_be_trimmed[i].Trim.Overloads[Rhino.Geometry.Brep,System.Double](cutter_brep_obj,tol)
            task[1].extend(trimmed)
            #for brep in trimmed:
            #    trimmed_brep_id = sc.doc.Objects.AddBrep(brep)
if parallel:
    N=int(iterations_count/200)
    count=0
    _tasks=[]
    for i in xrange(iterations_count):
        if count==0:
            task=[]
            _tasks.append((task,[]))#second list is a place to save the results
        task.append(i)
        count=count+1
        if count>N:
            count=0
    tasks.Parallel.ForEach(_tasks, foo)
    #merge
    for task,result in _tasks:
        for brep in result:
            sc.doc.Objects.AddBrep(brep)
else:
    for i in range(iterations_count):
        foo(i)
1 Like

There are two things that I see that would need to be changed.

The first is that you should not add objects to the doc from inside of your parallel for call. The RhinoDoc is not thread safe.

The second is a matter of what you are measuring. If you are measuring the entire script, then there may be a single threaded portion that both scripts use that overwhelms the time. For example, the time it takes to generate the display cache for all of that new geometry may be throwing your numbers off. You would need to time just the portion the script that can be run either serial or parallel to make a comparison.

1 Like