plain-vanilla-games/schematics — pull request #17 by johannth (2013-11-02 13:00:27+00:00)
Title: Optimizing serialize

In order to figure out if serialize could be optimised I created a simple benchmark which serializes a lot of rather complex models (with a nested structure containing other models). The benchmark intentionally doesn't fill the models with data as I was trying to optimise the overall structure of the serialization (and not many of the types override `to_primitive`. We could improve the benchmark and fill the model with data to see if `to_primitive` methods are really affecting the serialization.

I made the following changes:

1. Removed filter_by_role and made serialize call a new to_primitive_with_filter. This removes a lot of duplication as to_primitive and filter_by_role where both looping through all the fields and to_primitive was converting objects to primitives that were then discarded by the filter.
2. Removed the value argument to a role filter. We were never using it and it had some overhead.
3. Reduced some function calls in favour of cached bools.

The results from the benchmarks are as follows:

Without any refactoring

```
$ python tests/serialization_benchmark.py
8050001 function calls (7810001 primitive calls) in 4.774 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
1250000 0.877 0.000 1.856 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:294()
120000/10000 0.688 0.000 2.403 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:131(filter_by_role)
140000/10000 0.600 0.000 2.256 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:116(to_primitive)
990000 0.348 0.000 0.687 0.000 {getattr}
990000 0.340 0.000 0.340 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:18(__get__)
260000 0.325 0.000 0.647 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:285(iter)
990000 0.291 0.000 0.979 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:296(__getitem__)
690000 0.173 0.000 0.173 0.000 {isinstance}
260000 0.155 0.000 0.280 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:127(iteritems)
260000 0.125 0.000 0.125 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:171(itervalues)
270000 0.106 0.000 0.128 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:69(whitelist)
270000 0.103 0.000 0.230 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:61(__call__)
10000 0.077 0.000 0.077 0.000 {method 'strftime' of 'datetime.date' objects}
260000 0.068 0.000 0.068 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:14(should_serialize_if_none)
10000 0.068 0.000 1.216 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:257(filter_by_role)
120000 0.060 0.000 0.060 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:81(_configure_filter)
20000 0.053 0.000 1.023 0.000 {map}
280000 0.045 0.000 0.045 0.000 {method 'iteritems' of 'dict' objects}
10000 0.043 0.000 0.088 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:50(__init__)
10000 0.043 0.000 0.043 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:100(__init__)
20000 0.041 0.000 0.048 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:328(to_primitive)
200000 0.029 0.000 0.029 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:82()
180000 0.024 0.000 0.024 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:126(to_primitive)
270000 0.022 0.000 0.022 0.000 {len}
10000 0.022 0.000 4.769 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:109(serialize)
20000 0.011 0.000 1.033 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:254(to_primitive)
10000 0.010 0.000 0.088 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:521(to_primitive)
10000 0.009 0.000 0.009 0.000 {zip}
60000 0.006 0.000 0.006 0.000 {method 'get' of 'dict' objects}
20000 0.005 0.000 0.005 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:329()
10000 0.005 0.000 4.774 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:220(serialize)
10000 0.002 0.000 0.002 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:126(fields)
10000 0.001 0.000 0.001 0.000 {method 'pop' of 'dict' objects}
10000 0.001 0.000 0.001 0.000 {callable}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
```

After combining to_primitive and filter_by_role

```
$ python tests/serialization_benchmark.py
5280001 function calls (5170001 primitive calls) in 3.185 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
120000/10000 0.785 0.000 3.076 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:90(filter_to_primitives)
590000 0.440 0.000 0.934 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:294()
470000 0.243 0.000 0.503 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:97()
470000 0.176 0.000 0.176 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:18(__get__)
470000 0.171 0.000 0.347 0.000 {getattr}
120000 0.167 0.000 0.316 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:285(iter)
470000 0.147 0.000 0.494 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:296(__getitem__)
270000 0.110 0.000 0.132 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:69(whitelist)
270000 0.100 0.000 0.233 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:61(__call__)
370000 0.100 0.000 0.100 0.000 {isinstance}
10000 0.075 0.000 0.075 0.000 {method 'strftime' of 'datetime.date' objects}
120000 0.073 0.000 0.132 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:127(iteritems)
10000 0.072 0.000 1.544 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:241(filter_to_primitives)
110000 0.070 0.000 1.453 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:249()
120000 0.060 0.000 0.060 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:81(_configure_filter)
120000 0.059 0.000 0.059 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:171(itervalues)
20000 0.054 0.000 0.066 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:314(filter_to_primitives)
200000 0.053 0.000 0.053 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:14(should_serialize_if_none)
10000 0.042 0.000 0.085 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:50(__init__)
10000 0.040 0.000 0.040 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:100(__init__)
200000 0.028 0.000 0.028 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:82()
270000 0.022 0.000 0.022 0.000 {len}
10000 0.019 0.000 3.180 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:109(serialize)
140000 0.019 0.000 0.019 0.000 {method 'iteritems' of 'dict' objects}
140000 0.018 0.000 0.018 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:126(to_primitive)
100000 0.017 0.000 0.017 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:243(should_include_in_primitives)
10000 0.009 0.000 0.086 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:521(to_primitive)
20000 0.005 0.000 0.005 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:323()
10000 0.005 0.000 3.185 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:220(serialize)
10000 0.002 0.000 0.002 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:126(fields)
10000 0.001 0.000 0.001 0.000 {method 'pop' of 'dict' objects}
10000 0.001 0.000 0.001 0.000 {callable}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
```

By removing the functionality of roles having to look at the value of the field.

```
$ python tests/serialization_benchmark.py
4170001 function calls (4060001 primitive calls) in 2.714 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
120000/10000 0.603 0.000 2.604 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:90(to_primitive_with_filter)
490000 0.524 0.000 1.116 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:295()
120000 0.184 0.000 0.357 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:285(iter)
370000 0.143 0.000 0.143 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:18(__get__)
370000 0.133 0.000 0.276 0.000 {getattr}
370000 0.125 0.000 0.401 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:300(__getitem__)
270000 0.101 0.000 0.162 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:61(__call__)
360000 0.095 0.000 0.095 0.000 {isinstance}
120000 0.078 0.000 0.144 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:127(iteritems)
10000 0.074 0.000 1.460 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:214(to_primitive_with_filter)
10000 0.072 0.000 0.072 0.000 {method 'strftime' of 'datetime.date' objects}
120000 0.066 0.000 0.066 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:171(itervalues)
110000 0.065 0.000 1.368 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:222()
120000 0.062 0.000 0.062 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:81(_configure_filter)
270000 0.061 0.000 0.061 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:69(whitelist)
20000 0.054 0.000 0.065 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:287(to_primitive_with_filter)
200000 0.052 0.000 0.052 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:14(should_serialize_if_none)
10000 0.042 0.000 0.086 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:50(__init__)
10000 0.041 0.000 0.041 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:100(__init__)
200000 0.030 0.000 0.030 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:82()
140000 0.022 0.000 0.022 0.000 {method 'iteritems' of 'dict' objects}
10000 0.019 0.000 2.709 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:106(serialize)
140000 0.018 0.000 0.018 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:126(to_primitive)
100000 0.018 0.000 0.018 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:216(should_include_in_primitives)
10000 0.009 0.000 0.082 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:521(to_primitive)
20000 0.008 0.000 0.010 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:37(__len__)
10000 0.005 0.000 2.714 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:220(serialize)
20000 0.005 0.000 0.005 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:296()
20000 0.003 0.000 0.003 0.000 {len}
10000 0.002 0.000 0.002 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:126(fields)
10000 0.001 0.000 0.001 0.000 {method 'pop' of 'dict' objects}
10000 0.001 0.000 0.001 0.000 {callable}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
```

After replacing isinstance calls with a supports_filtering boolean

```
$ python tests/serialization_benchmark.py
3840001 function calls (3730001 primitive calls) in 2.487 seconds

Ordered by: internal time

ncalls tottime percall cumtime percall filename:lineno(function)
120000/10000 0.510 0.000 2.364 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:92(to_primitive_with_filter)
490000 0.506 0.000 1.080 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:295()
120000 0.183 0.000 0.345 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:285(iter)
370000 0.139 0.000 0.139 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:18(__get__)
370000 0.132 0.000 0.271 0.000 {getattr}
370000 0.118 0.000 0.389 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:300(__getitem__)
270000 0.099 0.000 0.158 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:61(__call__)
120000 0.074 0.000 0.134 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:127(iteritems)
10000 0.072 0.000 0.072 0.000 {method 'strftime' of 'datetime.date' objects}
10000 0.071 0.000 1.291 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:218(to_primitive_with_filter)
110000 0.062 0.000 1.202 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:226()
120000 0.060 0.000 0.060 0.000 /Users/johannth/Development/PV/schematics/schematics/datastructures.py:171(itervalues)
270000 0.059 0.000 0.059 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:69(whitelist)
120000 0.059 0.000 0.059 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:83(_configure_filter)
20000 0.054 0.000 0.068 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:281(to_primitive_with_filter)
200000 0.052 0.000 0.052 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:14(should_serialize_if_none)
10000 0.043 0.000 0.100 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:52(__init__)
10000 0.041 0.000 0.041 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:100(__init__)
200000 0.027 0.000 0.027 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:84()
140000 0.020 0.000 0.020 0.000 {method 'iteritems' of 'dict' objects}
10000 0.019 0.000 2.483 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:106(serialize)
100000 0.018 0.000 0.018 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:220(should_include_in_primitives)
140000 0.017 0.000 0.017 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:128(to_primitive)
10000 0.013 0.000 0.053 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:21(__init__)
10000 0.010 0.000 0.082 0.000 /Users/johannth/Development/PV/schematics/schematics/types/base.py:523(to_primitive)
20000 0.007 0.000 0.010 0.000 /Users/johannth/Development/PV/schematics/schematics/serialize.py:37(__len__)
20000 0.006 0.000 0.006 0.000 {isinstance}
20000 0.005 0.000 0.005 0.000 /Users/johannth/Development/PV/schematics/schematics/types/compound.py:290()
10000 0.005 0.000 2.487 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:220(serialize)
20000 0.003 0.000 0.003 0.000 {len}
10000 0.001 0.000 0.001 0.000 /Users/johannth/Development/PV/schematics/schematics/models.py:126(fields)
10000 0.001 0.000 0.001 0.000 {method 'pop' of 'dict' objects}
10000 0.001 0.000 0.001 0.000 {callable}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
```