From ecacbf65d484039aaba2aa99850f01bb6d64fe2a Mon Sep 17 00:00:00 2001 From: Seebs Date: Tue, 8 Sep 2020 11:44:56 -0500 Subject: [PATCH] Document copy-on-write semantics, at all. The copy-on-write semantics were previously documented only in the 125-line commit log from the patch which introduced them. Add documentation for them in a few likely places. --- roaring/container_stash.go | 24 +++++++++++++++++++++++- roaring/roaring.go | 15 +++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/roaring/container_stash.go b/roaring/container_stash.go index 6cdd53287..e18725d6f 100644 --- a/roaring/container_stash.go +++ b/roaring/container_stash.go @@ -34,6 +34,26 @@ const ( // less than 4,096 values, an array is often used. Containers with long runs of // integers would use run length encoding, and more random data usually uses // bitmap encoding. +// +// The Container type has somewhat magical semantics. Containers can be marked +// as "frozen" by the Freeze method, after which, nothing should ever modify +// that specific container object again, no matter what. Because of this, but +// also sometimes for Even More Esoteric Reasons, *no* container method should +// ever be assumed to be genuinely modifying the container it was called on, +// and *every* container method that might modify a container should return +// the "modified" *Container, which *may point to a different object*. The +// caller should always use this resulting container, and if you're storing +// a *Container in a data structure, you need to update the data structure's +// pointer too. +// +// A nil *Container is a valid empty container. +// +// In general, operations on containers which produce new containers *may* +// yield new containers, and *may* yield their operands. +// +// The reason for all of this is to allow containers to have copy-on-write +// semantics, which allow us to reduce memory usage dramatically, and GC +// load even more dramatically. type Container struct { pointer *uint16 // the data pointer len, cap int32 // length and cap @@ -264,7 +284,9 @@ func (c *Container) setMapped(mapped bool) { // Freeze returns an unmodifiable container identical to c. This might // be c, now marked unmodifiable, or might be a new container. If c // is currently marked as "mapped", referring to a backing store that's -// not a conventional Go pointer, the storage may be copied. +// not a conventional Go pointer, the storage may (or may not) be copied. +// Do not call Freeze on a temporarily-corrupt container, such as one +// returned from UnionInPlace but on which you haven't since called Repair. func (c *Container) Freeze() *Container { if c == nil { return nil diff --git a/roaring/roaring.go b/roaring/roaring.go index d6dc93362..88733db2e 100644 --- a/roaring/roaring.go +++ b/roaring/roaring.go @@ -6876,9 +6876,14 @@ func ConvertRunToBitmap(c *Container) *Container { return c.runToBitmap() } +// Optimize yields a container with the same bits as c, but +// adjusted to the smallest-storage type by Roaring rules (thus, +// runs where that's smaller, otherwise arrays for N < 4096 and +// bitmaps for N >= 4096). func Optimize(c *Container) *Container { return c.optimize() } + func Union(a, b *Container) (c *Container) { c = union(a, b) // c can be have arrays that are too big, and need @@ -6890,10 +6895,16 @@ func Difference(a, b *Container) *Container { return difference(a, b) } +// Add yields a container identical to c, but with the given bit set; added +// is true if the bit wasn't previously set. It is unspecified whether +// the original container is modified. func (c *Container) Add(v uint16) (newC *Container, added bool) { return c.add(v) } +// Add yields a container identical to c, but with the given bit cleared; +// removed is true if the bit was previously set. It is unspecified whether +// the original container is modified. func (c *Container) Remove(v uint16) (c2 *Container, removed bool) { return c.remove(v) } @@ -6906,6 +6917,10 @@ func (c *Container) CountRange(start, end int32) (n int32) { return c.countRange(start, end) } +// UnionInPlace yields a container containing all the bits set in either +// c or other. It may, or may not, modify c. The resulting container's +// count, as returned by c.N(), may be incorrect; see (*Container).Repair(). +// Do not freeze a container produced by this operation before repairing it. func (c *Container) UnionInPlace(other *Container) (r *Container) { return c.unionInPlace(other) }