Many VIKTOR developers have encountered issues with slow parametrization, which is deemed problematic when the parameterization call exceeds a duration of 1 second. Sluggish parametrization significantly impacts the application, resulting in a laggy and unresponsive user experience, hampering its overall performance and responsiveness.
As the application grows in complexity with a multitude of parameters and intricate callbacks, pinpointing the specific areas where time is consumed becomes increasingly challenging. The expanding scope and complexity might obscure the source of the time penalty within the codebase, making it difficult to identify the exact segments responsible for the slowdown in execution.
Here profiling can help you out.
Time profiling
Time profiling in programming refers to the practice of measuring and analyzing the execution time of specific sections or the entirety of a program’s code. This technique helps developers identify performance bottlenecks by pinpointing which parts of the code take the most time to run. By utilizing specialized tools or modules, such as timeit
or cProfile
in Python, time profiling allows for precise measurement of execution times, aiding in the optimization and improvement of code efficiency for better overall performance.
Profiling wrapper
The following decorator can be used to profile a certain function as follows:
@profile_it()
def some_function(input_1, input_2)
...
import cProfile
from functools import wraps
def profile_it(output_file: str= None):
"""A time profiler decorator.
Inspired by and modified the profile decorator of Giampaolo Rodola:
http://code.activestate.com/recipes/577817-profile-decorator/
Args:
output_file: str or None. Default is None
Path of the output file. If only name of the file is given, it's
saved in the current directory.
If it's None, the name of the decorated function is used.
Returns:
Profile of the decorated function
"""
def inner(func):
@wraps(func)
def wrapper(*args, **kwargs):
_output_file = output_file or func.__name__ + ".prof"
pr = cProfile.Profile()
pr.enable()
retval = func(*args, **kwargs)
pr.disable()
pr.dump_stats(_output_file)
return retval
return wrapper
return inner
Profiling the parametrization
Now the big question is, how do we profile the parametrization. This is done by overwriting the protected method _generate()
on the parametrization object.
DISCLAIMER: YOU SHOULD NEVER OVERWRITE A PROTECTED MEMBER IN PRODUCTION CODE, ONLY USE IN DEVELOPMENT AND REMOVE THIS BEFORE PUBLISHING.
class Parametrization(ViktorParametrization):
@profile_it()
def _generate(self, *args, **kwargs):
return super()._generate(*args, **kwargs)
... (field definitions)
Interpreting the profiling result
The decorator will save a .prof
file on you local file system. This file can be interpreted using a tool like SnakeViz or the pstats module.
Using SnakeViz, it can be seen that, in the above example, 2 callbacks are responsible for the slow parametrization. Both these callback rely on the slow get_file_from_storage
function, which is called 6 times!
Usual causes of slow parametrization
Often when an app is suffering from slow parametrization, some callback rely on a slow underleying function. This function can appear not that slow (+/- 300ms), but when multiple callbacks rely on this function or the callback is used in multiple fields, this can add up to multiple seconds of lag.
Profiling creates a great insight where to start optimizing your Parametrization.