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 :

  1. Add the following snippet to your source code
  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:
        doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)
        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)
    
    c4d.EventAdd()


if __name__ == '__main__':
    main()


The results

No comments

Post a Comment