V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  lesismal  ›  全部回复第 53 页 / 共 60 页
回复总数  1195
1 ... 45  46  47  48  49  50  51  52  53  54 ... 60  
“Java 比 Go 快,而且领先幅度不小。”
—— 这是错觉。

全部对比应该是在这里吧:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/go.html

这是 go 和 java 对比的,大部分测试项 cpu 消耗其实各有千秋,最后两个测试项差距确实很大,但是,稍微修改下再对比:

以最后一项的 binary-trees 为例( go 耗时 12.80s ,java 耗时 2.48s ):

go 代码修改:
func inner(depth, iterations uint32) string {
chk := uint32(0)
tree := bottomUpTree(depth) // 这里从循环内移到了循环外
for i := uint32(0); i < iterations; i++ {
chk += itemCheck(tree)
}
return fmt.Sprintf("%d\t trees of depth %d\t check: %d",
iterations, depth, chk)
}

java 代码修改:
for (int d = MIN_DEPTH; d <= maxDepth; d += 2) {
final int depth = d;
EXECUTOR_SERVICE.execute(() -> {
int check = 0;

final int iterations = 1 << (maxDepth - depth + MIN_DEPTH);
final TreeNode treeNode1 = bottomUpTree(depth); // 这里从循环内移到了循环外
for (int i = 1; i <= iterations; ++i) {
check += treeNode1.itemCheck();
}
results[(depth - MIN_DEPTH) / 2] =
iterations + "\t trees of depth " + depth + "\t check: " + check;
});
}

我对 java 不熟,猜测 java 的编译器对 for 循环内局部对象甚至 tree 的构造过程做了复用优化

因为这种简单逻辑内的遍历,更成熟的老龄编译器可能会做更针对的优化,C++比 C 性能强主要就在于现代 C++编译器的优化,并且实际的业务场景,很少有需要这种频繁创建这样深度和节点数量的临时对象,几乎不会遇到这种小代码段级别的优化在实际业务中发挥太大性能优势,所以把遍历的部分构造放在外层时只对比 check 消耗时,性能差不多、在我机器上 go 略好。

语言、编译器有一个漫长的成长阶段,go 会越来越强的
2021-05-14 20:04:27 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 你的钻研精神非常棒,很赞,继续加油!
2021-05-14 16:22:01 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 放松一下,出门转转透透气,喝点茶水、咖啡,眺望下远方,过阵子再想,就豁然开朗了。有时候思考比较绕的问题会懵住,我也经常,有时候要一个问题纠结几天想不明白,然后放下了,突然某个时候又想起来、灵光一闪,灯火阑珊的感觉
2021-05-14 16:19:26 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 你相当于是用可能性去解释必然性,所以会懵 :joy: :joy:
2021-05-14 16:15:46 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 其实就两点:
1. #20 里的 [3->4->1] 与 [2->5->6],这两段不包含 chan 的过程互相没影响的两个并发流,所以,跟 chan 没关系
2. 抢占式,随时可能被调度,跟是不是 print 也没关系

chan 和 print 都不是影响实际运行时的调度的充要条件,如果你多加一些 print,除了各自 goroutine 内的顺序能保证,多个 goroutine 之间的顺序没法保证
chan 、print 或者其他语句,是被调度器决定他们的执行权,他们反过来只能影响调度器对调度时间等的累计、调度时间的分配,但只是影响,影响是“可能会怎样”而不是“必然会怎样”
2021-05-14 15:27:42 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 所以我让你看调度的资料,#12 就说过了:
“—— golang 好像是 1.2 版中开始引入比较初级的抢占式调度,然后好像是 1.14 做得更彻底,即使 for{} 也能释放调度权了”

“所以我们的结论并不是矛盾的”
—— 想啥呢,你的解释跟实际现象都不一样了。。先去查资料,去分析为什么会这样、不要纠结于自己分析的对错,纠结对错就被自己陷住了、会不自觉地想往自己是合理的方向上靠、然后失去理智分析的判断力
2021-05-14 15:12:14 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
“不过去掉偶尔存在的乱序问题,连续两次的输出可以认为是 chan 等待队列机制的作用吗?”
—— 你搜下 golang 内存模型、happens before,结合 #20 的例子,其实楼主这个例子是对 chan 在要求时序场景用法的误解,[保证内存读写顺序 /临界区顺序] 跟 [多个并发流非锁定(包括类似#20 用 chan 做类似的穿行方式)区域内代码段调度顺序] 是两码事。
如果想明白了,你就能理解其实这个现象跟 chan 没直接关系,你只要思考代码段、调度就行了:楼主代码里的 chan send 和 recv 后面直到下次循环 send recv 阻塞之前的代码段,其实都是无串行化的两个或者多个并发流,这些代码段(相当于#20 里的 [3->4->1] 与 [2->5->6],这两个过程中互相没影响没有被串行化),并不受 chan 内部实现逻辑的影响,而是被调度器决定运行时机

上一楼怕有误解,编辑下,v 站这个不能编辑确实难受 :joy:
2021-05-14 15:08:01 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
“不过去掉偶尔存在的乱序问题,连续两次的输出可以认为是 chan 等待队列机制的作用吗?”
—— 你搜下 golang 内存模型、happens before,结合 #20 的例子,其实这个是对 chan 在要求时序场景用法的误解,保证内存读写顺序 /临界区顺序,跟多个并发流非锁定(包括类似#20 用 chan 做类似的穿行方式)区域内代码段调度顺序是两码事。
如果想明白了,你就能理解其实这个现象跟 chan 没直接关系,你只要思考代码段、调度就行了:楼主代码里的 chan send 和 recv 后面直到下次循环 chan recv 阻塞之前的代码段,其实都是无串行化的两个或者多个并发流,这些代码段(相当于#20 里的 [3->4->1] 与 [2->5->6],这两个过程中互相没影响没有被串行化),并不受 chan 内部实现逻辑的影响,而是被调度器决定运行时机
2021-05-14 15:01:34 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 咱们再看下 print 的源码

```golang
package main

func main() {
print("hello world")
}
```

print 是 buildin,对应的汇编源码:

```sh
go tool compile -S .\print.go > print.s
```

有点长,只看 print 的部分:

```asm
0x0024 00036 (.\print.go:4) CALL runtime.printlock(SB) // 加锁
0x0029 00041 (.\print.go:4) LEAQ go.string."hello world %d, %s\n"(SB), AX
0x0030 00048 (.\print.go:4) MOVQ AX, (SP)
0x0034 00052 (.\print.go:4) MOVQ $19, 8(SP)
0x003d 00061 (.\print.go:4) NOP
0x0040 00064 (.\print.go:4) CALL runtime.printstring(SB)
0x0045 00069 (.\print.go:4) MOVQ $1, (SP)
0x004d 00077 (.\print.go:4) CALL runtime.printint(SB)
0x0052 00082 (.\print.go:4) LEAQ go.string."hi"(SB), AX
0x0059 00089 (.\print.go:4) MOVQ AX, (SP)
0x005d 00093 (.\print.go:4) MOVQ $2, 8(SP)
0x0066 00102 (.\print.go:4) CALL runtime.printstring(SB)
0x006b 00107 (.\print.go:4) CALL runtime.printunlock(SB) // 解锁
```

print 执行过程中是对本 m 加了锁的,即使是 runtime.GOMAXPROCS(1),也能保证 print 先后的顺序:
https://github.com/golang/go/blob/master/src/runtime/print.go#L66
https://github.com/golang/go/blob/master/src/runtime/print.go#L76

而即使加了锁,依然会出现非固定的两两一组或者交替,说明这并不是进入 print 后造成的,所以即使是源码分析,也跟直接 print 还是 fmt 的 print 系列没关系
我前面说的 print,都是说 print 之前就可能被调度了,其实都是调度器决定的,而调度器并不能保证这些固定的顺序
2021-05-14 14:25:06 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 两个问题:

1. 分析楼主的问题,当然应该尽量用楼主相同的代码好些 :smile: :smile:

2. 你这里的例子循环次数只有 5,数量太少可能观察不到,修改下就来 10 秒的,你试试这个
package main

import (
"runtime"
"time"
)

func main() {
runtime.GOMAXPROCS(1)
ch := make(chan int)
go func() {
runtime.Gosched()
for i := 0; true; i++ {
ch <- 100
print("written", i, "\n")
}
}()
var ep = 100
go func() {
for {
ep = <-ch
print("received\n")
}
}()
time.Sleep(10 * time.Second)
}

然后再统计下( print 好像是直接 stderr 的,所以重定向下):
go run main.go 2>&1 | uniq -c | awk '$1!="2"'
或者 > x.log 日志文件你自己再搜下,应该就可以发现有不是连续两次的,我这里已经有只一次的日志产生
2021-05-14 13:15:54 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 一起学习研究,有新发现咱们继续讨论
2021-05-14 12:23:24 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@no1xsyzy 我找这个实际的日志例子是为了说 baiyi 的分析存在的问题,其实我第一次回复中的解释已经算比较清楚了,1.14 之后的抢占式,随时可能调度,所以在 chan send 和 recv 后面代码段之间的并发 print 的顺序是无法保证的,交替各一次和各连续两次都没法保证

我杠不动 baiyi 这孩子了,你帮我劝劝他 :joy::joy:。。。 :
2021-05-14 12:17:59 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
"这里只要 runtime.GOMAXPROCS 设置为 1,那么除了第一次和最后一次(如果存在的话)外,其他的输出绝对是连续两次的"
—— 这是你在 17 楼说的"绝对是两次",我举乱序的例子,是反驳你的绝对两次。同样的代码,现象已经证明你的解释是错的,你还要坚持你的解释,那我放弃跟你讨论这个问题

“楼主也没有问为什么在多次连续的操作中会有一次乱序”
—— 但是楼主问的是“为什么不是一个一个交替的形式”,我在以上好几个楼都解释过了 print 的原因,并且这个并不能保证固定的连续两次

“你的论证是什么?你看了 fmt.Printf 函数的源码,发现确实有能主动触发调度的操作吗?还是根据现象推断的?”
—— 你这么讲话的话,说明你根本没了解什么是抢占式调度、go 什么时候可能发生调度(我之前楼层也有提过一点),那我只能怀疑你看不懂我说的了,那就没必要再聊了,这个相关的资料一搜大把,去找资料先看一下吧。。。

从你回复的分析中能看的出,你算是个能钻研的娃,一般人不会去啃源码。但人年轻气盛的时候,可能聪明反被聪明误,因为觉得自己具备多数人不具备的源码阅读调试能力和钻研精神、并且在源码中窥探读懂了一些,所以更偏执于自己是正确的、可能会在对错上纠结、听不进去别人说什么,这个问题,我建议是冷静一下过几天你再来仔细研究下吧,我的回复已经足够详细了,如果你认为哪里有错误可以指出,我也会虚心继续研究

我也年轻过,但是接触得越多,越会明白自己还很菜,所以技术问题,心态平和些
2021-05-14 10:41:27 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
“我还是认为 chan 本身的特性所导致的,这个特性就是 chan 的等待队列可直接传值的操作。”
—— 最简单的问题,单从现象上说, #19 的日志,你可以试一下,这能说明 chan 本身特性的解释是不对的,明明都解释不了,就没必要继续坚持了吧 :joy:

“同时我认为你在 12 楼的解释将其认为是 printf 造成的调度我不认可,你也没有给出论证。”
—— 论证我已经解释得很清楚了,如果这都算没论证,那我无言以对了。或者你考虑下再仔细看看我上面几楼的回复,如果哪里不对,你也可以指出来、我再琢磨琢磨。。。
2021-05-13 18:17:04 +08:00
回复了 lesismal 创建的主题 分享创造 发布个 golang 高性能异步网络框架 nbio,单击百万不是梦!
@kksco 感谢支持!太爱 golang 了
2021-05-13 18:16:28 +08:00
回复了 lesismal 创建的主题 分享创造 发布个 golang 高性能异步网络框架 nbio,单击百万不是梦!
@foam 感谢支持!
2021-05-13 18:14:19 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 如果考虑到其他调度的话是超出讨论范围了,我只是思考了为什么会有连续两个输出而不是交替输出的问题。”
—— 这个现象本身并不是 chan op 的单句代码导致的,所以你只分析 chan 内部的肯定不足够。反而正是因为其他部分代码的调度导致的现象,所以考虑其他调度也不是超出范围

“其实在真正的使用中是要避免依赖这种 runtime 的执行顺序”
—— 对于多数人,“是否需要依赖以及如何避免依赖 rutime 调度”本身就是个难题,楼主和很多人的意图其实应该是想依赖 chan 做流控,但是对 golang 内存模型+happens-before 与调度场景下的代码执行顺序没弄太清楚所以才会疑惑。#20 例子中的想确保 1 和 2 的顺序这种场景用 chan 还是可以的
2021-05-13 17:35:55 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi
send 的时候先检查 recvq,等待队列有 waiter 的话直接发给第一个 waiter
https://github.com/golang/go/blob/master/src/runtime/chan.go#L207
并标记 waiter 的那个 g 为可运行状态,顺着代码往下看就是了
https://github.com/golang/go/blob/master/src/runtime/chan.go#L320

这里需要着重说的一点是,标记可运行不是立刻就运行,而且就算立刻运行,也不能保证 chan op 之后的一段代码全部在单次调度运行中执行完,所以你调试 chan 内部的实现逻辑,其实解释不了这个现象,解释现象,我 #12 的应该说得差不多了

recv 的逻辑也类似,代码就不贴了
2021-05-13 16:53:53 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi golang 的内存模型,无缓冲的 chan,比如两个 goroutine 分别 send 、recv 之间,可以保证这两个 A chan op 前段代码先于 B chan op 后段代码执行,但不能保证 A 和 B op 后段代码的执行顺序,因为 chan op 之后的代码随时也可能被调度

比如

goroutine A:

some code... // 1
chan <- v
some code... // 3
some code... // 4

goroutine B:
<-chan
some code... // 2
some code... // 5
some code... // 6

这里能保证的是 1 先于 2/5/6 执行,但是不能保证 3 和 4,因为 3 和 4 执行之前就可能被调度了
2021-05-13 16:48:14 +08:00
回复了 ng29 创建的主题 Go 编程语言 资讯一个 golang 并发的问题
@baiyi 你多跑几次例子试试,至少我这里中间可以遇到这样的日志:

| <-r recv|10001
| <-r recv|10001
|write <- who|10000
|write <- who|10000
| <-r recv|10001
| <-r recv|10001
|write <- who|10000 ///////////// 不是连续的两次,也不是第一次和最后一次
| <-r recv|10001 ///////////// 不是连续的两次,也不是第一次和最后一次
|write <- who|10000
|write <- who|10000
| <-r recv|10001
| <-r recv|10001
|write <- who|10000
|write <- who|10000
| <-r recv|10001
| <-r recv|10001
1 ... 45  46  47  48  49  50  51  52  53  54 ... 60  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4569 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 22ms · UTC 04:02 · PVG 12:02 · LAX 20:02 · JFK 23:02
Developed with CodeLauncher
♥ Do have faith in what you're doing.