ansys / pyansys-geometry

A Python wrapper for Ansys Geometry Services
https://geometry.docs.pyansys.com/
MIT License
36 stars 9 forks source link

Geometry manipulation slow compared to native manipulation #1215

Open andreas-hilti opened 1 month ago

andreas-hilti commented 1 month ago

šŸ” Before submitting the issue

šŸž Description of the bug

When doing geometry operations via pyansys-geometry, this is at least an order of magnitude slower than using SpaceClaim/Discovery scripting (and probably even more when compared to the SpaceClaim API).

Attached is just a dummy example to illustrate the point.

šŸ“ Steps to reproduce

Run the attached scripts with the geometry service and inside SC (with the same geom kernel).

I observed (but timings are noisy):

Geometry Service: Elapsed time: 38.75350642204285

SC: Elapsed time: 3.78514099121

geom_service_performance.zip

šŸ’» Which operating system are you using?

Windows

šŸ“€ Which ANSYS version are you using?

2024 R2

šŸ Which Python version are you using?

3.10

šŸ“¦ Installed packages

ansys-api-dbu==0.3.0
ansys-api-geometry==0.4.1
ansys-geometry-core==0.5.6
ansys-tools-path==0.5.2
beartype==0.18.5
certifi==2024.2.2
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
contourpy==1.2.1
cycler==0.12.1
fonttools==4.51.0
grpcio==1.64.0
grpcio-health-checking==1.64.0
idna==3.7
kiwisolver==1.4.5
matplotlib==3.9.0
numpy==1.26.4
packaging==24.0
pillow==10.3.0
Pint==0.23
platformdirs==4.2.2
pooch==1.8.1
protobuf==5.27.0
pyparsing==3.1.2
python-dateutil==2.9.0.post0
pyvista==0.43.8
requests==2.32.2
scipy==1.13.1
scooby==0.10.0
semver==3.0.2
six==1.16.0
typing_extensions==4.12.0
urllib3==2.2.1
vtk==9.3.0
RobPasMue commented 1 month ago

Hi @andreas-hilti - delay is expected indeed, since the native implementation is making direct usage of the library, whereas PyAnsys Geometry includes the library overhead, safety rails and gRPC communication with the service. Nonetheless these values are pretty high... let me investigate in depth. This might take some time.

RobPasMue commented 1 month ago

Investigating...

RobPasMue commented 1 month ago

Findings:

  1. Timing "section" was not exactly the same for both scripts - measuring specific part we are interested in.
# TIME IT
############################################

start_time = time.time()

w = 0.05
body1 = create_box_body(design, "box1", w, w, w)
body2 = body1.copy(design, "box2")
body2.translate(UNITVECTOR3D_X, w / 2)

for i in range(1000):
    newBody1 = body1.copy(design, f"box1_{i}")
    newBody2 = body2.copy(design, f"box2_{i}")
    newBody1.translate(UNITVECTOR3D_X, w * 1.1 * i)
    newBody2.translate(UNITVECTOR3D_X, w * 1.1 * i)
    newBody1.intersect(newBody2)

end_time = time.time()

############################################
  1. Operations were not the same in SCSCRIPT and PyAnsys Geometry script - translation occurs in Y axis in PyAnsys Geometry which, when trying to intersect, causes an error which is captured. try...excepts are somewhat costly. Changing to X direction translation like in SCSCRIPT
  2. Since we are only using one doc, we can enable pygeo.DISABLE_MULTIPLE_DESIGN_CHECK = True - this is in case we are only using a single doc approach, which is the case.

With all these changes, time goes down to 30.44142... secs. Investigating with profiler now.

RobPasMue commented 1 month ago

Performed a cProfiling operation on the code... There is little to nothing else that can be done on the Python side of things. Have a look at the file. Operations needed on your side are:

  1. pip install snakeviz
  2. snakeviz <filename> --> snakevix profiledata.prof

Find attached the file. I have only profiled the part we are interested in:


import time
import cProfile, pstats

...

# TIME IT
############################################

start_time = time.time()
profiler.enable()

w = 0.05
body1 = create_box_body(design, "box1", w, w, w)
body2 = body1.copy(design, "box2")
body2.translate(UNITVECTOR3D_X, w / 2)

for i in range(1000):
    newBody1 = body1.copy(design, f"box1_{i}")
    newBody2 = body2.copy(design, f"box2_{i}")
    newBody1.translate(UNITVECTOR3D_X, w * 1.1 * i)
    newBody2.translate(UNITVECTOR3D_X, w * 1.1 * i)
    newBody1.intersect(newBody2)

profiler.disable()
end_time = time.time()

############################################

# Export profiler output to file
stats = pstats.Stats(profiler)
stats.dump_stats("profiledata.prof")

If you sort out the times by "tottime" you will see that around 27secs are spent in a "blocking" condition coming from the gRPC channel. Meaning that this is the time spent in the service... Client side has its "problems" for sure like type checking, keeping track of server side status etc. but those are minimal in comparison.

profiledata.zip

RobPasMue commented 1 month ago

script.zip

This is the file used on my side...