Live programming, originally introduced by Smalltalk and Lisp, and now gaining popularity in contemporary systems such as Swift, requires on-the-fly support for object schema migration, such that the layout of objects may be changed while the program is at one and the same time being run and developed. In Smalltalk schema migration is supported by two primitives, one that answers a collection of all instances of a class, and one that exchanges the identities of pairs of objects, called the become primitive. Existing instances are collected, copies are created using the new schema with state copied from the corresponding existing instance, and all pairs of instances are exchanged with become, effecting the schema migration.
Historically the implementation of become has either required an extra level of indirection between an object’s address and its body, slowing down slot access, or has required a sweep of all objects, a very slow operation on large heaps. Spur, a new object representation and memory manager for Smalltalk-like languages, has neither of these deficiencies. It uses direct pointers but still provides a fast become operation in large heaps, thanks to forwarding objects that when read conceptually answer another object and a partial read barrier that avoids the cost of explicitly checking for forwarding objects on the vast majority of object accesses.
2. Myself
• Clément Béra
• 2 years virtual machine engineer in the
Pharo team
• Phd student for the past year
• JIT compiler
• Memory management
3. Paper
Eliot Miranda, Clément Béra,
A Partial Read Barrier for Efficient Support of
Live Object-oriented Programming,
International Symposium on Memory
Management 2015
4. Plan
• What is schema migration
• Existing implementations
• Spur’s partial read barrier
7. Schema Migration
• 1) Creation of a new class and new
subclasses
• 2) Creation of new instances
• 3) Migration of the instances
8. 1. Class Creation
• Person has no subclasses
• Creation of the new class Person with two
instance variables
9. 1I. Instances Creation
• A new instance of Person is created for each
existing instance
• New instances have an additional slot in
memory
10. III. Migration
• All the references to the old instances
now refers to the new instances
• How is that implemented ?
11. III. Migration (become)
• Conceptually, become exchanges the
references of two objects
• If a becomes b, all the references to a
now refer to b and all the references to b
now refer to a
19. Full Heap Scan
new John header
name = 'John'
address = nil
old John header
name = 'John'
an Object
d field 1
pointer to
chunks of memory
with unknown data
new version of
the John object
old version of
the John object
Heap portion
20. Full Heap Scan
new John header
name = 'John'
address = nil
old John header
name = 'John'
an Object
d field 1
pointer to
chunks of memory
with unknown data
new version of
the John object
old version of
the John object
Heap portion
new John header
name = 'John'
address = nil
old John header
name = 'John'
an Object
d field 1
pointer to
chunks of memory
with unknown data
new version of
the John object
old version of
the John object
Heap portion
21. Pros And Cons
• Object in 1 chunk of memory with direct
pointers: faster field access
• Become performance is relative to the
heap size
24. Object Table
• Each reference to an object refers to an
entry in a table with all objects
25. Xerox Dorado Example
old John header
name = 'John'
an Object
d field 1
Heap portionObject table
object metadata object
address
0x0000143e
pointer to
chunks of memory
with unknown data
conceptual pointer
refCount gc type bank
10100101 101 1 1010
29. Split Memory Representation
• Advanced version of the object table
• Implemented in ???
• I heard a guy who knows a guy that
whispered once that it was
implemented like that inVW, but who
knows for sure?
30. Split Memory Representation
• The object is split in memory in 2 parts:
• fixed sized header with a pointer to its
fields
• fields
31. Split Memory Representation
John header
name = 'John'
pointer to
chunks of memory
with unknown data
John object
Heap portion
fields
33. Split Memory
new John header
name = 'John'
address = nil
old John header
name = 'John'
an Object
field 1
pointer to
chunks of memory
with unknown data
new version of
the John object
old version of
the John object
Heap portion
fields
fields
34. Split Memory
new John header
name = 'John'
address = nil
old John header
name = 'John'
an Object
field 1
pointer to
chunks of memory
with unknown data
new version of
the John object
old version of
the John object
Heap portion
fields
fields
old John header
name = 'John'
address = nil
new John header
name = 'John'
an Object
field 1
pointer to
chunks of memory
with unknown data
Heap portion
fields
fields
35. Pros And Cons
• Very fast become (~ 2 microseconds)
• Split memory representation slows field
access (extra far memory read)
36. Spur’s Partial Read Barrier
• We wanted none of the deficiencies
• Fast become independent to heap size
• Objects in 1 chunk of memory
• Direct pointers
40. Forwarding Objects
• Forwarding objects have special class and
special format
• Most operations requires a class or
format check
• send: receiver class check
• at: / at:put: format check
41. Send Example
• lookup cache (global or inline)
• receiver class + selector => method
• Caches refuse to enter forwarder class
• Cache hit implies receiver is not a
forwarder
42. Send example
• 97% of interpreted sends have global
cache hit
• 99% of machine code sends have inline
cache hit
• Needs to check for forwarders only on
cache miss
44. Issue 1
• Each object requires at least 1 field
• Objects with 0 fields now requires 1 field
• Still less memory wasted than in a split
memory representation / object table
design
45. Issue 2
• Some operations have no class / format
check:
• Instance variable access
• method access
• Solution: receiver and method fields in stack
frames are not forwarders
• A new forwarder requires a stack zone scan
46. Become step 1/2
• step 1/2
• copy large object
• write small object into the large object
memory zone
• replace the small object by a forwarder
to the large object copy
47. old John header
name = 'John'
(unused) address
Forwarder
object
an Object
d field 1
pointer to
chunks of memory
with unknown data
Heap portion
new John header
name = 'John'
address = nil
conceptual pointer
Become step 1/2
new John header
name = 'John'
address = nil
old John header
name = 'John'
an Object
d field 1
pointer to
chunks of memory
with unknown data
new version of
the John object
old version of
the John object
Heap portion
48. Become step 2/2
• Stack zone scan
• Replace forwarder references for
receiver and method slots in frames
• Micro optimizations
49. Comparison
• Full heap scan
• Become last ~200 microseconds
• Independent to the heap size
• Objects are also in 1 chunk of memory
with direct pointers
50. Comparison (2)
• Object Table / Split Memory
representation
• Objects are in 1 chunk of memory and
direct pointers
• Become last ~200 microseconds
instead of ~2
• Become slower but Smalltalk
51. Conclusion
• Spur’s implementation have none of the
deficiencies:
• Fast become
• Objects in 1 chunk of memory
• In production for NewSpeak and Squeak
• Incoming for Pharo
53. Pypy’s
• In Python you can dynamically add
instance variable to instances
• Similar problem than become
54. Pypy’s
• Each object have exactly 5 fields in
memory + their header
• The last field is a reference to the rest of
the fields if the object is bigger than 5
55. Pypy’s
object header (2 inst
vars)
chunks of memory
with unknown data
Heap portion
field 1
field 2
(unused) field 3
(unused) field 4
(unused) extra fields
object header (6 inst
vars)
field 1
field 2
field 3
field 4
extra fields
pointer to
field 5
field 6