diff --git a/internal/intern/intern.go b/internal/intern/intern.go index 125d2fbe..e5f2acdd 100644 --- a/internal/intern/intern.go +++ b/internal/intern/intern.go @@ -76,23 +76,41 @@ type Table struct { // // This function may be called by multiple goroutines concurrently. func (t *Table) Intern(s string) ID { - if char6, ok := encodeChar6(s); ok { - return char6 - } - // Fast path for strings that have already been interned. In the common case // all strings are interned, so we can take a read lock to avoid needing // to trap to the scheduler on concurrent access (all calls to Intern() will // still contend mu.readCount, because RLock atomically increments it). + if id, ok := t.Query(s); ok { + return id + } + + // Outline the fallback for when we haven't interned, to promote inlining + // of Intern(). + return t.internSlow(s) +} + +// Query will query whether s has already been interned. +// +// If s has never been interned, returns false. This is useful for e.g. querying +// an intern-keyed map using a string: a failed query indicates that the string +// has never been seen before, so searching the map will be futile. +// +// If s is small enough to be inlined in an ID, it is treated as always being +// interned. +func (t *Table) Query(s string) (ID, bool) { + if char6, ok := encodeChar6(s); ok { + // This also handles s == "". + return char6, true + } + t.mu.RLock() id, ok := t.index[s] t.mu.RUnlock() - if ok { - // We never delete from this map, so if we see ok here, that cannot be - // changed by another goroutine. - return id - } + return id, ok +} + +func (t *Table) internSlow(s string) ID { // Intern tables are expected to be long-lived. Avoid holding onto a larger // buffer that s is an internal pointer to by cloning it. s = strings.Clone(s) @@ -116,7 +134,7 @@ func (t *Table) Intern(s string) ID { t.table = append(t.table, s) // The first ID will have value 1. ID 0 is reserved for "". - id = ID(len(t.table)) + id := ID(len(t.table)) if id < 0 { panic(fmt.Sprintf("internal/intern: %d interning IDs exhausted", len(t.table))) }