## 26 March 2019

### [C4D Script] Measure the execution time of a script

Sometimes we have to choose between several algorithms that have the same goal. Performance and therefore the execution time can be considered as a good criterion, although there are many others. For that the best solution would be to use a profiling tool, but if you want a quick solution we can simply calculate the difference of time between the beginning and the end of each algorithm.

In python, for ease and clarity of use, we can write this as a custom decorator and call it anywhere in our script.

To use this decorator :

2. Add the @measure() annotation above the function's signature that you want to test

```def measure(iterations=1):
"""
This decorator allows you to print the average execution time of a function
:param iterations: number of times that function will be called
"""
import time

def decorator(fn):

def wrapper(*args, **kwargs):
_sum = 0.0
_result = None
for i in xrange(iterations):
_start = time.time()
_result = fn(*args, **kwargs)
_sum += time.time() - _start

print '"%s" Function called %s time(s). Average execution time: %f sec' % (fn.__name__, iterations, _sum / iterations)
return _result

return wrapper

return decorator
```

#### Example

```@measure()
def my_function():
...
```

The tested function is called once, but we can increase the number of iterations to get a more precise value. this can be achieved by changing the value of the decorator's argument

```@measure(1000)
def my_function():
...
```

#### Example of use

Let's say we have a scene containing 2000 different objects and we want to sort them (in the object manager) by type, and by name (if they have the same type).

In the following source code we test 3 sorting algorithms:

• Python default sort algorithm (Timsort)
• The Insertion sort
• The Bubble sort
the goal is to know which one is the fastest.

```import c4d

def measure(iterations=1):
"""
This decorator allows you to print the average execution time of a function
:param iterations: number of times that function will be called
:return: fn
"""
import time

def decorator(fn):
def wrapper(*args, **kwargs):
_sum = 0.0
_result = None
for i in xrange(iterations):
_start = time.time()
_result = fn(*args, **kwargs)
_sum += time.time() - _start

print '"%s" Function called %s time(s). Average execution time: %f sec' % (fn.__name__, iterations, _sum / iterations)
return _result
return wrapper
return decorator

def gt(a, b):
""" Checks that A value is greater than B """
type_a = a.GetType()
type_b = b.GetType()

if type_a == type_b:
return a.GetName() > b.GetName()

return a.GetType() > b.GetType()

def insertion_sort(array):
""" This function allows you to sort an array using the insertion sort algorithm """
count = len(array)
for i in xrange(count):
current_value = array[i]
pos = i
while pos > 0 and gt(array[pos - 1], current_value):
array[pos] = array[pos - 1]
pos = pos - 1
array[pos] = current_value
return array

def bubble_sort(array):
""" This function allows you to sort an array using the bubble sort algorithm """
def swap(a, b):
array[a], array[b] = array[b], array[a]

count = len(array)
swapped = True
idx = -1
while swapped:
swapped = False
idx = idx + 1
for i in xrange(1, count - idx):
if gt(array[i - 1], array[i]):
swap(i - 1, i)
swapped = True

return array

def _cmp(obj_a, obj_b):
return cmp(obj_a.GetType(), obj_b.GetType()) or cmp(obj_a.GetName(), obj_b.GetName())

def update_manager(doc, object_list):
""" Updates all objects position in the object manager """
doc.StartUndo()
for obj in object_list:
obj.Remove()
doc.InsertObject(obj, None, None)

doc.EndUndo()

@measure()
def main():

obj = doc.GetFirstObject()
if not obj: return

objects = [obj]
obj = obj.GetNext()
while obj:
objects.append(obj)
obj = obj.GetNext()

# Default sorting
objects.sort(cmp=_cmp)

# Insertion sorting
# objects = insertion_sort(objects)

# Bubble sorting
# objects = bubble_sort(objects)

objects.reverse()
update_manager(doc, objects)

if __name__ == '__main__':
main()

```

#### The results 