Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
with
386 additions
and 198 deletions.
- BIN docs/blog/images/type-specialized-instances-after.png
- BIN docs/blog/images/type-specialized-instances-before.png
- +1 −0 docs/blog/index.rst
- +53 −0 docs/blog/type-specialized-instance-variables.rst
- +0 −39 tests/jit/test_basic.py
- +74 −0 tests/jit/test_instance_vars.py
- +17 −15 tests/test_mapdict.py
- +214 −116 topaz/mapdict.py
- +27 −28 topaz/objects/objectobject.py
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,53 @@ | ||
Type Specialized Instance Variables | ||
=================================== | ||
|
||
**Posted: July 13, 2013** | ||
|
||
In Topaz, like most other VMs, all objects are stored in what are called | ||
"boxes". Essentially that means when you have something like ``x = 5``, ``x`` | ||
is really a pointer to an object which contains ``5``, not the value ``5`` | ||
itself. This is often a source of performance problems for VMs, this generates | ||
more garbage for the GC to process and means that to access the value ``5`` | ||
more memory dereferences are needed. Topaz's just-in-time compiler (JIT) is | ||
often able to remove these allocations and memory dereferences in individual | ||
loops or functions, however it's not able to remove them in structures that | ||
stick around in memory, like objects. | ||
|
||
Therefore, over the past week I've been working on an optimization for Topaz | ||
called "type specialized instance variables". Basically what that means is that | ||
Topaz keeps track of what types instance variables in an object tend to have, | ||
and then specializes the storage to remove the indirection for ``Fixnum`` and | ||
``Float`` objects. | ||
|
||
Let's look at an example: | ||
|
||
.. sourcecode:: ruby | ||
|
||
class Point | ||
def initialize(x, y, z) | ||
@x = x | ||
@y = y | ||
@z = z | ||
end | ||
end | ||
|
||
p = Point.new(1, 2, 3) | ||
|
||
Before this optimization, ``p`` looked like this in memory: | ||
|
||
.. image:: images/type-specialized-instances-before.png | ||
|
||
And after the optimization, it looks like this: | ||
|
||
.. image:: images/type-specialized-instances-after.png | ||
|
||
With this optimization landed, Topaz will use less memory and be faster for | ||
programs that store ``Fixnum`` and ``Float`` objects in memory. If you're | ||
interested in this type of optimization you can read about a similar one in | ||
`PyPy for lists`_ that we're in the process of porting to Topaz. | ||
|
||
We're looking forward to doing our first release soon, we hope you'll test | ||
Topaz out, and give us feedback with the `nightly builds`_ until then | ||
|
||
.. _`PyPy for lists`: http://morepypy.blogspot.com/2011/10/more-compact-lists-with-list-strategies.html | ||
.. _`nightly builds`: http://topazruby.com/builds/ |
@@ -1,39 +1,41 @@ | ||
import pytest | ||
|
||
from topaz.mapdict import ClassNode | ||
|
||
from .base import BaseTopazTest | ||
from topaz import mapdict | ||
|
||
|
||
class FakeObject(object): | ||
storage = None | ||
|
||
def __init__(self, map): | ||
self.map = map | ||
self.object_storage = self.unboxed_storage = None | ||
|
||
|
||
class TestMapDict(BaseTopazTest): | ||
class TestMapDict(object): | ||
@pytest.mark.parametrize("i", range(10)) | ||
def test_simple_size_estimation(self, space, i): | ||
class_node = ClassNode(i) | ||
assert class_node.size_estimate() == 0 | ||
class_node = mapdict.ClassNode(i) | ||
assert class_node.size_estimate.object_size_estimate() == 0 | ||
assert class_node.size_estimate.unboxed_size_estimate() == 0 | ||
|
||
for j in range(1000): | ||
w_obj = FakeObject(class_node) | ||
for a in "abcdefghij"[:i]: | ||
w_obj.map.add_attr(space, w_obj, a) | ||
assert class_node.size_estimate() == i | ||
w_obj.map = w_obj.map.add(space, mapdict.ObjectAttributeNode, a, w_obj) | ||
assert class_node.size_estimate.object_size_estimate() == i | ||
assert class_node.size_estimate.unboxed_size_estimate() == 0 | ||
|
||
@pytest.mark.parametrize("i", range(1, 10)) | ||
def test_avg_size_estimation(self, space, i): | ||
class_node = ClassNode(i) | ||
assert class_node.size_estimate() == 0 | ||
class_node = mapdict.ClassNode(i) | ||
assert class_node.size_estimate.object_size_estimate() == 0 | ||
assert class_node.size_estimate.unboxed_size_estimate() == 0 | ||
|
||
for j in range(1000): | ||
w_obj = FakeObject(class_node) | ||
for a in "abcdefghij"[:i]: | ||
w_obj.map.add_attr(space, w_obj, a) | ||
w_obj.map = w_obj.map.add(space, mapdict.ObjectAttributeNode, a, w_obj) | ||
w_obj = FakeObject(class_node) | ||
for a in "klmnopqars": | ||
w_obj.map.add_attr(space, w_obj, a) | ||
assert class_node.size_estimate() in [(i + 10) // 2, (i + 11) // 2] | ||
w_obj.map = w_obj.map.add(space, mapdict.ObjectAttributeNode, a, w_obj) | ||
|
||
assert class_node.size_estimate.object_size_estimate() in [(i + 10) // 2, (i + 11) // 2] | ||
assert class_node.size_estimate.unboxed_size_estimate() == 0 |
Oops, something went wrong.