V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  lesismal  ›  全部回复第 3 页 / 共 63 页
回复总数  1246
1  2  3  4  5  6  7  8  9  10 ... 63  
23 天前
回复了 hez2010 创建的主题 程序员 运行 100 万个异步并发任务需要多少内存
@hez2010

> 标准库目前有计划改善这个问题吗?毕竟标准库用起来最简单,如果标准库能解决这个问题的话那岂不是不需要 nbio 这类的方案优化了。

如我前面提到的, 标准库目前的 net.Conn 这些只提供了阻塞的 io 接口, 除非是特别简单的场景, 否则没法避免一个连接一个协程. 例如读, 不一定什么时候读到一个完整 request, 就只能阻塞在那等待, 这就需要占用一个协程.

> 另外看了一眼 nbio ,似乎是针对 client-server 网络场景特化的,牺牲了通用性。例如通过 goroutine 来代替多线程进行并行计算也是一个有效的场景。

nbio 的 http 与标准库基本兼容, 写法和标准库基本一样的:
https://github.com/lesismal/nbio-examples/blob/master/http/server/server.go

少量特殊情况不能像标准库那样, 例如 nbio 的 epoll 部分负责的 conn 是非阻塞的, 不适合拿去像标准库那样 io.Copy.
因为 nbio 本来就是为了避免持续占用协程, 如果还像标准库那样提供阻塞的 io 接口可以去 io.Copy, nbio 就没有意义了
23 天前
回复了 hez2010 创建的主题 程序员 运行 100 万个异步并发任务需要多少内存
@grzhan #8
可能作者本意不是拉踩, 或者不是故意拉踩. 但不正确的方法, 得出的结论就会误导很多人. 因为绝大部分开发者不了解系统知识, 不了解不同语言在这个不适合的测试场景与实际应用中的区别, 却给他们造成了错误的印象——例如 java 虚拟线程优秀, 然后可能误解 java 就是比 go 好.

刚看了下文章作者的 github, 主要是 rust java, 所以可能他自己也是默认偏好 rust 和 java 多些, 所以也没考虑过要对 go 的做多一些说明吧, 非故意的默认的放纵某些他以为的`正确`结论
所以我出来澄清一下

OP 或者其他人不要误解, 这并不是 go 邪教, 而只是实事求是
如果有人说 go 性能比 c/cpp/rust 强, 我要是有时间有兴趣也是会出来反驳的, 所以请 OP 或者其他人不要拿"我认为"golang 天下第一"说事
23 天前
回复了 hez2010 创建的主题 程序员 运行 100 万个异步并发任务需要多少内存
@grzhan #9

> 所以一般定时要求不严格的话很多 Golang 开源项目会给定时 duration 加个 10% 左右的随机抖动吧
例如 VictoriaMetrics 的 timeutil.AddJitterToDuration -

通常的定时功能没必要加抖动, 这个库的具体内容我没看. 定时加抖动有可能是为了避免同时创建大量具有相同超时时间的内容, 后续同时到期去创建大量协程异步删除内容导致 goroutine 过多造成不稳定, 或者其他什么特殊的需要
23 天前
回复了 hez2010 创建的主题 程序员 运行 100 万个异步并发任务需要多少内存
@hez2010 #5

这个确实是毫无意义的测试, 你可以看下 #6, 当然, 其他语言也一样可以不用虚拟线程或者协程之类的, 都没必要用这么多内存. 但文章却非要选择使用虚拟线程和协程来对比, 然后海量的有栈 goroutine 就劣势了, 这不搞笑呢嘛

> `从结果来看 Java 的 Virtual Thread 要远比 Goroutine 更轻量。`

这个是文章里的结论之一, 用错误的测试方法, 得出一个作者想要的`正确`结论, 有意思?
别说什么都各自有褒贬, 方法和结论都不正确的前提下, 褒贬也都是不准确的. 然后这个所谓的结论对于大多数读者而言, 就是 java 的虚拟线程更优秀, 这本身算不算误导就各自琢磨吧

> 如果你认为 Go 就是天下第一,一切 Go 表现不好的测试都是因为测试不好,而 Go 没有任何问题的话那我也没话说。

我可从来没说过这个, go 标准库海量并发占用高我自己就知道, 所以我才搞 nbio 之类的方案优化, 而且相对成熟了, 可以替换标准库方案, 但是你们非要"只用标准库方案"的方式来评价整个 golang, 就不合理了

> 况且这测试也不是我设计的。

那你可以看下我的观点, 顺便多思考下, 避免只看表象, 避免不知其中深意, 避免人云亦云

这个测试如果是哪个老外设计的, 那他是不专业的, 我本来也是说这个测试而不是针对你
23 天前
回复了 hez2010 创建的主题 程序员 运行 100 万个异步并发任务需要多少内存
试了下这个:
var wg sync.WaitGroup
for i := 0; i < 1000000; i++ {
wg.Add(1)
time.AfterFunc(10*time.Second, func() {
wg.Done()
})
}
wg.Wait()

macos m3 占用 150M, 应该是有不少定时器到时然后并发创建的 goroutine 较多
如果改用时间轮或者堆的定时器, 数量可控的协程池, 这个占用会降更多.

@server 可以看下这个帖子: /t/1089474
实际对比下其他的看看, 我和一些朋友测试, ants 跟其他实现方式相比没有优势, 甚至是劣势, 以及实现复杂可能存在的不确定性
23 天前
回复了 hez2010 创建的主题 程序员 运行 100 万个异步并发任务需要多少内存
1. 实际场景里也不可能全是 cpu 消耗的 task, 所以 sleep 类型的 task 是合理的
2. 并发度极大超过系统核心数量的情况下, go 全用标准库直接 go func()是不公平的, 因为协程数量太大了, 这种完全可以优化成 size 可控的协程池, sleep 可以 timer 回调
3. 如果 sleep 是为了模拟实际场景里的 io 消耗例如网络 io, go 标准库主要是提供同步 io 接口, 那么多数需求确实需要每个 conn 一个协程, 但是, 你可以选择不用标准库, 例如我的 nbio, 百万连接 websocket echo 1k payload 压测, server 部署在 4c 8g ubuntu vm 上, 实际内存占用可以控制到 1g 以内:
https://github.com/lesismal/go-websocket-benchmark?tab=readme-ov-file#1m-connections-1k-payload-benchmark-for-nbiogreatws

请注意, 3 里说的百万链接占用 1g 内存, 比 OP 的这种简单 task 占用多, 不代表相同 task 测试优化后的方案就比其他语言多这么多.


另外, 不同语言的这个测试, OP 用的都是未经优化的方式, 方案并不是好的解决方案, 所以这个测试本身就是不合理的.
比如, 如果只是用这种 sleep task, go 直接用 for { time.AfterFunc() } 占用很低的. 但我相信这并不对应任何实际应用场景.

毫无意义的测试, 却顺便拉踩, 捧 java 踩 go, 实在看不下去了我才必须出来澄清下.
说脏话不太好, 所以没有在 h 同学下面评论
给 ASCII-generator star 了
34 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
@IIInsomnia #42

> 还有退出的资源也不会及时 GC ,有等待期

不管是用协程池协程常驻去执行 func()还是 go func(), 在 func()内创建的资源都是相同的, 这些资源应该都是 func() return 后可以被 gc, 如果不及时两者也都是不及时, 要想优化 func()内的资源还是得自己加 Pool 之类的

所以主要区别应该还是常驻协程复用和 runtime 复用协程的区别吧
36 天前
回复了 pike0002 创建的主题 Go 编程语言 Go 语言中的接口 nil 检查需谨慎
太复杂了, golang 的好多语法细节我都没搞懂, 惭愧, 惭愧
36 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
@IIInsomnia #40 频繁创建协程没啥压力的, 我们压测 RPC Echo 每秒几十万都毫无压力而且性能和占用稳定, 除非是任务太多, 创建速度大于执行完的退出速度导致协程数量越来越大或者有泄漏之类的搞爆了
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
@kuanat

> 也许我这样说有点暴论,但我认为,一个承载能力为 N 的系统和一个承载能力在 0~N 之间变动的系统在生产环境是没有区别的,而前者的复杂度耕地,可维护性更高。

看上去有道理, 但这就和 javaer 说"堆机器就行了"是一个道理, 都属于看上去对, 但只适用于普通场景

一旦放大到规模效应, 海量的业务, 这差别就大了, 别人努力去搞动态的方向上是对的, 如果实现的方式能达到较优解就更好了
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
@kuanat

> 反倒需要我经常强调动态可变实现带来的不可预测性才是大忌。

接#28, go func()的成本很低, 如果稳定性有问题, 那 runtime 也不稳定了, 预热创建也会不稳定. 较早版本的 go 可能没这么优秀, 1.15 还是 1.18 哪个版本之后来着, runtime go func()和协程复用已经足够优秀了, 所以你不必太担忧这个问题

> 即便是一个理想的动态实现,使用的时候还是会根据硬件、网络实测一个并发上限并一直沿用下去,那这和写一个固定的实现没有什么区别

预热创建和动态创建销毁, 在软硬件的常驻占用和能源消耗硬件持久健康上还是有区别的, 规模越大, 成本效益越大


引起 golang 不稳定的更多是过载, 海量连接之类的导致的协程数量, 对象数量, 对应的内存和调度和 gc 压力, 对应的 oom 和 stw 问题. 为了搞这些, 我搞了这个:
https://github.com/lesismal/nbio
1m websocket connections on 4core cpu ubuntu vm, 1k payload echo test, server costs 1g mem, 能跑 10w qps:
https://github.com/lesismal/go-websocket-benchmark?tab=readme-ov-file#1m-connections-1k-payload-benchmark-for-nbiogreatws
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
@kuanat #24

> 一次性预热分配好常驻就可以了

没必要, runtime 复用协程的性能是足够的, 我们做 RPC 框架的压测, 每个 RPC Call 都是 go func() 一个协程处理, 处理完后就退出了, 每秒几十万 Call, 也就是每秒几十万次 go func(), 毫无压力. 可以参考鸟窝老师的文章, 这里的 RPC 框架的测试都是每个 RPC Call 都 go func() 的, go func() 成本很低的:
https://colobu.com/2022/07/31/2022-rpc-frameworks-benchmarks/

所以, 预热分配除了浪费常驻内存, 额外的 chan 或者 cond_t 反而可能比直接 go func()还慢, 真的没什么必要, 限制协程数量+队列+go func() 可能才是更优解
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
@kuanat #14 基本上就是有需要限制协程数量的地方需要这个, 比如海量并发 epoll+逻辑协程池, 比如不同业务模块的协程池(例如常见的连接池)

没有限制协程数量的需求的场景, 硬要用协程池代替 go func 的基本都是画蛇添足
37 天前
回复了 chenfang 创建的主题 程序员 redis 逆天问题 每多少分钟限制多少次
lua 成熟的实现应该挺多的,golang 里这个就可以,拿去用你的语言调用就可以了:
https://github.com/go-redis/redis_rate/blob/v10/lua.go
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
扫了一眼 OP 的这个协程池,练手可以,但建议还是参考下 #9 吧
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
另外,协程池做性能测试需要考虑不同的业务场景特点,比如:
1. 任务的消耗类型和消耗时长
2. 协程池 size 、并发度

不同的测试参数,不同的协程池的压测数据可能各有快慢,要考虑实际的业务特点,用某个测试参数就得出某个或者某些协程池最快的结论是不太准确的。

一些库的作者自己宣称自己的库性能第一,但结论并不准确。
比如某些网络库号称自己 tcp 比标准库快、但其实可能是他的压测例子里自己的库的 write buffer size 大、标准库的 write buffer size 小,导致标准库浪费更多 syscall ,然后得出比标准库快的结论。
比如某些网络库号称自己的 http 拿过“天梯”第一,但其实他的压测用例只是实现了简单的 http 头和尾的判断,根本没法处理完整 http 协议或功能,拿去对比别人完整功能的框架,然后得到个第一的跑分,但作者自己知道、天体跑分仓库的 owner 也知道。但这种结果拿来给自己的仓库做宣传并且没有明确说明,就有点误导用户了

测试方法不严谨或者测试参数不全面、不同框架的配置、测试参数不统一,就不能得出准确的结论,用户们应该自己去多看几眼、多自己跑代码测试一下,别轻信仓库官方自己声明的数据、不管官方是大厂还是个人开发者、也不管仓库主要作者是中国还是外国背景(因为都可能有类似问题)。
37 天前
回复了 IIInsomnia 创建的主题 Go 编程语言 从 0 到 1 手撸一个协程池
按照协程是否常驻,目前主要分两大类:

### 一、idle 协程常驻
1. ants ,好象是常驻协程 cond_t 方式实现
2. 常驻协程+chan 的多种方式

goroutine 比较轻量,runtime 自己就有协程复用相关的,所以每次 go 创建新协程其实成本不大,所以常驻协程未必就是好事情,反倒是常驻协程额外的 chan 或者 cond_t 会有相对一点性能损失以及常驻的内存开销
我和一些小伙伴对各种协程池做压测,想比喻非常驻协程的方案,ants 并不具有性能优势

### 二、idle 协程不常驻
1. 字节的 gopool 这种:协程数量控制+任务队列的方式。当前协程数量没有达到最大则新任务直接创建协程执行,每个协程执行完当前也都会检查队列里是否有新的任务、如果有继续取出任务执行、否则协程退出。如果当前协程数量达到最大值就加入队列等待被执行。字节 gopool 的队列用的 list ,其他实现也有复用 slice 的

goppol 避免了常驻协程的内存开销,协程用完就归还给 runtime ,清爽轻量,没有额外的 chan 、cond_t 之类的操作的损失、性能好,但缺点是 gopool 这种实现的队列,再并发任务数量巨大、任务执行较慢时,会导致队列 size 爆炸性增长,没办法像 chan 那样自然限流去实现系统平衡

2. nbio 的协程池,协类似 gopool 但队列替换成了一个常驻协程+chan 。任务协程数量没达到最大值时新任务直接创建新协程执行、否则加到 chan 队列里,只有一个常驻协程+chan 做队列可以实现自然限流( chan 满了阻塞、自动反馈给调用方)。任务协程用完就还给 runtime ,1 个常驻协程成本也很低,也弥补了 gopool 那种限流缺陷,比较平衡。
39 天前
回复了 FanyFull 创建的主题 生活 三万六千块人民币的房子能住吗?
@Hookery 评论符合 ID, sixsixsix
1  2  3  4  5  6  7  8  9  10 ... 63  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1097 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 23ms · UTC 18:54 · PVG 02:54 · LAX 10:54 · JFK 13:54
Developed with CodeLauncher
♥ Do have faith in what you're doing.