Repository: Zuzu-Typ/PyGLM (language: python)

Issue:
measure_function_glm
In `PyGLM vs NumPy.py` have you considered compensating for how much time is spent in the NumPy/PyGLM functions versus the ancillary measurement bits: per-iteration `time.time`, formatting, printing, etc. For example, here is my dot product measurement:
```
| dot product                            |    6.623M  |    1.512M  |     4.38x |
```

Commenting out `func(*args, **kw)` and executing again:
```
| dot product                            |    8.303M  |    8.282M  |     1.00x |
```

This seems to suggest the measurement function is unintentionally a bottleneck for the libraries actual (or potential) throughput.

Thanks.


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 9e9c0b7..e8d7a08 100644
--- a/README.md
+++ b/README.md
@@ -132,37 +132,67 @@ Take a look at the [examples](https://github.com/Zuzu-Typ/LearnOpenGL-Python) fr
 ![LearnOpenGL](https://raw.githubusercontent.com/Zuzu-Typ/PyGLM/master/LearnOpenGL.png)  
   
 ### Speed comparison to numpy  
+The following is the output generated by [test/PyGLM vs Numpy\.py](test/PyGLM%20vs%20NumPy.py)  
 ``` 
-The following chart shows the results of running "PyGLM vs NumPy.py".
-Each of the instructions ran for 1 second to get more accurate results.
+Evaluating performance of PyGLM compared to NumPy.
+
+Running on platform 'win32'.
+
+Python version:
+3.9.0 (tags/v3.9.0:9cf6752, Oct  5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)]
+
+Comparing the following module versions:
+PyGLM (DEFAULT) version 2.4.0
+ vs
+NumPy 1.21.2
+________________________________________________________________________________
+
+The following table shows information about a task to be achieved and the time
+it took when using the given module. Lower time is better.
+Each task is repeated five times per module, only showing the best (i.e. lowest)
+value.
+
 
 +----------------------------------------+------------+------------+-----------+
-| Description                            | PyGLM runs | NumPy runs | ratio     |
+| Description                            | PyGLM time | NumPy time |     ratio |
 +----------------------------------------+------------+------------+-----------+
-| 3 component vector creation            |    3.031M  |    2.426M  |     1.25x |
+| 3 component vector creation            |            |            |           |
+| (100,000 times)                        |        8ms |       19ms |     2.52x |
 +----------------------------------------+------------+------------+-----------+
 | 3 component vector creation with       |            |            |           |
-| custom components                      |    2.730M  |    1.046M  |     2.61x |
+| custom components                      |            |            |           |
+| (50,000 times)                         |        8ms |       36ms |     4.47x |
++----------------------------------------+------------+------------+-----------+
+| dot product                            |            |            |           |
+| (50,000 times)                         |        3ms |       51ms |    15.76x |
 +----------------------------------------+------------+------------+-----------+
-| dot product                            |    3.360M  |  702,325   |     4.79x |
+| cross product                          |            |            |           |
+| (25,000 times)                         |        2ms |      538ms |   243.93x |
 +----------------------------------------+------------+------------+-----------+
-| cross product                          |    3.271M  |   45,358   |    72.14x |
+| L2-Norm of 3 component vector          |            |            |           |
+| (100,000 times)                        |        6ms |      317ms |    57.05x |
 +----------------------------------------+------------+------------+-----------+
-| L2-Norm of 3 component vector          |    3.605M  |  284,225   |    12.69x |
+| 4x4 matrix creation                    |            |            |           |
+| (50,000 times)                         |        4ms |       10ms |     2.35x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix creation                    |    3.238M  |    2.371M  |     1.37x |
+| 4x4 identity matrix creation           |            |            |           |
+| (100,000 times)                        |        7ms |      183ms |    27.83x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 identity matrix creation           |    3.203M  |  487,035   |     6.58x |
+| 4x4 matrix transposition               |            |            |           |
+| (50,000 times)                         |        5ms |       33ms |     6.86x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix transposition               |    3.115M  |    1.176M  |     2.65x |
+| 4x4 multiplicative inverse             |            |            |           |
+| (50,000 times)                         |        5ms |     1627ms |   322.14x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix multiplicative inverse      |    3.116M  |  189,735   |    16.43x |
+| 3 component vector addition            |            |            |           |
+| (100,000 times)                        |        4ms |       40ms |     9.26x |
 +----------------------------------------+------------+------------+-----------+
-| 3 component vector addition            |    2.921M  |    1.310M  |     2.23x |
+| 4x4 matrix multiplication              |            |            |           |
+| (100,000 times)                        |        8ms |       40ms |     5.13x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix multiplication              |    2.454M  |    1.276M  |     1.92x |
+| 4x4 matrix x vector multiplication     |            |            |           |
+| (100,000 times)                        |        6ms |      119ms |    21.61x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix - 4 component vector        |            |            |           |
-| multiplication                         |    2.825M  |  615,594   |     4.59x |
+| TOTAL                                  |      0.07s |      3.01s |    46.17x |
 +----------------------------------------+------------+------------+-----------+
  ```
\ No newline at end of file
diff --git a/README.sb b/README.sb
index 9902be4..491cc48 100644
--- a/README.sb
+++ b/README.sb
@@ -130,37 +130,67 @@ Take a look at the \url https://github.com/Zuzu-Typ/LearnOpenGL-Python\examples\
 \raw\![LearnOpenGL](https://raw.githubusercontent.com/Zuzu-Typ/PyGLM/master/LearnOpenGL.png)\raw\
 
 \h3 \Speed comparison to numpy\ h3\
+The following is the output generated by \url test/PyGLM%20vs%20NumPy.py \test/PyGLM vs Numpy.py\ url\
 \code \
-The following chart shows the results of running "PyGLM vs NumPy.py".
-Each of the instructions ran for 1 second to get more accurate results.
+Evaluating performance of PyGLM compared to NumPy.
+
+Running on platform 'win32'.
+
+Python version:
+3.9.0 (tags/v3.9.0:9cf6752, Oct  5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)]
+
+Comparing the following module versions:
+PyGLM (DEFAULT) version 2.4.0
+ vs
+NumPy 1.21.2
+________________________________________________________________________________
+
+The following table shows information about a task to be achieved and the time
+it took when using the given module. Lower time is better.
+Each task is repeated five times per module, only showing the best (i.e. lowest)
+value.
+
 
 +----------------------------------------+------------+------------+-----------+
-| Description                            | PyGLM runs | NumPy runs | ratio     |
+| Description                            | PyGLM time | NumPy time |     ratio |
 +----------------------------------------+------------+------------+-----------+
-| 3 component vector creation            |    3.031M  |    2.426M  |     1.25x |
+| 3 component vector creation            |            |            |           |
+| (100,000 times)                        |        8ms |       19ms |     2.52x |
 +----------------------------------------+------------+------------+-----------+
 | 3 component vector creation with       |            |            |           |
-| custom components                      |    2.730M  |    1.046M  |     2.61x |
+| custom components                      |            |            |           |
+| (50,000 times)                         |        8ms |       36ms |     4.47x |
++----------------------------------------+------------+------------+-----------+
+| dot product                            |            |            |           |
+| (50,000 times)                         |        3ms |       51ms |    15.76x |
 +----------------------------------------+------------+------------+-----------+
-| dot product                            |    3.360M  |  702,325   |     4.79x |
+| cross product                          |            |            |           |
+| (25,000 times)                         |        2ms |      538ms |   243.93x |
 +----------------------------------------+------------+------------+-----------+
-| cross product                          |    3.271M  |   45,358   |    72.14x |
+| L2-Norm of 3 component vector          |            |            |           |
+| (100,000 times)                        |        6ms |      317ms |    57.05x |
 +----------------------------------------+------------+------------+-----------+
-| L2-Norm of 3 component vector          |    3.605M  |  284,225   |    12.69x |
+| 4x4 matrix creation                    |            |            |           |
+| (50,000 times)                         |        4ms |       10ms |     2.35x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix creation                    |    3.238M  |    2.371M  |     1.37x |
+| 4x4 identity matrix creation           |            |            |           |
+| (100,000 times)                        |        7ms |      183ms |    27.83x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 identity matrix creation           |    3.203M  |  487,035   |     6.58x |
+| 4x4 matrix transposition               |            |            |           |
+| (50,000 times)                         |        5ms |       33ms |     6.86x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix transposition               |    3.115M  |    1.176M  |     2.65x |
+| 4x4 multiplicative inverse             |            |            |           |
+| (50,000 times)                         |        5ms |     1627ms |   322.14x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix multiplicative inverse      |    3.116M  |  189,735   |    16.43x |
+| 3 component vector addition            |            |            |           |
+| (100,000 times)                        |        4ms |       40ms |     9.26x |
 +----------------------------------------+------------+------------+-----------+
-| 3 component vector addition            |    2.921M  |    1.310M  |     2.23x |
+| 4x4 matrix multiplication              |            |            |           |
+| (100,000 times)                        |        8ms |       40ms |     5.13x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix multiplication              |    2.454M  |    1.276M  |     1.92x |
+| 4x4 matrix x vector multiplication     |            |            |           |
+| (100,000 times)                        |        6ms |      119ms |    21.61x |
 +----------------------------------------+------------+------------+-----------+
-| 4x4 matrix - 4 component vector        |            |            |           |
-| multiplication                         |    2.825M  |  615,594   |     4.59x |
+| TOTAL                                  |      0.07s |      3.01s |    46.17x |
 +----------------------------------------+------------+------------+-----------+
 \ code\
\ No newline at end of file


Test patch:
diff --git a/test/PyGLM vs NumPy.py b/test/PyGLM vs NumPy.py
index 1a47492..d72634b 100644
--- a/test/PyGLM vs NumPy.py
+++ b/test/PyGLM vs NumPy.py
@@ -1,168 +1,242 @@
-from threading import Thread
-
-import time, math
-
-glm_counter = 0
-numpy_counter = 0
-
-glm_time = 0
-numpy_time = 0
-
-test_duration = 1
-
-display_update_delay = 1 / 10
-
-results = []
-
-def measure_function_glm(func, *args, **kw):
-    global glm_counter
-    glm_counter = 0
-    start = time.time()
-    last_print = 0
-    while True:
-        func(*args, **kw)
-        glm_counter += 1
-        now = time.time()
-        if now >= start + test_duration:
-            break
-        if now - last_print > display_update_delay:
-            last_print = now
-            print("\rPyGLM: {: 9d}x".format(glm_counter), end="")
-    print("\rPyGLM: {: 9d}x".format(glm_counter))
-
-def measure_function_numpy(func, *args, **kw):
-    global numpy_counter
-    numpy_counter = 0
-    start = time.time()
-    last_print = 0
-    while True:
-        func(*args, **kw)
-        numpy_counter += 1
-        now = time.time()
-        if now >= start + test_duration:
-            break
-        if now - last_print > display_update_delay:
-            last_print = now
-            print("\rNumPy: {: 9d}x".format(numpy_counter), end="")
-    print("\rNumPy: {: 9d}x".format(numpy_counter))
-
-"""
-def print_percentage():
-    global glm_counter, numpy_counter
-    start = time.time()
-    last_print = 0
-    while glm_counter < repititions or numpy_counter < repititions:
-        print("\rPyGLM: {: 4d}%, NumPy: {: 4d}%".format(int(glm_counter / repititions * 100), int(numpy_counter / repititions * 100)), end="")
-    print("\rPyGLM: {: 4d}%, NumPy: {: 4d}%".format(int(glm_counter / repititions * 100), int(numpy_counter / repititions * 100)))
-"""
-
-def arg_to_string(arg):
-    if isinstance(arg, type):
-        return "{}.{}".format(arg.__module__, arg.__name__)
-    return repr(arg)
-
-def get_evaluation_string(glm_counter, numpy_counter):
-    if (glm_counter > numpy_counter):
-        return "PyGLM was {:.2f}x as fast as NumPy".format(glm_counter / numpy_counter)
-    else:
-        return "NumPy was {:.2f}x as fast as PyGLM".format(numpy_counter / glm_counter)
-        
-def test_func(description, glm_func, glm_args, numpy_func, numpy_args):
-    print("-"*80)
-    print("Comparing {}.\n".format(description))
-    print("PyGLM instruction:\n\t{}.{}({})\n".format(glm_func.__module__, glm_func.__name__, ", ".join([arg_to_string(x) for x in glm_args])))
-    print("NumPy instruction:\n\t{}.{}({})\n".format(numpy_func.__module__, numpy_func.__name__, ", ".join([arg_to_string(x) for x in numpy_args])))
-    print("Running for {} seconds...".format(test_duration))
-    measure_function_glm(glm_func, *glm_args)
-    measure_function_numpy(numpy_func, *numpy_args)
-    #Thread(target=measure_function_glm, args=[glm_func] + list(glm_args)).start()
-    #Thread(target=measure_function_numpy, args=[numpy_func] + list(numpy_args)).start()
-    #print_percentage()
-    print("\nTimes ran:\n\tPyGLM: {:.2f}x\n\tNumPy: {:.2f}x\n".format(glm_counter, numpy_counter))
-    print("{}.\n".format(get_evaluation_string(glm_counter, numpy_counter)))
-
-    results.append((description, glm_counter, numpy_counter))
-
-def test_operation(description, operation, format_, glm_args, numpy_args):
-    print("-"*80)
-    print("Comparing {}.\n".format(description))
-    print("PyGLM instruction:\n\t{}\n".format(format_.format(*[arg_to_string(x) for x in glm_args])))
-    print("NumPy instruction:\n\t{}\n".format(format_.format(*[arg_to_string(x) for x in numpy_args])))
-    print("Running for {} second(s)...".format(test_duration))
-    measure_function_glm(operation, *glm_args)
-    measure_function_numpy(operation, *numpy_args)
-    #Thread(target=measure_function_glm, args=[operation] + list(glm_args)).start()
-    #Thread(target=measure_function_numpy, args=[operation] + list(numpy_args)).start()
-    #print_percentage()
-    print("\nTimes ran:\n\tPyGLM: {:.2f}x\n\tNumPy: {:.2f}x\n".format(glm_counter, numpy_counter))
-    print("{}.\n".format(get_evaluation_string(glm_counter, numpy_counter)))
-
-    results.append((description, glm_counter, numpy_counter))
-
-def print_table_row(descr, glm_time, numpy_time, ratio):
-    while len(descr) > 38:
-        last_space = descr[:38].rfind(" ")
-        print(("| {} | " + " " * 10 + " | " + " " * 10 + " | " + " " * 9 + " |").format(descr[:last_space] + (38 - last_space) * " "))
-        descr = descr[last_space + 1:]
-    print("| {} | {} | {} | {} |".format(descr + (38 - len(descr)) * " ", glm_time, numpy_time, ratio))
+import timeit, sys
+import glm, numpy
+
+ON_WINDOWS = sys.platform == 'win32'
+
+print_horizontal_rule = lambda: print("+----------------------------------------+------------+------------+-----------+")
+
+pad_with_spaces = lambda text, length, align="left": text + " " * (length - len(text)) if align == "left" else " " * (length - len(text)) + text
+
+seconds_to_milliseconds = lambda seconds: int(round(seconds * 1000, 0))
+
+pyglm_total_time = 0
+numpy_total_time = 0
+
+def word_wrap(text, max_length):
+    out = []
+    current_word = []
+    current_length = 0
+    for c in text:
+        if current_length >= max_length:
+            out.append("\n")
+            current_length = len(current_word)
+
+        if c == " ":
+            out.append("".join(current_word))
+            if (current_length < max_length):
+                out.append(" ")
+            current_word = []
+        elif c == "\n":
+            out.append("".join(current_word))
+            out.append("\n")
+            current_word = []
+            current_length = 0
+        else:
+            current_word.append(c)
+
+        current_length += 1
+
+    if current_word:
+        out.append("".join(current_word))
+
+    return "".join(out).rstrip()
+
+def print_row(descr, pyglm, numpy, ratio, print_header=True, end="\n"):
+    descr = word_wrap(descr, 38)
+
+    descr_lines = descr.split("\n")
+
+    if print_header:
+        for line in descr_lines[:-1]:
+            print(f"| {pad_with_spaces(line, 38)} | {' ' * 10} | {' ' * 10} | {' ' * 9} |")
+
+    descr_last_line = descr_lines[-1]
+    
+    print(f"| {pad_with_spaces(descr_last_line, 38)} | {pad_with_spaces(pyglm, 10, align='right')} | {pad_with_spaces(numpy, 10, align='right')} | {pad_with_spaces(ratio, 9, align='right')} |", end=end)
+
+def run_test(descr, pyglm_setup_code, pyglm_code, numpy_setup_code, numpy_code, number):
+    global pyglm_total_time, numpy_total_time
+
+    descr += f"\n({number:,} times)"
+    
+    if ON_WINDOWS:
+        print_row(descr, "", "", "", end="\r")
+    
+    pyglm_result = min(timeit.repeat(pyglm_code, pyglm_setup_code, number=number))
     
-def format_large_num(num):
-    log10 = int(math.log10(num))
-    if log10 <= 5:
-        out = "{:,} ".format(num)
-    elif log10 <= 8:
-        out = "{:,}M".format(num // 1000).replace(",", ".")
-    else:
-        out = "{:,}B".format(num // 1000000).replace(",", ".")
-        
-    return " " * (9 - len(out)) + out
+    if ON_WINDOWS:
+        print_row(descr, "{}ms".format(seconds_to_milliseconds(pyglm_result)), "", "", end="\r", print_header=False)
     
+    numpy_result = min(timeit.repeat(numpy_code, numpy_setup_code, number=number))
 
-def print_results():
-    for description, glm_counter, numpy_counter in results:
-        print_table_row(description, "{} ".format(format_large_num(glm_counter)), "{} ".format(format_large_num(numpy_counter)), "{: 8.2f}x".format(glm_counter / numpy_counter))
-        print("+----------------------------------------+------------+------------+-----------+")
+    pyglm_total_time += pyglm_result
+    numpy_total_time += numpy_result
     
-import glm, numpy       
-"""
-print("-"*80)
-print("Comparing import speed.")
-start = time.time()
-import glm
-glm_time = time.time() - start
-
-start = time.time()
-import numpy
-numpy_time = time.time() - start
-
-results.append(("import", numpy_time, glm_time))
-
-print("\nTime taken:\n\tPyGLM: {:.2f}s\n\tNumPy: {:.2f}s\n".format(glm_time, numpy_time))
-print("{}.\n".format(get_evaluation_string(numpy_time, glm_time)))
-"""
-
-test_func("3 component vector creation", glm.vec3, [0], numpy.zeros, [(3,), numpy.float32])
-test_func("3 component vector creation with custom components", glm.vec3, [1, 2, 3], numpy.array, [(1, 2, 3), numpy.float32])
-test_func("dot product", glm.dot, [glm.vec3(), glm.vec3()], numpy.dot, [numpy.zeros((3,)), numpy.zeros((3,))])
-repititions = 100_000
-test_func("cross product", glm.cross, [glm.vec3(1), glm.vec3(1,2,3)], numpy.cross, [numpy.array((1,1,1), numpy.float32), numpy.array((1,2,3), numpy.float32)])
-test_func("L2-Norm of 3 component vector", glm.l2Norm, [glm.vec3(1,2,3)], numpy.linalg.norm, [numpy.array((1,2,3), numpy.float32)])
-repititions = 1_000_000
-test_func("4x4 matrix creation", glm.mat4, [0], numpy.zeros, [(4,4), numpy.float32])
-test_func("4x4 identity matrix creation", glm.identity, [glm.mat4], numpy.identity, [4, numpy.float32])
-test_func("4x4 matrix transposition", glm.transpose, [glm.mat4()], numpy.transpose, [numpy.identity(4, numpy.float32)])
-repititions = 100_000
-test_func("4x4 matrix multiplicative inverse", glm.inverse, [glm.mat4()], numpy.linalg.inv, [numpy.identity(4, numpy.float32)])
-repititions = 1_000_000
-test_operation("3 component vector addition", lambda x, y: (x+y), "{} + {}", [glm.vec3(), glm.vec3()], [numpy.zeros(3, numpy.float32), numpy.zeros(3, numpy.float32)])
-test_operation("4x4 matrix multiplication", lambda x, y: (x*y), "{} * {}", [glm.mat4(), glm.mat4()], [numpy.identity(4, numpy.float32), numpy.identity(4, numpy.float32)])
-test_operation("4x4 matrix - 4 component vector multiplication", lambda x, y: (x*y), "{} * {}", [glm.mat4(), glm.vec4()], [numpy.identity(4, numpy.float32), numpy.zeros(4, numpy.float32)])
-
-print("#" *80)
-print("RESULTS:\n")
-
-print("+----------------------------------------+------------+------------+-----------+")
-print("| Description                            | PyGLM runs | NumPy runs | ratio     |")
-print("+----------------------------------------+------------+------------+-----------+")
-
-print_results()
+    print_row(descr, "{:d}ms".format(seconds_to_milliseconds(pyglm_result)), "{}ms".format(seconds_to_milliseconds(numpy_result)),  "{:.02f}x".format(numpy_result / pyglm_result), print_header=not ON_WINDOWS)
+    print_horizontal_rule()
+
+
+############################
+# Actual tests start here: #
+############################
+
+print(f"""Evaluating performance of PyGLM compared to NumPy.
+
+Running on platform '{sys.platform}'.
+
+Python version:
+{sys.version}
+
+Comparing the following module versions:
+{glm.version}
+ vs
+NumPy {numpy.__version__}
+________________________________________________________________________________
+
+The following table shows information about a task to be achieved and the time
+it took when using the given module. Lower time is better.
+Each task is repeated five times per module, only showing the best (i.e. lowest)
+value.
+
+""")
+
+print_horizontal_rule()
+print_row("Description", "PyGLM time", "NumPy time", "ratio")
+print_horizontal_rule()
+
+run_test("3 component vector creation",
+         
+         "import glm",
+         "glm.vec3()",
+
+         "import numpy",
+         "numpy.zeros((3,), numpy.float32)",
+
+         100000
+)
+
+run_test("3 component vector creation with custom components",
+         
+         "import glm",
+         "glm.vec3(1,2,3)",
+
+         "import numpy",
+         "numpy.array((1,2,3), numpy.float32)",
+
+         50000
+)
+
+run_test("dot product",
+         
+         "import glm; v1 = glm.vec3(); v2 = glm.vec3()",
+         "glm.dot(v1, v2)",
+         
+         "import numpy; v1 = numpy.zeros((3,), numpy.float32); v2 = numpy.zeros((3,), numpy.float32)",
+         "numpy.dot(v1, v2)",
+
+         50000
+)
+
+run_test("cross product",
+         
+         "import glm; v1 = glm.vec3(1); v2 = glm.vec3(1,2,3)",
+         "glm.cross(v1, v2)",
+
+         "import numpy; v1 = numpy.array((1,1,1), numpy.float32); v2 = numpy.array((1,2,3), numpy.float32)",
+         "numpy.cross(v1, v2)",
+
+         25000
+)
+
+run_test("L2-Norm of 3 component vector",
+         
+         "import glm; v = glm.vec3(1,2,3)",
+         "glm.l2Norm(v)",
+         
+         "import numpy; v = numpy.array((1,1,1), numpy.float32)",
+         "numpy.linalg.norm(v)",
+
+         100000
+)
+
+run_test("4x4 matrix creation",
+         
+         "import glm",
+         "glm.mat4(0)",
+         
+         "import numpy",
+         "numpy.zeros((4,4), numpy.float32)",
+
+         50000
+)
+run_test("4x4 identity matrix creation",
+         
+         "import glm",
+         "glm.mat4()",
+         
+         "import numpy",
+         "numpy.identity(4, numpy.float32)",
+
+         100000
+)
+
+run_test("4x4 matrix transposition",
+         
+         "import glm; m = glm.mat4()",
+         "glm.transpose(m)",
+         
+         "import numpy; m = numpy.identity(4, numpy.float32)",
+         "numpy.transpose(m)",
+
+         50000
+)
+
+run_test("4x4 multiplicative inverse",
+         
+         "import glm; m = glm.mat4()",
+         "glm.inverse(m)",
+         
+         "import numpy; m = numpy.identity(4, numpy.float32)",
+         "numpy.linalg.inv(m)",
+
+         50000
+)
+
+run_test("3 component vector addition",
+         
+         "import glm; v1 = glm.vec3(1); v2 = glm.vec3(1,2,3)",
+         "v1 + v2",
+         
+         "import numpy; v1 = numpy.array((1,1,1), numpy.float32); v2 = numpy.array((1,2,3), numpy.float32)",
+         "v1 + v2",
+
+         100000
+)
+
+run_test("4x4 matrix multiplication",
+         
+         "import glm; m1 = glm.mat4(); m2 = glm.mat4(2)",
+         "m1 * m2",
+         
+         "import numpy; m1 = numpy.identity(4, numpy.float32); m2 = numpy.identity(4, numpy.float32) * 2",
+         "m1 * m2",
+
+         100000
+)
+
+run_test("4x4 matrix x vector multiplication",
+         
+         "import glm; m = glm.mat4(); v = glm.vec4()",
+         "m * v",
+         
+         "import numpy; m = numpy.identity(4, numpy.float32); v = numpy.zeros((4,), numpy.float32)",
+         "m * v",
+
+         100000
+)
+
+print_row("TOTAL",
+          "{:.02f}s".format(pyglm_total_time),
+          "{:.02f}s".format(numpy_total_time),
+          "{:.02f}x".format(numpy_total_time / pyglm_total_time))
+print_horizontal_rule()