Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUESTION] pycatia perfo vs VBA #205

Open
Djey51 opened this issue Mar 26, 2024 · 17 comments
Open

[QUESTION] pycatia perfo vs VBA #205

Djey51 opened this issue Mar 26, 2024 · 17 comments

Comments

@Djey51
Copy link

Djey51 commented Mar 26, 2024

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

@evereux
Copy link
Owner

evereux commented Mar 26, 2024

Some more information / context?

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

@evereux evereux removed their assignment Mar 26, 2024
@Djey51
Copy link
Author

Djey51 commented Mar 26, 2024

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?

@deloarts
Copy link
Contributor

deloarts commented Mar 28, 2024

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.

@evereux
Copy link
Owner

evereux commented Apr 18, 2024

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
Copy link
Owner

evereux commented Apr 18, 2024

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.

@Djey51
Copy link
Author

Djey51 commented Apr 18, 2024

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

@evereux
Copy link
Owner

evereux commented Apr 19, 2024

No problem. 👍

@Mithro86
Copy link
Contributor

Mithro86 commented Jul 2, 2024

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

@evereux
Copy link
Owner

evereux commented Jul 2, 2024

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.

@Mithro86
Copy link
Contributor

Mithro86 commented Jul 3, 2024

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

@Mithro86
Copy link
Contributor

Mithro86 commented Jul 3, 2024

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.

@evereux
Copy link
Owner

evereux commented Jul 4, 2024

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. 😃

@deloarts
Copy link
Contributor

deloarts commented Jul 7, 2024

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

@evereux
Copy link
Owner

evereux commented Jul 7, 2024

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
Copy link
Owner

evereux commented Jul 7, 2024

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.

@Djey51
Copy link
Author

Djey51 commented Jul 17, 2024

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

@evereux
Copy link
Owner

evereux commented Jul 18, 2024

Thanks @Djey51!

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

you're not kidding! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants