We do a combination of a priori and a posteriori coalescence with integers. IntegerDescriptor has a static array of instances for values in the range [0..255]. We can choose to use values from this array when we know we're working with an unsigned byte, or if we're working with an int and expect the cost of checking if it's actually an unsigned byte to be low enough and true often enough. A similar array is used in CharacterDescriptor to make Latin-1 characters have low cost.
However, we intentionally allow "slip" in the design. It's perfectly legal to have two integer objects in memory that happen to be equal, even if they fall in the range [0..255] (or whatever we adjust it to). Comparing them is exceptionally fast if they're the same instance of AvailObject, but if they're not and we happen to compare two equal AvailObjects, we replace one with an indirection so the next compare will be fast. That's a best-of-both-worlds approach, where we don't *require* canonization, but it's allowed (and essentially undetectable to the Avail programmer). Todd described a similar "slip" that we allow with mutable updates. A quasi-destructive operation (like o_PlusCanDestroy()) on a mutable object may, but is not required to, clobber the object in place. It always returns the result, which may or may not be the object it was given. In fact, for o_PlusCanDestroy() it's allowed to recycle either the receiver or the argument, to avoid having the programmer use superstitious coding tricks like using "1 + n" instead of the more usual "n + 1". The same happens for the empty tuple, empty set, and empty map, as well as some commonly used types. Note that these statically-accessible values can be accessed by code running in any fiber, so they're always marked shared, as are all function implementations (a.k.a., raw code, see CompiledCodeDescriptor.java), but not necessarily function closures (FunctionDescriptor.java). Function closures that are used as method implementations or semantic restriction bodies are always marked shared. As for garbage collection... having a garbage collector coalesce equal objects is another opportunistic middle ground between a priori and a posteriori canonization. It allows short lived objects to avoid the canonization step (since they won't survive a scavenge), while allowing longer lived objects to take less space and be compared more quickly – since they're more likely to be compared a lot than objects that disappear almost immediately. And that's just one special case of garbage-collection-induced representation change. I had a simple working scavenger back in the day that was fully incremental. It switched the "sense" of all the descriptors at flip time, turning all objects into ToSpace stubs that pointed back into FromSpace. All operations on a ToSpace stub caused the fields to be scanned, creating (or locating) ToSpace stubs for every immediately referenced object, then switching the descriptor of the parent object's descriptor from ToSpace stub to the appropriate descriptor. We have similar plans for a multi-threaded incremental (or scavenged) garbage collector that almost never has to coordinate (pause) all threads. We can segregate shared objects into a common space, allowing fibers to use private pools until (and unless) objects become shared. When a system is 21 years old (!) and has no installed user base to prevent drastic improvements, well,... those drastic improvements all happen. :-) On May 7, 2014, at 2:27 PM, Robbert van Dalen wrote: > Todd, > >>> lastly, are weak references first-class in avail? >> >> Mark and I have reasoned that weakness implies identity, and is therefore >> inappropriate for most values in Avail. Weakness is certainly useful for >> identities though, but is currently not supported. Mark and I have talked >> about supporting weakness at some point in the future, because weak sets and >> maps are obviously very powerful. Weak sets and maps would themselves have >> to be identities, rather than values. (Ordinary sets and maps are just >> identityless values in Avail.) > > indeed, weak versions of set and map have to have identity and doesn’t sit > well with the language. > >>> i need this to be able to hash-cons objects (via weak maps) >> >> Avail supports coalescence of equal objects through indirections. Check out >> IndirectionDescriptor, and the becomeIndirectionTo() method of A_BasicObject >> / AvailObject. The basic idea is that the equals() operation is usually >> permitted to replace one of two semantically equal participants with an >> indirection to the other; the method isBetterRepresentationThan() helps >> equals() decide which of two semantically equal objects is preferable (and >> which should become the indirection). Eventually Avail will run natively and >> provide its own garbage collector, at which time we will be able to rewrite >> indirections into strong pointers. > > from the java documentation, i understand that a (avail native) garbage > collector can decide to coalescence equal objects, while it traverses the > live set. > That’s a very cool idea. > > but is it possible to somehow implement hash-consing (a-priori coalescence) > in avail? > i want hash-consing to be able to do garbage collectable memoization. > via indirections, it appears you can only achieve a-posteriori coalescence > via equals? > > anyway, i think it is great that avail has all these notions baked in, and > not as a afterthought. > indeed, avail is truly about advanced value and identity programming! > > i don’t understand why avail isn’t getting much love on the internet. > for me it’s the best next thing since sliced bread. > > cheers, > Robbert.
signature.asc
Description: Message signed with OpenPGP using GPGMail
