Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ADVICE ON MULTITHREADING
#1
Language: Python 3.11
RoboDK version: 5.4.3

I have an application where I need to run SolveIK() many times (on the order of 10^8). To improve the speed, I am attempting to apply multithreading.

Using both 2 and 6 threads, I did not observe any performance improvement (in fact, it was slower). Additionally, in Windows resource monitor, I observed that no matter the number of threads, only 2 logical cores were utilized. However, console prints indicated that multiple threads were indeed running and making progress simultaneously. This leads me to believe that there is some issue preventing multiple SolveIK functions from running simultaneously.

Details:
- Each thread uses the -NEWINSTANCE flag and specifies a unique port to prevent multiple functions from calling the same instance of RoboDK.
- My machine has 32GB RAM and 16 logical cores

Here are my best guesses for the cause of the issue:
1. Something weird is happening with Python's GIL. However, since robolink.Robolink.SolveIK() just sends a command ("G_IK") to a library that was written in C, I am somewhat skeptical that the GIL is the problem.
2. There is some GIL-like thread lock object that is shared for all instances of RoboDK. I am not sure how to test this, except for asking the source-code authors.
3. A single call to SolveIK() returns fast enough that breaking out into multithreading is not faster. I would need to go back and write my own routine for batch-processing SolveIK() that is thread-safe and GIL-independent.

I suspect that (3) is the most likely cause of what I am seeing, but before I go to the effort of writing my own wrapper for SolveIK(), I wanted to get confirmation that there isn't something else going on that I can't solve.

If (3) is indeed the issue, would using the C API rather than the Python API fix my problem?

Thanks!
#2
Calculating the inverse kinematics (SolveIK) should be very, very fast (1-50 micro seconds, depending on the robot and the position you request). This function is single threaded so you can't multithread it unless you use a new instance of RoboDK for each calculation (I believe you are already doing that).

You may be experiencing delays due to the overhead of using Python and the API.

If you want very fast results it would be better to use a plugin which directly calls the native C function in RoboDK. Another option is to use the API in another language (using the C or C++ API should be at least 5 times faster than Python).

The main page of the plugin interface page on GitHub shows a specific timing example calculating inverse kinematics for ABB:
https://github.com/RoboDK/Plug-In-Interf...ming-tests
   
#3
Thanks for the thorough response. This answers my original questions -- seems like C/C++ is the way.

A couple follow-up questions...
I created a routine that checks a number of targets using both the SolveIK() and MoveJ() functions. See example code and console output below. My workstation is attached. Note that the external axis must be at 1881.86 to reproduce my results.
1. I found that using MoveJ() was faster than SolveIK() by about 2x. I assume this is another consequence of using the API rather than the native C function?
2. I found that MoveJ() occasionally failed when SolveIK() succeeded. Is this expected behavior? (MoveJ() only found 33 targets while SolveIK() found 41.)


Code used: (Incomplete! My actual code uses infrastructure for getting workstation objects that would be confusing to include in this example.)
Code:
from robodk import robolink
from robodk.robolink import *

def check_target_ik(target, robot, tool, part):

    joints = robot.SolveIK(target.PoseWrt(target.Parent()), tool=robot.PoseTool(), reference=robot.PoseFrame())
    if str(joints) == "[[ 0 ]]\n":
        status = False
    else:
        status = True

    return status

def check_target_movej(target, robot, part):

    status = False
    try:
        robot.MoveJ(target)
        status = True
    except TargetReachError:
        pass

    return status

# some routine to get the handles of everything you need...
# workstation_objects = get_workstation_objects()

num_reachable = 0
tStart = time.time()
for i in range(len(targets)):
    status = check_target_ik(targets[i], robot, tool, part)
    if status:
        num_reachable = num_reachable + 1
print("Checked " + str(len(targets)) + " targets.")
print("\tMethod: solveIK")
print("\tTime elapsed: " + str(time.time() - tStart) + " seconds.")
print("\tnum_reachable: " + str(num_reachable) + "\n")

num_reachable = 0
tStart = time.time()
for i in range(len(targets)):
    status = check_target_movej(targets[i], robot, part)
    if status:
        num_reachable = num_reachable + 1
print("Checked " + str(len(targets)) + " targets.")
print("\tMethod: MoveJ")
print("\tTime elapsed: " + str(time.time() - tStart) + " seconds.")
print("\tnum_reachable: " + str(num_reachable) + "\n")

Console output:
Code:
Checked 47 targets.
       Method: solveIK
       Time elapsed: 0.5362012386322021 seconds.
       num_reachable: 41

Checked 47 targets.
       Method: MoveJ
       Time elapsed: 0.24617290496826172 seconds.
       num_reachable: 33


Attached Files
.rdk   test_solver.rdk (Size: 2.18 MB / Downloads: 8)
#4
The problem between MoveJ and SolveIK is that MoveJ checks if the path from the current position to your target is feasible. In general, there shouldn't be any issues with a joint move if the start and end joint positions are valid. However, more issues can happen compared to a simple SolveIK. For example, if the start or end joints are close to a joint limit.

Also for some robots have special joint 2-3 coupling. Since the interpolation happens at the joint level and the Comau robot you are using it could be that some points are not reachable with a simple joint movement.

It would help to see a report of the list of joints that are not reachable with a MoveJ command and from where is it that you are trying to move.
#5
Your explanation makes sense. Since that MoveJ()/SolveIK() behavior is expected, I'll go ahead and mark this as resolved.

I don't have a particular need to investigate the details of when exactly it is occurring -- I just wanted to make sure I wasn't missing something.

Thanks, Albert!
#6
Great, thank you for your feedback!
  




Users browsing this thread:
1 Guest(s)