V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  lesismal  ›  全部回复第 27 页 / 共 56 页
回复总数  1114
1 ... 23  24  25  26  27  28  29  30  31  32 ... 56  
2023-02-18 21:52:49 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Nazz
对于异步写的问题,其实 gws 只对标 gorilla/websocket 的话就可以不考虑它,因为只是提供 ws 的基础库,gorilla/websocket 和 net.TCPConn 也同样需要用户自己去封装读写。
但是用户使用涉及广播时,应该建议用户自己注意写阻塞可能导致的问题。
而且读写个一个协程虽然并不复杂,但细节也并不那么简单,一致性、时序性、timeout 等细节,都需要仔细处理。而且我个人就遇到过 5+以上的朋友来让我帮忙 review 他们 ws 的代码,其中有些代码的封装,其实他们是知道写阻塞可能导致的问题的,所以他们封装了单独协程+chan 写的代码,但一些细节上仍然存在问题比如导致 panic 、状态不一致、timer 泄露甚至协程泄露等。
2023-02-18 21:45:36 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Nazz

> 断开连接是自动的
> 以我多年 crud 的经验来看,似乎很少有人关心 write 是否返回了错误. 一般来说,在 write 之前业务逻辑都处理好了,或者开启了协程去处理错误,有错误关闭连接退出就好

不是能不能断开的问题,而是 WriteMessage 后及时性的问题。按 go 的习俗,应该是 if err != nil 就处理了,但是 WriteMessage 不返回 err ,如果这后面还有其他逻辑,就造成了后面逻辑代码的浪费,虽然整体可能不影响,但你提供 err 返回毕竟也没什么复杂度。标准库也好、其他 repo 也好大家都是这样,你这里的设计会显得不符合习惯。
而且其实这样做,相当于是基于标准库的同步 io 、能够顺序代码的情况下,糅杂进来了异步框架的回调机制。nbio 的 OnError 这些也是回调,但那是因为 io 和逻辑协程是在不同协程,为了避免用户再占用一个协程来处理,只能回调,并且 nbio 的 WriteMessage 这些也是提供 err 返回的,所以也还算符合 go 的习惯

> 使用 channel 异步写会增加一倍的常驻协程,我更倾向于广播的时候开启一小批临时协程
> async write 这块我还有个 idea ,可以维护一个全局的 WriteMessageQueue

这两种实际使用的前提都是:业务无所谓,卡了就卡了吧

但对于工程严谨性和高实时要求的业务而言,都无法允许你说的这两种策略,因为都解决不了我说的问题,只要你是在单个循环中处理多个 conn ,就都可能存在一些健康 conn 被某些网络不佳的 conn 造成卡顿的问题。
举个例子,RPG 游戏,地图上广播的消息非常多,如果某个玩家的连接卡了,其他人都跟着卡,这是不可接受的,否则游戏服务提供者上线用不了多久就可以解散项目了,除非项目本来也没人、本来挣不到钱。
其他的游戏类型,比如 FPS 、Moba 类、其他 PVP 的动作类,都是同样的无法忍受这种一人卡多人的问题的。
复杂的 APP 业务同样会存在类似的消息推送及时性需要。
既然是做通用框架,就不要投机取巧了。协程多了硬件不够用,你还可以加机器解决,但是业务卡了公司都可能直接倒闭的,这是不行的。技术方案该硬刚的地方需要硬刚,该加机器的时候就要加机器。

> 我就和基于 std net 的库比一比

nbio 是支持直接使用标注库 std net 、不使用 nbio 自己的 poller io 的,这里例子代码就是基于标准库的,另外也有描述多种 io 模式的特点、可以根据自家业务特性来 cover 不同场景:
https://github.com/lesismal/nbio/releases/tag/v1.3.5
2023-02-18 18:27:19 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Nazz 这样似乎有个问题,就是执行 WriteMessage 的地方没有收到 err 、出错了不好处理后续流程、比如应该踢掉连接,OnError 里收到 err 却不知道 WriteMessage 的上下文
2023-02-18 18:18:53 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@liuxu
以前基于 fasthttp 的有个 github.com/dgrr/fastws ,好像有点拉跨。这个基于 gorilla 的改天我也看看

> gws 在 write 时非常直接,一个锁就发数据了,其他 2 个框架 write 前或多或少的用管道或锁处理了下别的状态情况,然后才一个锁发数据

不管是锁还是 chan ,只要不是发送队列而是直接 conn write ,就都需要注意广播业务存在 #14 的问题
2023-02-18 18:14:50 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Nazz #11 很正常,for loop 处理单个慢、其他要等,我在其他帖子里说的跨协程的各种逃逸成本、亲和性差之类的,异步部分只有在连接数达到阈值才有优势,所以我自己的库以前只支持异步的时候性能就是干不过基于标准库的,无奈加上了多种 io 模式支持后、阻塞 io 的 conn 就能干过了。
基于 fasthttp 的 fastws 那个实现好像是个前几年的在校生搞的,性能和资源占用好像更拉跨,也可以对比下看看
2023-02-18 18:11:17 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Trim21
我总结了下,使用 gobwas/ws 的项目大概需要几个“靠”:
1. 一靠运气好(公网速度稳定)
2. 二靠没人搞
3. 三靠业务小

手动狗头:dog:
2023-02-18 18:08:50 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Trim21
对。
使用 grilla/websocket 或者其他多数基于标准库的 ws 库,如果有广播业务,也要注意不只是处理读要单独协程,处理写也要单独协程,否则单个 conn 的写可能阻塞,造成类似的 for loop 内其他 conn 等待的问题。
基于 OP 的库也需要注意同样的问题。melody 对 gorilla/websocket 的封装是单独的写协程,代码质量还不错,可以作为参考
2023-02-18 17:33:04 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
所以,没有必要测试 gobwas/ws ,它还不适合用于商业项目
2023-02-18 17:31:49 +08:00
回复了 Nazz 创建的主题 程序员 go websocket rps, cpu, latency 全面测评
@Trim21 @Nazz
1m-go-websocket 和 gobwas/ws 都是错误的方案,有类似线头阻塞或者 for loop 内阻塞的问题的:io for loop 内单个 conn 阻塞(ws Upgrade 、Read 等调用都有可能当前只收到了 half-packet 而导致阻塞读的等待),这样会导致该 poller 的 for loop 内其他 conn 等待,我有在它的 repo 和 example 的 repo 里聊到相关的,但该作者似乎并不想解决问题、或者他们目前的方案(只是自定制了事件监听、读写并不是非阻塞)无法解决反而是 close issue 假装看不见来解决问题。。。
2023-02-18 12:52:13 +08:00
回复了 machen 创建的主题 程序员 美团:某动态线程池框架是官方开源的么?
@cloudzhou

> 因为这个帖子本身在推广自己 lib (还蹭美团),你又贴了自己链接,导致把你也一顿输出~。

没事的,我以前的一些工作面向的在线连接数比较大,加上有一些旧的技术惯性——服务端方案总是想考虑解决 10k/100k/1000k 的问题,go 这方面不太行所以自己有一些应对方案。看到你们也确实遇到协程爆炸的问题,所以想随便交流下。想推广的时候我就自己也单独发过推广贴了,之前发过,不过感觉 v 站相关的流量不大,所以就不怎么发了,推广随缘,能多多交流就好,哈哈哈


> 回到技术问题:之前没看你这个项目,这次去认真看了~
> 但是,盲猜通过回调实现,比如 Java 里面的 netty ,只有这样才实现少数 io 协程分发
> 结果确实是:
> g.OnData(func(c *nbio.Conn, data []byte) {
> c.Write(append([]byte{}, data...))
> })

其实这里只是 4 层的,要想省协程解决网络相关的协程爆炸的问题,不能主动去阻塞读写,一阻塞就长时间占用协程,所以只能以回调的方式做,其他语言或者 go runtime 也都是这样。

> 刚好我写过 netty 协议的 encoder/decoder ,还写过一些嵌入式类似回调解析协议
> 我的体验就是,回调式符合机器设计,不符合人类思想,维护状态是很累的

因为 4 层 TCP/UDP 只是数据流 /数据报过来,并不能确保每次受到的数据就是 7 层应用协议的一个完整包,加之为了解决协程爆炸问题、用回调的方式收数据、不用阻塞读写,就只能自己维护状态机了。同样的,除了 golang/erlang ,其他语言的高性能异步非阻塞 io 也都是这样做,目前的计算机科学体系里,绕不过这种方案。

> 如果你用两种方式来解析 websocket 协议,就能体现明显差距

其实不是用两种方式来解析 websocket ,而是两种 io 收数据,然后传递给 websocket 解析器。
标准库那种同步解析器阻塞等待读到自己想要的数据量、没办法处理这种非阻塞 io 的 conn 每次不能保证读到它想要的数据就返回 err 的场景。
nbio 的 websocket 解析器只有一解析器,就是你上面说的状态机、异步流解析器。因为这种解析器不关心你单次传递的数据是不是完整包或者多个包,给数据过来就行了,所以它是大于同步解析器的、也能处理同步解析器的场景。
所以就不需要两种解析器,只需要支持多种数据来源就行。
nbio 支持不同的 io 模式是因为,以前的版本只有异步 io 这种,io 协程池读到数据解析出来需要传递给逻辑协程池。如果直接在 io 协程池中处理、当前 conn 处理完当前消息后才能进行其他 conn 的读取、其他 conn 就都慢了。而传递给逻辑协程,这就带来了更多的逃逸、协程切换的成本,buffer 、obj 的生命周期和 pool 优化也都比基于标准库的方案更复杂并且性能差一点。以标准库或者 fasthttp 为例,它们的读 buffer 和各种对象,是同一个协程中 for 循环处理,复用和 pool 优化都很容易,而且如果数据量不是特别大,这些还都是栈上变量,协程亲和性都要好得多。而 nbio 异步 io 这种传递给另外的逻辑协程池,在连接数不是特别大的场景,性能就要差一截了,在连接数阈值达到了的时候才能发挥优势。所以又加上了不同 io 模式的支持,高在线低在线的性能、占用、稳定性更加均衡了。

> 我说的“反感”,就类似,Golang 官方那么努力能你写平铺直叙的同步读写,一把给干回来了

4 层上没办法,原因如前面所述。
但是对于 7 层,其实异步流解析的部分是 nbhttp websocket 框架内做好了的。
你看一下 nbio 的 HTTP 和 Websocket 例子,在你的 HTTP Handler 或者 Websocket 的 OnMessage Handler 里,其实你仍然同平铺直叙的同步逻辑去写业务的。
比如 nbio 对 gin 、echo 的支持,多数业务的 gin 、echo 代码不用变、只要替换 nbio 作为网络层就可以了。
少量的性能场景比如想要 zero copy 的 sendfile 之类的,需要一些业务代码改变,刚好前阵子有人遇到过这问题,更让我没想到的是,gin 、echo 这些自己实现了 Response 竟然还不支持 zero copy ,详情再这里:
https://github.com/lesismal/nbio/issues/263

再以 websocket 为例,比如使用 gorilla/websocket ,是需要自己处理 io 的,比如用一个协程循环读,如果业务涉及广播、还应该用另一个协程+chan 去处理写(否则单个 conn 写阻塞时,广播的 for 循环里其他 conn 就要等待了)。
但是如果用 nbio 的 websocket ,你不需要去自己处理 io 的部分了,只需要写业务逻辑就可以了。

> 我另外一个顾虑是:类似这种依赖某个 x 这么基础组件,有一天 Golang 升级到 xxx 版本了,协程更高效了

未来协程更高效是有可能的,但我估计主要是体现在创建回收复用和调度的效率上,但解决不了根本问题:
1. 因为 go 是有栈协程,所以只要同时存在大量协程,协程的资源占用问题应该是解决不了
2. 大量协程大量上下文变量,GC 的 CPU 消耗也是很难,uber 那些做了很多自己的优化,但仍然压力大

nbio 的方式是相当于把连接数对应的协程数量和上下文省掉了,只是处理当前,而且这个并发度是数量可配置的协程池,超过这个并发度的被协程池内存池资源自然限流等待就相对均衡了。比如协程池数量配置成 5w 这个级别,go runtime 调度和 GC 都不会有压力,业务层即使有阻塞操作,比如每个请求的处理时常 100ms ,1s 能处理 10 轮,5w*10 也能 50w qps/tps ,这数字只是举例子,多数业务比这小得多、应该是足够了。如果不够用,那就还是业务压力大于系统资源能力的问题,怎么都得加机器了。
配置更低的节点想用更少资源,协程池就再配置小点比如 1w 、甚至 1k ,用户自己定制就行

> 然后没有人敢升级,因为这个 x 组件重度依赖,做了好多高科技,没人敢动,这在我们技术上出现过

即使标准库也是有 bug ,所以这个只能是使用者以规范的工程流程做好业务的不同测试,还有就是对于多节点的服务,可以分步、一节点一节点或者一批次一批次地更新技术方案,技术升级把影响范围控制好,稳定的版本不轻易升级。
然后就是社区多互动打磨稳定性了。

> 你这项目我准备 clone 下来看看怎么实现的

欢迎欢迎,多多交流!

> 我刚想说这种回调式写法,协议要使用状态机去实现,就看到你 nbhttp 用状态机解析 http 协议
omg ,这工作量我都不敢想,但是你确定实现了一个完整的 http 协议解析了吗?这工作量可不小

HTTP1.x 这个倒是不难,目前遇到过的最难的部分是 TLS 的异步流解析魔改,因为协议更复杂、版本和代码 if else 分支多、工作量大,所以选择了在标准库 TLS 上直接魔改,爆肝生病了好几次:joy:。
还有更难的一些,目前 nbio 的异步 io 部分只支持了 HTTP1.x ,2.0 、3.0/QUIC 都还没支持,工作量太大,不知道什么时候有档期去搞了。。。
2023-02-17 23:47:08 +08:00
回复了 machen 创建的主题 程序员 美团:某动态线程池框架是官方开源的么?
@cloudzhou
> 说点打击你积极性的话,我非常反感一些很底层操作,使用非官方库的行为,其中包括协程、nio 部分

但凡官方库好用够用消耗少能解决所有痛点,谁闲着蛋疼去重复造轮子啊。如果你是对的,那造轮子的人都是脑子有病似的,字节那帮搞这块地人都该被裁员才对得起资本家了
2023-02-17 23:38:40 +08:00
回复了 machen 创建的主题 程序员 美团:某动态线程池框架是官方开源的么?
@cloudzhou #45

戾气有点重啊,上来就一顿同行相轻,像是吃了火药,没必要。我相信你不是出于恶意,只是对自己技术比较自信,所以觉得别人说的没用。我以前也这样,所以能理解。

> @lesismal 你这逻辑,无非是,net.Conn 都会挂住一个协程,比如 Read 操作,到几百万协程
而你自己写了类似 epoll 监听,只有 readable connection 才会实际占据协程,对不?

差不多是你说的这个意思,传统的其他语言主流高性能的异步 io 部分,也都是如此,非阻塞 io ,可读时占用协程也是短暂占用,所以 io 线程 /协程数量不需要太多。


> 我说的几十万协程,是指实际进行同步 io 的行为,比如消费 kafka 然后 db/redis/rpc 操作等,瞬间击垮对方 /自己系统
对于因为监听 io 阻塞的协程,其实 Go 官方也是足够用的,虽然看起来协程多很多

你前面没有明确说你的几十万协程问题是自家业务这种类似消费 kafka 占用几十万协程,通常遇到的是每个连接一个协程的问题,所以我先入为主了以为是连接数相关的协程爆炸的问题。
但有一个点是相同的,我说的 nbio 之上的逻辑协程池是可控的,配合非阻塞 io ,是可以把这种协程数量爆炸的问题解决的。
比如你们业务阻塞消费 kafka 这种,是因为 topic 太多?是不是可以考虑下改进?比如很多数据 topic 发布到公共的 topic partition ,然后消费者数量就不用那么多,实际的 topic 作为消息的结构体字段,消费者再去自行分流处理。

db/redis/rpc 这些同样的,你们同时几十万个协程去怼下游本身的行为就是不好的呀。要想让自己的服务资源消耗与性能平衡,纵向分层的实现中,每层自己做好限流、连接池协程池这些限制,而不是一股脑都怼下去。

至于标准库一个连接至少一个协程的方案,你这里说够用,但是不代表别人家也够用啊,或者说成本不划算呀。流量大在线高的厂,硬件省 50-70%甚至更高,成本也是不小的。

> 说点打击你积极性的话,我非常反感一些很底层操作,使用非官方库的行为,其中包括协程、nio 部分
真正有你这种需求的,只有有限的 im 、网络转发才需要
the key point: 绝大部分遇到的问题,都是 io 相关,说的天花乱坠,db/redis/log 先要看能不能抗住

放心吧,你不会打击到我的,你自己都列出了几个有我这种需求的场景,而且别人实际业务也有需要的。另外啊,最常见的大厂 http 服务,用我这个替换标准库的话,本身就能省不少硬件、资源占用也可控。

你这些话我觉得有点自相矛盾了。
你前面提出的几十万协程问题,是指实际进行同步 io 的行为,后面楼层又说标准库的同步 io 也足够。足够为啥还造成了几十万协程然后扛不住了呢?那说到底不还是因为标准库同步 io 导致写成数量爆炸的问题吗?那到底标准库是足够不足够呢?


> 很多人做的事情,很像是,把一个长板,构建的更长,然后给大家看,哇,我做了多厉害的工作,然后短板依在。

这是在说我做无用功吗?但是你确定你了解我的库吗?
举个例子,nbio 的 HTTP/Websocket 支持混合 io 模式,混合模式下,支持一定数量内的连接用标准库同步 io ,超过配置阈值的连接由 nbio 的 poller 进行异步 io ,这样的好处是,连接数阈值以内时,性能有保障,我简单压测了下,性能高于标准库、gorilla/websocket 。然后如果海量连接数进来时,照样资源占用可控、服务稳定。
2023-02-17 21:49:17 +08:00
回复了 machen 创建的主题 程序员 美团:某动态线程池框架是官方开源的么?
> @lesismal 我知道,你实现了一个 nio ,替代标准库,不过我的观点不在此,我的意思是,真实的业务,在这个网络层面之前,其他已经挂了,还没到这里,问题终究就是需要对资源进行限制

@cloudzhou 看这个描述,你应该是不知道。。。

nbio 的资源都是可控的,比如协程数量:标准库 net.Conn 方案是每个连接一个协程,所以才会有你前面描述的几十万协程崩了的问题。nbio 里的协程池数量完全是可配置的,比如你百万个连接,不需要每个 Conn 一个协程,少量 io 协程(数量可配置)处理 io ,逻辑协程池(数量可配置、也可以用户定制自己的协程池实现)处理具体的请求。所以比如这百万个连接,并发度只有 1w ,那么同时存在的逻辑协程数量最多也就 1w 。
nbio 的 4 层、HTTP/Websocket 的资源都是可配置的,所以是可控的,只要你不想 OOM ,做合理的配置,就能做到自然均衡限流。
2023-02-17 16:52:21 +08:00
回复了 machen 创建的主题 程序员 美团:某动态线程池框架是官方开源的么?
@cloudzhou
或许我的库能帮助改善 go 的一些状况:github.com/lesismal/nbio
2023-02-15 22:32:56 +08:00
回复了 aapeli 创建的主题 Go 编程语言 有一个 Golang 泛型的问题咨询
@Nazz
v 站回复的 markdown fmt 感人,看这里吧:
https://gist.github.com/lesismal/0cee2bb8d76c7f907a8e2a8401b4fd41
2023-02-15 22:27:44 +08:00
回复了 aapeli 创建的主题 Go 编程语言 有一个 Golang 泛型的问题咨询
@Nazz
看了下简单的基础类型泛型汇编

```golang
// main.go
package main

func SumInt8(a, b int8) int8 {
return a + b
}

func SumInt32(a, b int32) int32 {
return a + b
}

func SumFloat32(a, b float32) float32 {
return a + b
}

func SumFloat64(a, b float64) float64 {
return a + b
}

func SumGenerics[T int8 | int32 | float32 | float64](a, b T) T {
return a + b
}

func main() {
SumInt8(1, 2)
SumInt32(1, 2)
SumFloat32(1.0, 2.0)
SumFloat64(1.0, 2.0)
SumGenerics[int8](1, 2)
SumGenerics[int32](1, 2)
SumGenerics[float32](1.0, 2.0)
SumGenerics(1.0, 2.0)
}
```


```sh
ubuntu@ubuntu:~/generics$ go tool compile -S main.go
ubuntu@ubuntu:~/generics$ go tool objdump main.o
```

```asm
TEXT "".SumInt8(SB) gofile../home/ubuntu/generics/main.go
main.go:4 0x2cb7 01d8 ADDL BX, AX
main.go:4 0x2cb9 c3 RET

TEXT "".SumInt32(SB) gofile../home/ubuntu/generics/main.go
main.go:8 0x2cba 01d8 ADDL BX, AX
main.go:8 0x2cbc c3 RET

TEXT "".SumFloat32(SB) gofile../home/ubuntu/generics/main.go
main.go:12 0x2cbd f30f58c1 ADDSS X1, X0
main.go:12 0x2cc1 c3 RET

TEXT "".SumFloat64(SB) gofile../home/ubuntu/generics/main.go
main.go:16 0x2cc2 f20f58c1 ADDSD X1, X0
main.go:16 0x2cc6 c3 RET

TEXT "".main(SB) gofile../home/ubuntu/generics/main.go
main.go:32 0x2cc7 c3 RET

TEXT "".SumGenerics[go.shape.int8_0](SB) gofile../home/ubuntu/generics/main.go
main.go:20 0x2ed6 8d040b LEAL 0(BX)(CX*1), AX
main.go:20 0x2ed9 c3 RET

TEXT "".SumGenerics[go.shape.int32_0](SB) gofile../home/ubuntu/generics/main.go
main.go:20 0x2eda 8d040b LEAL 0(BX)(CX*1), AX
main.go:20 0x2edd c3 RET

TEXT "".SumGenerics[go.shape.float32_0](SB) gofile../home/ubuntu/generics/main.go
main.go:20 0x2ede f30f58c1 ADDSS X1, X0
main.go:20 0x2ee2 c3 RET

TEXT "".SumGenerics[go.shape.float64_0](SB) gofile../home/ubuntu/generics/main.go
main.go:20 0x2ee3 f20f58c1 ADDSD X1, X0
main.go:20 0x2ee7 c3 RET
```

int8/int32 的泛型生成的汇编是用的 LEAL 指令,非泛型的是 ADDL 指令,虽然都是一条指令但是 LEAL 内存加载应该比 ADDL 慢一点,这应该是你说的换成 int 会快一点的原因吧;
另外,泛型 float32/float64 生成的汇编与非泛型的汇编是一致的,都是 ADDSS/ADDSD 指令,所以性能应该没差别。
2023-02-15 18:59:20 +08:00
回复了 aapeli 创建的主题 Go 编程语言 有一个 Golang 泛型的问题咨询
@Nazz

> 你想表达的应该是 golang 泛型是有开销的, 而不是泛型可以提高性能.

基础类型和非基础类型的性能影响是不一样的

> 同样两份代码, 把参数换成泛型, 不可能会提高性能

你的测试没有提供代码,我没办法做测试结论对应的代码的因果分析


我前面的描述是有说明了场景条件的,但你可能还没仔细看所以没聊到一个点上,大概总结下
1. 主要限定于自定义 struct 类型接口调用,其他语言可能是 class 多态,所以请不要用基础类型来比较性能是否提升
2. 范型本身的实现方案,有办法提高性能的策略,但要看具体语言编译器的实现,go 的目前应该是没有提高,但 c++ rust 那些应该是提高了
3. 编译器优化能够做到范型静多态 /单态化相比于接口或面向对象 class 多态提高性能不是说 go 现在已经提高了,为什么能提高,请看下 #31

如果你熟悉一些 c++,可以简单看下《深度探索 c++对象模型》,对实际业务用处不大,但是对语言这块的理解会有些帮助。编译器理论过于深入我也不懂,其他讲编程语言的书鲜有涉及这块,所以推荐这本书
2023-02-15 16:05:56 +08:00
回复了 aapeli 创建的主题 Go 编程语言 有一个 Golang 泛型的问题咨询
@GeruzoniAnsasu
对,go 是解放了设计阶段的一些束缚,避免了面向对象的鸭嘴兽难题,避免了面对新领域尚无成型的领域设计加之快速迭代需求时顶层设计尾大不掉重构成本过高的问题。
如果项目非常复杂,新人接手之类的,没有自顶向下的类型关系图,这种自下而上地去熟悉代码也是有点心累,要靠个人阅读搜索分析代码和业务理解能力了。但相比于面向对象,这点成本其实要轻松得多。
2023-02-15 15:54:02 +08:00
回复了 aapeli 创建的主题 Go 编程语言 有一个 Golang 泛型的问题咨询
@Nazz #29
> 泛型本来就不能提高性能啊, 只是减少重复工作. 一个 Max 肯定比 MaxInt64, MaxInt32...优雅

你这里所述的是基础类型,我 #17 所说的 "泛型适合运算符重载之类的基础类型场景" 就是支持这样做的,这种场景我对使用泛型没有异议。

"泛型本来就不能提高性能啊" 是不正确的,只是 go 目前的泛型实现方案对自定义结构体类型没带来性能提升,但是其他一些语言是做到了的。我觉得可能是由于 go 自带 runtime 面临着更多复杂性,所以目前阶段没有支持这种优化,或许随着 go 未来版本编译器的优化也能带来这些提升。

泛型"能够"性能提升的场景主要是自定义的结构体类型相关的。
你可以先找些资料来研究下,比如面向对象多态的函数表,比如 c++模板静多态,我前面提到的 "泛型会让你的 Go 代码运行变慢",帖子里有讲比我更专业更深入的,引用一段落:
"从历史上看,C++、D 乃至 Rust 等系统语言一直采用单态化方法实现泛型。造成这一现实的原因很多,但总体来说就是想用更长的编译时间来换取结果代码的性能提升,并且只要我们能提前把泛型代码中的类型占位符替换成最终类型、再进行编译,就可以极大优化编译流程的性能表现。装箱方法就做不到这一点。另外,我们还可以对函数调用进行去虚拟化以回避 vtable ,甚至使用内联代码实现进一步优化。"

甚至不需要去研究这些,你可以站在编译器和运行时的角度自行想象一下比如:interfaceA 的一个实例在进行方法调用时,如何通过这个实例的指针实现方法调用?
首先你得知道这个实例的类型,否则A类型调用到B类型的方法上去就乱套了,go 的指针是胖指针,指向的内容一部分是类型元信息,一部分是指向值本身。要实现实例化调用,首先要通过这个 interfaceA 实例的指针去解引用取得类型信息、找到函数表,然后再把值和函数结合起来使用。这是由 runtime 来处理的,运行时才能搞定。
而静多态 /单态化,比如 c++模板,是编译期就生成了对应类型的代码,与上面说的 interfaceA 由运行时处理对比一下,静多态 /单态化可以把 runtime 先确定类型信息这一步省掉,虽然只是一些简单的指针操作,但量大积累起来,性能差距就拉开了。
2023-02-15 13:28:02 +08:00
回复了 aapeli 创建的主题 Go 编程语言 有一个 Golang 泛型的问题咨询
@lesismal #27 更正
我觉得这样说不太正确。接口与面向对象是不同的:
1. 面向对象是自顶向下的约束,非父类本身或者子孙类无法作为祖辈参数的实例入参传入;
2. 接口没有强制的向下约束,而是以更自由松散的方式、按需定以按劳分配,完全可以根据需求、抽取需要实现的方法集合作为一个单独的接口定以去作为其他公共形式的使用。可能是由于大家平时一些自定义类型需要去实现标准库已经定义了的接口,才会有这种被约束的错觉。
1 ... 23  24  25  26  27  28  29  30  31  32 ... 56  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   940 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 23ms · UTC 19:21 · PVG 03:21 · LAX 12:21 · JFK 15:21
Developed with CodeLauncher
♥ Do have faith in what you're doing.