pycatia icon indicating copy to clipboard operation
pycatia copied to clipboard

[QUESTION] pycatia perfo vs VBA

Open Djey51 opened this issue 10 months ago • 17 comments

Hello,

Is there some optimization to follow to obtain better performances with pycatia?

When I use the same macro developed in python with pycatia than one developed in VBA, I can see that the pycatia one is slower (next to 2x) than the VBA one.

Do you have some tips and tricks for me please?

Thanks

Djey51 avatar Mar 26 '24 15:03 Djey51

Some more information / context?

I don't understand why there would be any difference as they're both accessing the COM object.

evereux avatar Mar 26 '24 15:03 evereux

Same for me. But it is just a constatation. I'm the only guy who give you tgis feedback? Did you already perform performance tests on both languages?

Djey51 avatar Mar 26 '24 19:03 Djey51

I noticed the same, especially when dealing with many many user ref props. It's on my schedule to investigate this problem, but what I've found out so far isn't consistent, so I think I'm on a goose chase for now.

An interesting fact tho: I didn't have any perfomance issues when I used a very stripped version of pycatia. But I didn't test it under the same premisses as I do now.

deloarts avatar Mar 28 '24 14:03 deloarts

I'm the only guy who give you tgis feedback?

Yes.

Did you already perform performance tests on both languages?

Nope. Believe it or not I rarely do any CATIA scripting, pycatia or VBA.

I've been thinking this over the past week or so and my guess is that any noticeable performance difference is probably in those class methods that use system_service.evaluate(). For example Point.get_coordinates().

If only I / we / someone could figure out a way to remove the need for it. I've searched again today and no real luck. Lots of clues but nothing I can get to work.

As a test for the speed I created a part with 500 points in:

"""

Creates n random points in a new geometric set 'points'.

Requires CATIA V5 to be running and a CATPart active.

"""
import time

from random import randint

from pycatia import catia
from pycatia.mec_mod_interfaces.part_document import PartDocument

MIN = 0
MAX = 10000
TOTAL_POINTS = 500

start = time.time()


caa = catia()
active_document = PartDocument(caa.active_document.com_object)
part = active_document.part
hsf = part.hybrid_shape_factory
hbs = part.hybrid_bodies
gs_points = hbs.add()
gs_points.name = 'points'

for i in range(1, TOTAL_POINTS+1):
    new_point = hsf.add_new_point_coord(randint(MIN, MAX), randint(MIN, MAX), randint(MIN, MAX))
    gs_points.append_hybrid_shape(new_point)

part.update()
end = time.time()
print(end-start)

11 seconds

I then have a script that uses Point.get_coordinates() for all 500 points.

import time

from pycatia import catia
from pycatia.hybrid_shape_interfaces.point import Point
from pycatia.mec_mod_interfaces.part_document import PartDocument
from pycatia.enumeration.enumeration_types import geometrical_feature_type
from pycatia.space_analyses_interfaces.inertia import Inertia


start = time.time()

caa = catia()
application = caa.application
part_doc = PartDocument(application.active_document.com_object)
part = part_doc.part
spa_workbench = part_doc.spa_workbench()
hsf = part.hybrid_shape_factory
hbs = part.hybrid_bodies
hb_points = hbs.item('points')
shapes = hb_points.hybrid_shapes

for shape in shapes:
    gft = hsf.get_geometrical_feature_type(shape)
    gft_text = geometrical_feature_type[gft]
    if gft_text == 'Point':
        p = Point(shape.com_object)
        coord = p.get_coordinates()
        print(shape.name, coord)

end = time.time()
print(end-start)

23 seconds.

For just 500 points this took my machine 23 seconds. I think that's slow? But I don't know. I tried writing the equivalent in VBA but gave up as I just never really learned that side of things and writing VBA makes me feel dirty. Perhaps one of you could so we can do a real comparison?

An interesting fact tho: I didn't have any performance issues when I used a very stripped version of pycatia. But I didn't test it under the same premisses as I do now.

That framework looks pretty much identical to pycatia so why would it be any different? You've just got less modules which would possibly show a small difference in RAM usage and startup time. I doubt it would be enough to show a significance difference in execution time for a script hundreds/thousands of functions. But I'd be happy to see some evidence.

Less conjecture and more examples my friends. ❤️ 😄

evereux avatar Apr 18 '24 15:04 evereux

Also, for completeness we should time the point creation script too and create a VBA equivalent of that. This is because the point creation script doesn't use any system_service.evaluate() calls.

edited above reply to include the point creation script time.

evereux avatar Apr 18 '24 15:04 evereux

Hi, I propose to write the equivalent in VBA and also in VB.net to compare the 3 versions on the same device even if I really prefer python. But I need good performances with my scripts... Other point: I will be absent during the next 3 weeks. So it is not for very soon. Bye

Djey51 avatar Apr 18 '24 18:04 Djey51

No problem. 👍

evereux avatar Apr 19 '24 08:04 evereux

I did some testing comparing your code to a VBA version.

pycatia ~ 5s VBA ~ 2.3s

Something I noticed was that when using pycatia the display is refreshed. You can see the points created (if that makes sense). When using VBA however you don't.

I tried caa.refresh_display = False, but no difference.

Sub CATMain()

Dim MIN
Dim MAX
Dim TOTAL_POINTS
Dim i
Dim startTime
Dim endTime
Dim catia
Dim activeDocument 
Dim part
Dim hybridShapeFactory
Dim hybridBodies
Dim gsPoints
Dim newPoint

MIN = 0
MAX = 10000
TOTAL_POINTS = 500

startTime = Timer

Set catia = GetObject(, "CATIA.Application")
Set activeDocument = catia.ActiveDocument
Set part = activeDocument.Part
Set hybridShapeFactory = part.HybridShapeFactory
Set hybridBodies = part.HybridBodies
Set gsPoints = hybridBodies.Add()
gsPoints.Name = "points"

For i = 1 To TOTAL_POINTS
    Set newPoint = hybridShapeFactory.AddNewPointCoord(Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN))
    gsPoints.AppendHybridShape newPoint
Next

part.Update()

endTime = Timer
MsgBox "Elapsed time: " & (endTime - startTime) & " seconds"
End Sub

Mithro86 avatar Jul 02 '24 15:07 Mithro86

Great stuff. I'll have a play with this tomorrow.

Interesting what you say about the screen refresh. There's a pycatia script I was running the other day that I swear was faster by disabling the refresh_display, seemed to work. That was generating points I'm sure.

Also, we should really remove the random point generation outside of the timing. Probably won't make a noticeable difference but would be more scientific.

evereux avatar Jul 02 '24 16:07 evereux

Correction: caa.refresh_display = True --> 6s caa.refresh_display = False --> 5s

Mithro86 avatar Jul 03 '24 09:07 Mithro86

So, thanks to this I got exposed to dealing with early and late bindings for the first time. I will never be the same. Hahaha

I did a test with a mix off early bindings, did not do much ~0.3s (NOTE: if you run this you have to clear gen_py afterwards otherwise it get stuck to early-bindings). After creating the bindings I commented it out:

from win32com.client import Dispatch, CastTo
from win32com.client.dynamic import DumbDispatch
from timeit import default_timer as timer
from random import randint

def GetRightDispatch(o):
    return Dispatch(DumbDispatch(o))

MIN = 0
MAX = 10000
TOTAL_POINTS = 500

start = timer()
caa = Dispatch('CATIA.Application')

caa.RefreshDisplay = False

active_document = caa.ActiveDocument

part = Dispatch(active_document.Part)
#part = GetRightDispatch(active_document.Part)
#part = CastTo(part, "Part") 

hsf = Dispatch(part.HybridShapeFactory)
#hsf = GetRightDispatch(part.HybridShapeFactory)
#hsf = CastTo(hsf, "HybridShapeFactory")

hbs = Dispatch(part.HybridBodies)
#hbs = GetRightDispatch(part.HybridBodies)
#hbs = CastTo(hbs, "HybridBodies")

gs_points = hbs.Add()
gs_points.Name = 'points'

for i in range(1, TOTAL_POINTS+1):
    new_point = hsf.AddNewPointCoord(i, i, i)
    gs_points.AppendHybridShape(new_point)

part.Update()
end = timer()
caa.RefreshDisplay = True
print(end - start)

When running both scripts, yours and this, I noted that (at random) the Geometrical Set was collapsed when the points was created. When it was, it was ~1s faster. I have not found a way to control this.

Mithro86 avatar Jul 03 '24 16:07 Mithro86

Interesting. Thanks so much for posting your findings.

I stated:

Interesting what you say about the screen refresh. There's a pycatia script I was running the other day that I swear was faster by disabling the refresh_display, seemed to work. That was generating points I'm sure.

It wasn't generating points, it was for speeding up the drawing of a template into the Drawing background for my little side project pycatia-tools (shamless plug).

Jumping about from project to project is frying my little brain. 😃

evereux avatar Jul 04 '24 09:07 evereux

Finally had the time to play around a bit with this issue. I wrote 3 scripts, one with pycatia, one using the win32com.Dispatch and one in catvbs. All 3 of them do the same:

  1. Create 10 new parts
  2. Create 400 new StrParam type UserRefProperties in each part
  3. Read each single StrParam by index
  4. Read each single StrParam by name

I added all scripts as zip so I don't clutter this issue with code: time_comp_240707.zip

Setup:

  • Windows 11 on Dassault certified hardware
  • CATIA V5-6R 2023 (B33)
  • Python 3.10.7
  • Requirements:
    • pycatia==0.7.2
    • pywin32==306

Results:

Script Average creating time Average reading time (index) Average reading time (name)
catvbs 0.009375s 0.002734s 0.952343s
dispatch 0.098642s 0.038168s 3.057094s
pycatia 0.043048s 0.084434s 3.586073s

Some things I found noteworthy:

  • there's a ~15% deviation in all results in all 3 scripts (I ran them many times, the results above are the average of the average)
  • reading the UserRefProperties by index is significantly faster than reading them by name in all 3 scripts (could be important for anyone who might have performance issues regarding UserRefProperties)
  • pycatia is faster than the dispatch when creating the StrParams, but slower in reading by index (I really did not expect this and I will try this on another machine)
  • Using caa.refresh_display = False had no real impact on the performance, BUT if it's set to False CATIA doesn't recognize the creation of UserRefProperties (there's no document change shown in the Save Management). This is extremely dangerous because ist can lead to loss of data!

Sidenotes:

  • I don't know is if it's a fair comparison because I don't know the inner workings of the timing functions I used (python: timeit.default_timer vs. catvbs: Timer)
  • I used the UserRefProperties to test this because one of my projects does rely on a fast creation/reading of them
  • I hadn't had the time to investigate possible reasons for this issue (that aren't wild guessings)

Edit: Corrected values in result table

deloarts avatar Jul 07 '24 09:07 deloarts

Great work @deloarts.

reading the UserRefProperties by index is significantly faster than reading them by name in all 3 scripts (could be important for anyone who might have performance issues regarding UserRefProperties)

That does make sense to me. Like a database lookup by id rather than string.

pycatia is faster than the dispatch when creating the StrParams, but slower in reading by index (I really did not expect this and I will try this on another machine)

A noticeable difference? That does surprise me. Recently, to pycatia's dispatch method I added pythoncom.CoInitialize(). This was so I could use pycatia in a GUI application (wouldn't work without it).

Could you run your manual dispatch version of the script with this added? If this is slowing things down I'll update pycatia to make that call optional. I'll do it myself later this week using your script if you haven't time. It didn't help prevent the behavior I have noted below.


So, this may or may not be related ... I've just run a script opening and closing a document a 1000 times in a for loop and I noticed that CATIA V5 kind of freezes every so often (no obvious pattern I can see).

The times were not consistent at all!

So, I did a bit of playing:

import time
import statistics
from pathlib import Path

from pycatia import catia

def open_document(file: Path, number_of_tries:int):
    times = []
    caa = catia()
    caa.logger.disabled = True
    documents = caa.documents
    for i in range(number_of_tries):
        start = time.time()
        document = documents.open(test_file)
        document.close()
        end = time.time()
        times.append(end-start)
    return times


test_file = Path(r'C:\Users\evereux\cloud\pycatia\__archive__\Part1.CATPart')
number_of_tries = 100
times = open_document(test_file, number_of_tries)

print(f'TIMES_REUSE: The fastest opening time was {min(times)}.')
print(f'TIMES_REUSE: The slowest opening time was {max(times)}.')
print(f'TIMES_REUSE: The slowest opening time was {statistics.mean(times)}.')


  • caa.refresh_display = False didn't stop the pauses.
  • caa.visible = False was just slower all round?!?!?!
  • this could be a bug in the CATIA version I'm using right now: R21.
  • If I created the caa object each time before opening the file itself it was be consistently quicker to open the file. But if the time taken to create the caa object is factored it was slower. (edit: i did say similar but that wasn't the case)

evereux avatar Jul 07 '24 14:07 evereux

Just another thought .. when comparing VB versus Python I don't think we should allow anything to output to terminal as this will slow things down. For example, disable logging caa.logger.disabled = True and don't print anything to terminal within a timed operation.

I think I'm the only one that has done that above though. d'oh.

evereux avatar Jul 07 '24 14:07 evereux

Hello, Here is my contribution and my results:

Tests done with python, python_alt, vb.net, catvba for 500 and 5000 points (yes, my laptop is very efficient and the results are more significant with 5000 points 😜) For 500 pts. For 5000 pts

  • catvba 0,47 second 12 s
  • vb.net 2,53 s 28 s
  • python 3,85 s 223 s
  • python_alt 3,90 s 215 s

You will find below used scripts for these tests.

Python

import time
from random import randint
##########################################################
# insert syspath to project folder so examples can be run.
# for development purposes.
import os
import sys
from pathlib import Path
sys.path.insert(0, os.path.abspath('..\\pycatia'))
##########################################################
from pycatia import catia
from pycatia.mec_mod_interfaces.part_document import PartDocument
from pycatia.mec_mod_interfaces.part import Part
from pycatia.hybrid_shape_interfaces.hybrid_shape_factory import HybridShapeFactory
from pycatia.mec_mod_interfaces.hybrid_bodies import HybridBodies
def cat_main():
    min_val = 0
    max_val = 10000
    total_points = 5000
    start_time = time.time()
    # Get the CATIA application and active document
    cat = catia()
    cat.refresh_display = False
    # docs = Documents(cat.documents.com_object)
    part_doc = PartDocument(cat.active_document.com_object)
    part = Part(part_doc.part.com_object)
    hybrid_shape_factory = HybridShapeFactory(part.hybrid_shape_factory.com_object)
    hybrid_bodies = HybridBodies(part.hybrid_bodies.com_object)
    # Create a new hybrid body and set its name
    gs_points = hybrid_bodies.add()
    [gs_points.name](https://github.com/evereux/pycatia/issues/gs_points.name) = "points"
    for i in range(1, total_points +1):
        # Generate random coordinates for each point
        x, y, z = (randint(min_val, max_val), randint(min_val, max_val), randint(min_val, max_val))
        # Add a new point to the hybrid body
        new_point = hybrid_shape_factory.add_new_point_coord(x, y, z)
        gs_points.append_hybrid_shape(new_point)
    part.update()
    end_time = time.time()
    elapsed_time = end_time - start_time
    # cat.refresh_display = True
    print(f"Elapsed time: {elapsed_time:.2f} seconds")
if __name__ == "__main__":
    cat_main()

Python_alt

from win32com.client import Dispatch, CastTo
from win32com.client.dynamic import DumbDispatch
from timeit import default_timer as timer
from random import randint
def GetRightDispatch(o):
    return Dispatch(DumbDispatch(o))
MIN = 0
MAX = 10000
TOTAL_POINTS = 5000
start = timer()
caa = Dispatch('CATIA.Application')
caa.RefreshDisplay = False
active_document = caa.ActiveDocument
part = Dispatch(active_document.Part)
#part = GetRightDispatch(active_document.Part)
#part = CastTo(part, "Part")
hsf = Dispatch(part.HybridShapeFactory)
#hsf = GetRightDispatch(part.HybridShapeFactory)
#hsf = CastTo(hsf, "HybridShapeFactory")
hbs = Dispatch(part.HybridBodies)
#hbs = GetRightDispatch(part.HybridBodies)
#hbs = CastTo(hbs, "HybridBodies")
gs_points = hbs.Add()
gs_points.Name = 'points'
for i in range(1, TOTAL_POINTS+1): 
    # Generate random coordinates for each point
    x, y, z = (randint(MIN, MAX), randint(MIN, MAX), randint(MIN, MAX))
    # Add a new point to the hybrid body
    new_point = hsf.AddNewPointCoord(x, y, z)
    gs_points.AppendHybridShape(new_point)
part.Update()
end = timer()
caa.RefreshDisplay = True
print(end - start)

Vb.net

Imports System.Diagnostics
Imports MECMOD
Imports INFITF
Imports PARTITF
Imports HybridShapeTypeLib
Module Module1
    Sub Main()
        Dim MIN
        Dim MAX
        Dim TOTAL_POINTS
        Dim i As Integer
        Dim startTime
        Dim endTime
        Dim CATIA As Application
        MIN = 0
        MAX = 10000
        TOTAL_POINTS = 500
        startTime = Timer
        CATIA = GetObject(, "CATIA.Application")
        CATIA.RefreshDisplay = False
        ' Récupérer le document actif
        Dim partDocument As PartDocument
        partDocument = CATIA.ActiveDocument
        ' Récupérer la partie active
        Dim part As Part
        part = partDocument.Part
        ' Récupérer la collection des bodies
        Dim bodies As Bodies
        bodies = part.Bodies
        ' Créer un nouveau set géométrique
        Dim hybridBodies As HybridBodies
        hybridBodies = part.HybridBodies
        Dim hybridBody As HybridBody
        hybridBody = hybridBodies.Add()
        hybridBody.Name = "points"
        ' Créer un point
        Dim hybridShapeFactory As HybridShapeFactory
        hybridShapeFactory = part.HybridShapeFactory
        Dim point As HybridShapePointCoord
        Dim random As New Random()
        Dim x As Double
        Dim y As Double
        Dim z As Double
        For i = 1 To TOTAL_POINTS
            x = random.NextDouble() * 1000
            y = random.NextDouble() * 1000
            z = random.NextDouble() * 1000
            point = hybridShapeFactory.AddNewPointCoord(x, y, z)
            ' Ajouter le point au set géométrique
            hybridBody.AppendHybridShape(point)
        Next
        ' Mettre à jour la partie
        part.Update()
        endTime = Timer
        CATIA.RefreshDisplay = True
        MsgBox("Temps d'exécution : " & (endTime - startTime) & " Secondes")
    End Sub
End Module

Catvba

Sub CATMain()
    Dim MIN
    Dim MAX
    Dim TOTAL_POINTS
    Dim i
    Dim startTime
    Dim endTime
    Dim catia
    Dim activeDocument
    Dim part
    Dim hybridShapeFactory
    Dim hybridBodies
    Dim gsPoints
    Dim newPoint
   
    MIN = 0
    MAX = 10000
    TOTAL_POINTS = 500
   
    startTime = Timer
   
    Set catia = GetObject(, "CATIA.Application")
    Set activeDocument = catia.activeDocument
    Set part = activeDocument.part
    Set hybridShapeFactory = part.hybridShapeFactory
    Set hybridBodies = part.hybridBodies
    Set gsPoints = hybridBodies.Add()
    gsPoints.Name = "points"
   
    For i = 1 To TOTAL_POINTS
    Set newPoint = hybridShapeFactory.AddNewPointCoord(Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN), Int((MAX - MIN + 1) * Rnd + MIN))
    gsPoints.AppendHybridShape newPoint
    Next
   
    part.Update
   
    endTime = Timer
    MsgBox "Elapsed time: " & (endTime - startTime) & " seconds"
End Sub

Best regards

Djey51 avatar Jul 17 '24 14:07 Djey51

Thanks @Djey51!

my laptop is very efficient and the results are more significant with 5000 points

you're not kidding! 😄

evereux avatar Jul 18 '24 08:07 evereux