Performance of unique package

172 views
Skip to first unread message

Juliusz Chroboczek

unread,
Jun 25, 2024, 4:58:17 PM (4 days ago) Jun 25
to golan...@googlegroups.com
Hi,

Does anyone have any benchmarks for the new "unique" package? How does
it compare with manual interning using a map?

Thanks.

Michael Knyszek

unread,
Jun 25, 2024, 6:30:32 PM (4 days ago) Jun 25
to golang-nuts
The unique package itself has a few benchmarks you can run if you'd like, though they're somewhat limited. They exist primarily to validate that the implementation has generally good performance (that is, no obvious outliers or scaling issues).

Note that performance-wise one of the biggest things the unique package has going for it is efficient reclamation of memory. If an interned value is no longer referenced via a handle, its memory will be reclaimed in the next GC cycle. This is difficult to do with a regular map because it's not really clear when one should delete map entries.

In terms of lookup/insertion/etc., I would expect unique.Make to perform much better than a locked map when doing operations in parallel, but perform slightly worse when everything is done on a single core. The underlying concurrent map implementation scales well to many cores in my testing.

Juliusz Chroboczek

unread,
Jun 26, 2024, 7:32:48 AM (4 days ago) Jun 26
to golan...@googlegroups.com
> In terms of lookup/insertion/etc., I would expect unique.Make to perform
> much better than a locked map when doing operations in parallel, but
> perform slightly worse when everything is done on a single core.

Thanks, that's encouraging. I'll see if I can conjure a realistic
benchmark.

Juliusz Chroboczek

unread,
Jun 26, 2024, 8:11:11 AM (4 days ago) Jun 26
to golan...@googlegroups.com
> In terms of lookup/insertion/etc., I would expect unique.Make to perform
> much better than a locked map when doing operations in parallel, but
> perform slightly worse when everything is done on a single core.

It looks like your intuition was correct. Sync.Map still performs
better, though.

Uninterned-8 113ms ą 2%
Map-8 117ms ą 3%
LockedMap-8 124ms ą 2%
LockedParellel-8 118ms ą 7%
SyncMap-8 133ms ą 1%
SyncMapParallel-8 51.3ms ą 9%
Unique-8 170ms ą 3%
UniqueParallel-8 94.3ms ą 6%

https://play.golang.com/p/6F2jQH5uQYt

Michael Knyszek

unread,
Jun 26, 2024, 11:11:02 AM (4 days ago) Jun 26
to Juliusz Chroboczek, golan...@googlegroups.com
On Wed, Jun 26, 2024 at 8:11 AM Juliusz Chroboczek <j...@irif.fr> wrote:
> In terms of lookup/insertion/etc., I would expect unique.Make to perform
> much better than a locked map when doing operations in parallel, but
> perform slightly worse when everything is done on a single core.

It looks like your intuition was correct.  Sync.Map still performs
better, though.
It's not intuition, I did benchmark it myself early on. :)

Your benchmark doesn't quite make sense to me. Specifically:

var obarray sync.Map
build := func(s string) string {
    t, ok := obarray.Load(s)
    if ok {
        return t.(string)
    }
    return s
}

I don't think this is ever actually storing strings into the sync.Map. So, AFAICT, it's not really doing any interning. If I replace the Load with a LoadOrStore, and then isolate out GC effects by setting GOMEMLIMIT=8GiB GOGC=off, I get:

SyncMapParallel-8   31.95m ± 16%
UniqueParallel-8    18.09m ±  6%

I think the other non-unique benchmarks are doing the same thing -- they're only ever loading from the map, and not storing. That's not apples-to-apples, since unique.Make is mutating a map under the hood.

    Uninterned-8        113ms ą 2%
    Map-8               117ms ą 3%
    LockedMap-8         124ms ą 2%
    LockedParellel-8    118ms ą 7%
    SyncMap-8           133ms ą 1%
    SyncMapParallel-8  51.3ms ą 9%
    Unique-8            170ms ą 3%
    UniqueParallel-8   94.3ms ą 6%

https://play.golang.com/p/6F2jQH5uQYt

--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/ttzc8QHgdbA/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/878qyrucpg.fsf%40pirx.irif.fr.

Juliusz Chroboczek

unread,
Jun 26, 2024, 12:11:04 PM (4 days ago) Jun 26
to golan...@googlegroups.com
After fixing the bugs pointed out by Michael (I'm incompetent), here are
the new results:

Uninterned-8 120ms ą18%
Map-8 140ms ą 2%
LockedMap-8 139ms ą 2%
LockedParellel-8 146ms ą 2%
SyncMap-8 253ms ą 4%
SyncMapParallel-8 109ms ą 9%
Unique-8 169ms ą 2%
UniqueParallel-8 97.6ms ą 5%

SyncMap has pretty horrible memory behaviour, probably doe to all the
coercions between string and any:

Uninterned-8 41.9MB ą 0%
Map-8 41.9MB ą 0%
LockedMap-8 41.9MB ą 0%
LockedParellel-8 41.9MB ą 0%
SyncMap-8 75.5MB ą 0%
SyncMapParallel-8 75.5MB ą 0%
Unique-8 33.6MB ą 0%
UniqueParallel-8 33.6MB ą 0%

https://play.golang.com/p/Pv5S3SITuVz

-- Juliusz



Christian Stewart

unread,
Jun 26, 2024, 2:13:02 PM (4 days ago) Jun 26
to Michael Knyszek, golang-nuts
Hi all,

Is it possible to override the "comparable" for purposes of unique?

I want to intern some structs which have an Equals function to compare but they do not satisfy comparable beyond pointer comparison.

I'm guessing the answer is "no" currently because it depends on the comparable constraint and uses the go compilers internal hashing for comparison.

Thanks,
Christian Stewart 

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/7f5510e4-439a-4b9b-afb2-93231a9838e9n%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages