Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

性能优化 将锁替换为sync.map #249

Closed
wants to merge 0 commits into from
Closed

性能优化 将锁替换为sync.map #249

wants to merge 0 commits into from

Conversation

blight19
Copy link

性能优化 将锁替换为sync.map

@aceld
Copy link
Owner

aceld commented Jul 12, 2023

@blight19 感谢PR, 这边golangci-lint有点小问题,您再优化下~

@hcraM41
Copy link
Collaborator

hcraM41 commented Jul 13, 2023

已review~
sync.Map的性能高体现在读操作远多于写操作的时候。
极端情况下,只有读操作时,是普通map的性能的44.3倍。
反过来,如果是全写,没有读,那么sync.Map还不如普通的map+mutex锁,只有普通map性能的一半。
建议使用sync.Map时一定要考虑读定比例。当写操作只占总操作的十分之一左右的时候,使用sync.Map性能会明显高很多。

如果您看过sync.Map的源码,可以了解到,sync.Map是想追求无锁读写的结构,它最好的运行方式是读永远都命中read,写只命中dirty,这样能不用任何锁机制就能做到map读写。而它最差的运行状态是read和dirty不断做替换和清理动作,性能就无法达到预期。
而什么时候可能出现最差运行状态呢?大量的写操作和大量的读操作。大量读写会导致map的miss标记大于dirty的个数,这个时候sync.Map中第一层屏障会失效,dirty就会频繁变动。

针对于connManager,这就要看你的项目类型,如果项目类型是属于connManager频繁变动,那就不适于用sync.Map,如果connManager变动较少的,并且读比例远高于写,那就适合用sync.Map。

@hcraM41
Copy link
Collaborator

hcraM41 commented Jul 13, 2023

@aceld 冰哥最终决定是否合并~

@blight19
Copy link
Author

已review~ sync.Map的性能高体现在读操作远多于写操作的时候。 极端情况下,只有读操作时,是普通map的性能的44.3倍。 反过来,如果是全写,没有读,那么sync.Map还不如普通的map+mutex锁,只有普通map性能的一半。 建议使用sync.Map时一定要考虑读定比例。当写操作只占总操作的十分之一左右的时候,使用sync.Map性能会明显高很多。

如果您看过sync.Map的源码,可以了解到,sync.Map是想追求无锁读写的结构,它最好的运行方式是读永远都命中read,写只命中dirty,这样能不用任何锁机制就能做到map读写。而它最差的运行状态是read和dirty不断做替换和清理动作,性能就无法达到预期。 而什么时候可能出现最差运行状态呢?大量的写操作和大量的读操作。大量读写会导致map的miss标记大于dirty的个数,这个时候sync.Map中第一层屏障会失效,dirty就会频繁变动。

针对于connManager,这就要看你的项目类型,如果项目类型是属于connManager频繁变动,那就不适于用sync.Map,如果connManager变动较少的,并且读比例远高于写,那就适合用sync.Map。

感谢review,我认为,这里的dirty map是的大量写入和普通的map写入性能类似,而这里使用rwlock+map,多了个上锁操作,而只有性能损失只有在ditry map最后做替换的时候产生,我觉得性能上是可以提升一些的。可以做下测试~。如果结果是如您所说,我觉得因为是实现了接口可以实现两个manager,让用户适配自己的场景做定制化选择

@hcraM41
Copy link
Collaborator

hcraM41 commented Jul 13, 2023

我上面描述的情况就是通过测试后得出的数据,部分场景下sync.Map确实性能会优于Map;
但是,sync.Map并不是用来无脑替换内建的map类型的,它只能被应用在一些特殊的场景里;
https://golang.org/pkg/sync/#Map
官方文档也说了,在以下两个场景中使用sync.Map,会比使用map+RWMutex的方式,性能要好:
1.只会增长的缓存系统中,一个 key 只写入一次而被读很多次
2.多个 goroutine 为不相交的键集读、写和重写键值对
总结一下就是,标准库中的sync.Map是为append-only场景设计的;

不过针对于这个connManager的场景,我认为2种方案的选择还是如同我上面说的,要根据项目来拟定,没有绝对的谁优于谁,
你如果感兴趣的话可以看下concurrent map,它是用普通的map+分片锁实现的,有做性能比对,在绝大部分场景下,性能都是优于sync.Map的。

@blight19 blight19 closed this Jul 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants