Live data from Hacker News

Cmapv2: A high performance, concurrent map

github.com

11–20 of 28 posts

Re: Cmapv2: A high performance, concurrent map

#11
This repo is completely unsound, code like [1] is pervasive and demonstrates a total misunderstanding of what guarantees are provided -- or, really, not provided -- by "atomic" reads of unsafe.Pointer values. Data races everywhere!

Not safe, do not pass go, do not collect $200, absolutely do not use.

[1] https://github.com/sirgallo/cmapv2/blob/280e3017ae4ba212f6f8...

Re: Cmapv2: A high performance, concurrent map

#12
post #11

This repo is completely unsound, code like [1] is pervasive and demonstrates a total misunderstanding of what guarantees are provided -- or, really, not provided -- by "atomic" reads of unsafe.Pointer values. Data races everywhere! Not safe, do not pass go, do not collect $200, absolutely do not use. [1] https://github.com/sirgallo/cmapv2/blob/280e3017ae4ba212f6f8...

Be more specific? OK

---

CopyNode broken

`CopyNode` duplicates only the parent; every child pointer is still shared

    nodeCopy.setChildren(make([]*node, len(n.children)))
    copy(nodeCopy.children, n.children) // pointers reused
https://github.com/sirgallo/cmapv2/blob/main/node.go#L11-L17

Any later mutation (for example `setValue`) writes through those shared pointers, so readers and historical snapshots are modified concurrently -- invalid, data race, memory model violation

---

Bitmap corruption

`SetBit` uses XOR rather than “set”:

    func SetBit(bitmap uint32, position int) uint32 { return bitmap ^ (1 
https://github.com/sirgallo/cmapv2/blob/main/utils.go#L41-L4...

Calling it twice on the same index flips the bit back to 0. During branch-creation on insert and during delete, this function is invoked multiple times on the same index, clearing a bit that should remain set and leaving orphaned children.

---

Invalid assumptions re: interior pointers

Only the root pointer is read with `atomic.LoadPointer`. All deeper fields like `children[pos]`, `bitmap`, and the byte-slice keys/values, are accessed directly after a successful CAS. Readers therefore race with writers that mutate these fields in place -- race condition, memory model violation, etc.

    pos := cMap.getPosition(node.Bitmap(), hash, level)
    if node.Child(pos).IsLeaf() && bytes.Equal(key, node.Child(pos).Key()) {
        return node.Child(pos).Value()
    }

https://github.com/sirgallo/cmapv2/blob/main/operation.go#L5...

---

All xxxRecursive functions rely on those invalid interior pointer assumptions

Sequence in `putRecursive` / `deleteRecursive` is

    1. `curr := atomic.LoadPointer(ptr)`
    2. Build `nodeCopy`
    3. Recurse; grandchildren are mutated in place
    4. `atomic.CompareAndSwap(ptr, curr, nodeCopy)`
https://github.com/sirgallo/cmapv2/blob/main/operation.go#L1...

If another goroutine has already swapped in a different copy of `curr` (and mutated it) the CAS still succeeds because the pointer value is unchanged, merging incompatible sub-tries and corrupting the data

---

Use-after-free in sync.Pool

On CAS failure the freshly built `nodeCopy` is immediately returned to a `sync.Pool` -- undefined behavior

    cMap.pool.PutNode(nodeCopy) // may race with outstanding readers
https://github.com/sirgallo/cmapv2/blob/main/operation.go#L1...

Other goroutines still holding references to that node can now access a reclaimed object, oops.

---

K/V Aliasing

Keys and values (both []byte slices, which are not safe for concurrent r/w access) are stored by reference, a mistake:

    n.setKey(key)
    n.setValue(value)
If the caller mutates those slices later (or concurrently in another goroutine), data races ahoy

---

Reader panics, etc.

    - `getRecursive` accesses `children[pos]` without bounds or nil checks, concurrent shrink can make `pos` invalid
    - `GetIndex` allows a negative `shiftSize` once `level >= 7` with `chunkSize = 5`, producing nonsense indices and potential slice-out-of-bounds

Re: Cmapv2: A high performance, concurrent map

#13
post #11

This repo is completely unsound, code like [1] is pervasive and demonstrates a total misunderstanding of what guarantees are provided -- or, really, not provided -- by "atomic" reads of unsafe.Pointer values. Data races everywhere! Not safe, do not pass go, do not collect $200, absolutely do not use. [1] https://github.com/sirgallo/cmapv2/blob/280e3017ae4ba212f6f8...

Thank you for debunking it so I didn't have to. I don't think I've ever seen someone post a low-level/concurrent data structure in Go that wasn't wildly unsound, so I assumed this was too.

Re: Cmapv2: A high performance, concurrent map

#14
post #9

Earlier quoted context omitted.

Another good comparison would be against https://pkg.go.dev/github.com/puzpuzpuz/xsync/v3#Map

definitely, I can expand my comparisons and benchmarks

Honestly, if you're better than the rest, I would suggest collaborating with the existing solutions.

Re: Cmapv2: A high performance, concurrent map

#15
post #12
post #11

This repo is completely unsound, code like [1] is pervasive and demonstrates a total misunderstanding of what guarantees are provided -- or, really, not provided -- by "atomic" reads of unsafe.Pointer values. Data races everywhere! Not safe, do not pass go, do not collect $200, absolutely do not use. [1] https://github.com/sirgallo/cmapv2/blob/280e3017ae4ba212f6f8...

Be more specific? OK --- CopyNode broken `CopyNode` duplicates only the parent; every child pointer is still shared nodeCopy.setChildren(make([]*node, len(n.children))) copy(nodeCopy.children, n.children) // pointers reused https://github.com/sirgallo/cmapv2/blob/main/node.go#L11-L17 Any later mutation (for example `setValue`) writes through those shared pointers, so readers and historical snapshots are modified conc…

> both []byte slices, which are not safe for concurrent r/w access

You must clone the slice on both write and read, right?

I get that cloning incurs a memory allocation and a copy operation, but this is the price for safety when concurrent access is possible or your data may be bodified outside your structure.

You could probably intern immutable keys, or avoid storing if keys already exist and are immutable, or use an object pool (like sync.Pool) to reduce allocations if this happens at scale. Anything else I am missing?

Re: Cmapv2: A high performance, concurrent map

#16
post #11

This repo is completely unsound, code like [1] is pervasive and demonstrates a total misunderstanding of what guarantees are provided -- or, really, not provided -- by "atomic" reads of unsafe.Pointer values. Data races everywhere! Not safe, do not pass go, do not collect $200, absolutely do not use. [1] https://github.com/sirgallo/cmapv2/blob/280e3017ae4ba212f6f8...

Thank you for debunking it so I didn't have to. I don't think I've ever seen someone post a low-level/concurrent data structure in Go that wasn't wildly unsound, so I assumed this was too.

There are many good implementations.

Re: Cmapv2: A high performance, concurrent map

#17
post #11

This repo is completely unsound, code like [1] is pervasive and demonstrates a total misunderstanding of what guarantees are provided -- or, really, not provided -- by "atomic" reads of unsafe.Pointer values. Data races everywhere! Not safe, do not pass go, do not collect $200, absolutely do not use. [1] https://github.com/sirgallo/cmapv2/blob/280e3017ae4ba212f6f8...

Thank you for debunking it so I didn't have to. I don't think I've ever seen someone post a low-level/concurrent data structure in Go that wasn't wildly unsound, so I assumed this was too.

Go is made by Google. Do they not have someone writing an equivalent of Java.util.concurrent.ConcurrentHashMap?

Re: Cmapv2: A high performance, concurrent map

#18

Earlier quoted context omitted.

Thank you for debunking it so I didn't have to. I don't think I've ever seen someone post a low-level/concurrent data structure in Go that wasn't wildly unsound, so I assumed this was too.

Go is made by Google. Do they not have someone writing an equivalent of Java.util.concurrent.ConcurrentHashMap?

They do, there's a concurrent hashmap in the standard library. It doesn't get posted here or on Reddit, though.

Re: Cmapv2: A high performance, concurrent map

#19
post #12

Earlier quoted context omitted.

Be more specific? OK --- CopyNode broken `CopyNode` duplicates only the parent; every child pointer is still shared nodeCopy.setChildren(make([]*node, len(n.children))) copy(nodeCopy.children, n.children) // pointers reused https://github.com/sirgallo/cmapv2/blob/main/node.go#L11-L17 Any later mutation (for example `setValue`) writes through those shared pointers, so readers and historical snapshots are modified conc…

> both []byte slices, which are not safe for concurrent r/w access You must clone the slice on both write and read, right? I get that cloning incurs a memory allocation and a copy operation, but this is the price for safety when concurrent access is possible or your data may be bodified outside your structure. You could probably intern immutable keys, or avoid storing if keys already exist and are immutable, or use a…

> You must clone the slice on both write and read, right?

I haven't looked at the code, but that doesn't make sense to me. If you can't read the slice safely, you also can't clone it safely.

Re: Cmapv2: A high performance, concurrent map

#20

Earlier quoted context omitted.

> both []byte slices, which are not safe for concurrent r/w access You must clone the slice on both write and read, right? I get that cloning incurs a memory allocation and a copy operation, but this is the price for safety when concurrent access is possible or your data may be bodified outside your structure. You could probably intern immutable keys, or avoid storing if keys already exist and are immutable, or use a…

> You must clone the slice on both write and read, right? I haven't looked at the code, but that doesn't make sense to me. If you can't read the slice safely, you also can't clone it safely.

So, what are the solutions if they are indeed not safe to do read / write concurrently?

Like okay, I read "both []byte slices, which are not safe for concurrent r/w access", but then, what is the solution? If the claim is indeed true.

Post reply on HN