Skip to content

Performance profiling

If the created PythonPart has poor performance, it's useful to perform performance profiling of the Python source code. This can be done using different tools described below. Performance profiling is supported by the PythonParts framework for functions and lines and it is done using the class ProfileUtil

With this tool it is possible to do performance profiling of the functions. Implementation in the code can look as follows:

ProfileUtil.profile(modify_element_property_local, page, name, value,
                    calls_to_print = 20, show_graphical_results = False)

The profiling results are shown in the ALLPLAN trace window:

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1    0.000    0.000    0.348    0.348 TowerCrane.py:179(modify_element_property_local)
1    0.001    0.001    0.322    0.322 TowerCrane.py:685(draw_preview)
1    0.225    0.225    0.225    0.225 TowerCrane.py:1045(select_elements)
1    0.076    0.076    0.077    0.077 Utility.py:247(checking_collision)
1    0.021    0.021    0.021    0.021 TowerCrane.py:322(on_preview_draw)
1    0.000    0.000    0.006    0.006 SafetyZone.py:64(create_element)
1    0.001    0.001    0.006    0.006 SafetyZone.py:89(create_geometry)
1    0.005    0.005    0.005    0.005 SafetyZone.py:140(create_jib_safety_zone)
1    0.000    0.000    0.004    0.004 Tower.py:74(create_element)
1    0.000    0.000    0.004    0.004 Tower.py:133(create_geometry)
1    0.000    0.000    0.003    0.003 BuildingElementPaletteService.py:346(update_palette)
1    0.000    0.000    0.003    0.003 BuildingElementPalette.py:59(show)
1    0.000    0.000    0.003    0.003 TowerHead.py:51(create_element)
1    0.000    0.000    0.003    0.003 TowerHead.py:74(create_geometry)
1    0.003    0.003    0.003    0.003 TowerHead.py:97(create_towerhead)
1    0.000    0.000    0.002    0.002 Tower.py:282(create_tower)
37    0.002    0.000    0.002    0.000 Utility.py:57(extrude)
3    0.000    0.000    0.002    0.001 BuildingElementPalette.py:165(show_page_controls)
1    0.000    0.000    0.002    0.002 Jib.py:64(create_element)
1    0.000    0.000    0.002    0.002 Jib.py:103(create_geometry)

Visualizing the results

To make the results more visible, a graphical result of the profiling results can be generated. To do that, set the parameter show_graphical_results to True and use the following tools:

gprof2dot

grprof2dot is a Python module to convert the output from the used profiler into a dot graph. The installation is done with the help of the PythonPart InstallPythonPackage.

Manual installation
  • visit https://pypi.org/project/gprof2dot/
  • select Download files and download the package
  • unpack the files and copy the folder gprof2dot to the ALLPLAN folder ..\Etc\PythonParts-site-packages (if it doesn't exist, create it)

Graphviz

Graphviz is an open source graph visualization software.

Here is an example of how the graphical result of profiling may look like:

Profiling results

Here is an example of how profiling can be included in the PythonPart script in the function modify_element_property

Example
PROFILE_FUNCTION = True
...

def modify_element_property(self, page, name, value):
    ...

    if PROFILE_FUNCTION:
        return ProfileUtil.profile(modify_element_property_local,
                                   page, name, value, calls_to_print = 20,
                                   show_graphical_results = True)
    else:
        return self.modify_element_property_local(page, name, value)

def modify_element_property_local(self, page, name, value):

Profiling an Interactor PythonPart

The PythonParts framework do the profiling of your Interactor PythonPart. It gets executed, when you hold the Ctrl key and press left mouse button.

Info

Make sure, the gprof2dot is installed. Otherwise the profiling is not started.

Info

PythonParts framework uses the keyboard package to check, whether the Ctrl is hold. You can use this package also in your script:

import keyboard
...
if keyboard.is_pressed("ctrl"):
    # introduce an action only, when ctrl is pressed

The installation is done with the help of the PythonPart InstallPythonPackage.

With this tool it is possible to do performance profiling of the functions. Implementation in the code can look as follows:

ProfileUtil.profile(modify_element_property_local, page, name, value,
                    profiler_type = ProfilerType.PY_INSTRUMENT)
The profiling results are shown in the explorer:

pyinstrument

Here is an example of how profiling can be included in the PythonPart script in the function modify_element_property:

Example
PROFILE_FUNCTION = True
...

def modify_element_property(self, page, name, value):
    ...

    if PROFILE_FUNCTION:
        return ProfileUtil.profile(modify_element_property_local, page, name, value,
                                   profiler_type = ProfilerType.PY_INSTRUMENT))
    else:
        return self.modify_element_property_local(page, name, value)

def modify_element_property_local(self, page, name, value):
    ...

The installation is done with the help of the PythonPart InstallPythonPackage.

With this tool it is possible to do performance profiling of the lines. Implementation in the code can look as follows:

ProfileUtil.profile(modify_element_property_local, page, name, value,
                    profiler_type = ProfilerType.LINE_PROFILE)

The profiling results are shown in the ALLPLAN trace window:

Function: select_elements at line 1046

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
1046                                               @use_line_profiler
1047                                               def select_elements(self):
1048
1049         3          0.2      0.1      0.0          point, radius = self.calculate_point_to_select()
1050         3          0.1      0.0      0.0          world_point = AllplanGeo.Transform(point, self.trans_matrix)
1051         3          0.0      0.0      0.0          view_world_projection = self.coord_input.GetViewWorldProjection()
1052         3          0.0      0.0      0.0          new_point = view_world_projection.WorldToView(world_point)
1053
1054                                                   # Big polygon for Crane
1055         3          0.0      0.0      0.0          polygon1 = AllplanGeo.Polygon2D()
1056         3          0.0      0.0      0.0          diff_angle = math.pi*2/36
1057         3          0.0      0.0      0.0          angle = diff_angle/2
1058         3          0.0      0.0      0.0          rad = radius/math.cos(angle)
1059       111          0.0      0.0      0.0          for _ in range(0, 36):
1060       108          0.2      0.0      0.0              polygon1 += AllplanGeo.Point2D(rad * math.cos(angle), rad * math.sin(angle))
1061       108          0.0      0.0      0.0              angle += diff_angle
1062         3          0.0      0.0      0.0          polygon1 += polygon1[0]
1063
1064         3          0.0      0.0      0.0          move_matrix = AllplanGeo.Matrix2D()
1065         3          0.0      0.0      0.0          move_matrix.SetTranslation(AllplanGeo.Vector2D(new_point))
1066         3          0.0      0.0      0.0          crane_polygon = AllplanGeo.Transform(polygon1, move_matrix)
1067
1068         3          0.0      0.0      0.0          condition = AllplanIFW.SelectElementsService.eSelectCondition.SELECT_ALL
1069         3          0.0      0.0      0.0          selected_filter = AllplanIFW.SelectionQuery()
1070
1071         6        691.7    115.3     99.9          elements = AllplanIFW.SelectElementsService.SelectByPolygon(self.doc, crane_polygon, view_world_projection,
1072         3          0.0      0.0      0.0                                                                         condition, selected_filter)
1073
1074         3          0.0      0.0      0.0          return elements

Here is an example of how profiling can be included in the PythonPart script in the function modify_element_property:

Example
PROFILE_FUNCTION = True
...

def modify_element_property(self, page, name, value):
    ...

    if PROFILE_FUNCTION:
        return ProfileUtil.profile(modify_element_property_local, page, name, value,
                                   profiler_type = ProfilerType.LINE_PROFILE))
    else:
        return self.modify_element_property_local(page, name, value)

def modify_element_property_local(self, page, name, value):
    ...

Each function whose lines are to be included in the line profiling must be decorated as follows

@use_line_profiler         # temporary, must be removed again after line profiling
def select_elements(self):
Placeholder