刚学 GO,撸了个支付宝发券的程序,为什么性能还比不上 PHP ?

2019-06-17 19:15:56 +08:00
 echo404

下面是主程代码,这是详细代码

func main() {
	//解析参数
	filePath := flag.String("f", "", "文件路径")
	tplId := flag.String("t", "", "模版 ID")
	flag.Parse()

	//解析密钥
	pk, err := ParsePrivateKey()
	check(err)

	//读取文件
	start := time.Now()
	csvFile, err := os.Open(*filePath)
	check(err)
	defer csvFile.Close()
	csvReader := csv.NewReader(csvFile)
	arr, err := csvReader.ReadAll()
	fmt.Println(len(arr))
	check(err)
	paramsChan := make(chan string, 200)
	//统计成功与失败数量
	var mutex = &sync.Mutex{}
	successNum := 0
	failNum := 0

	var wg sync.WaitGroup
	go func() {
		for _, row := range arr {
			wg.Add(1)
			go func(row []string) { //通过添加显式参数,确保当 go 语句执行时,使用当前 row 值(参考 5.6.1 内部匿名函数中获取循环变量的问题)
				defer wg.Done()
				params, err := getQuery(row, *tplId, pk)
				if err != nil {
					fmt.Println(err)
				}
				paramsChan <- params
			}(row)
		}
		wg.Wait()
		close(paramsChan) //安全关闭通道
	}()

	var wg2 sync.WaitGroup
	limit := make(chan bool, 100)
	for s := range paramsChan {
		wg2.Add(1)
		limit <- true
		go func(s string) {
			defer wg2.Done()
			res, err := sendMsg(s)
			if err != nil {
				fmt.Println(err)
				mutex.Lock()
				failNum++
				mutex.Unlock()
			}
			if res {
				mutex.Lock()
				successNum++
				mutex.Unlock()
			} else {
				mutex.Lock()
				failNum++
				mutex.Unlock()
			}
			<-limit
		}(s)
	}
	wg2.Wait()

	fmt.Printf("发券成功:%d\n", successNum)
	fmt.Printf("发券失败:%d\n", failNum)
	fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

现在如果只整理请求参数,读取 10W 行的 csv 文件,大概耗时 110-120S 左右,耗费内存在 900M 左右。如果加上发送请求的代码,会因为内存消耗太大,直接被操作系统 KILL。
我用 PHP 开 4 个进程+guzzle 异步请求,处理完 10W 数据耗时在 110S 左右。
性能差这么多,这究竟是我代码写的太菜还是因为 PHP 是最好语言?(手动狗头)

7563 次点击
所在节点    Go 编程语言
38 条回复
rrfeng
2019-06-17 19:19:35 +08:00
无脑太菜。等下再看。
littlewing
2019-06-17 19:21:28 +08:00
php 是最好的语言
echo404
2019-06-17 19:23:21 +08:00
@rrfeng 菜成这样还有救么?
DefoliationM
2019-06-17 19:23:41 +08:00
你这前面加个 go 然后后面又 wait,你还不如直接把 go 和 wait 都去了
DefoliationM
2019-06-17 19:25:49 +08:00
你一个函数里最后写一个 wait 就行了 一个里面定义两次,太菜了,不多说
richzhu
2019-06-17 19:27:13 +08:00
老哥,你的性能应该是卡在 ReadAll 处,不要用 ReadAll,改成按行读取试试呢,还有,你这里的等待组,和 goroutine 组合的用法有点够浪啊😈
xdeng
2019-06-17 19:30:10 +08:00
试下 runtime.GOMAXPROCS(runtime.NumCPU() * 8)
rrfeng
2019-06-17 19:38:59 +08:00
@echo404
好好想一想哪里该用协程并发,哪里不该用。

我的话会这样写:
定义一个 channel 传消息
定义一个 channel 计数

go func(){ 计数器,不用锁了因为从 chan 读消息 }
go func(){
for line := read_lind(file) {
chan <- line
}
chan <- "end"
}

for msg := <- chan {
go func() { send() } // 这里做并发控制,免得一次全部消息都打出去
}
xdeng
2019-06-17 19:43:58 +08:00
好像还可以
atomic.AddUint64(failNum);
atomic.AddUint64(successNum);
EthanDon
2019-06-17 19:57:08 +08:00
你这个是串行啊。。。
harryge
2019-06-17 22:24:13 +08:00
因缺思厅,像 @richzhu 说的,你有输出的日志吗?是不是时间都耗在 readAll 上了? 有点好奇 php 是怎么读取大文件,这块的性能受限于 IO 吧,和语言没啥关系。除非你 PHP 不是一次 readAll 的
mengzhuo
2019-06-18 01:48:33 +08:00
太菜了~

Chan 20 行左右就能实现并发控制,不需要你这些奇怪锁

我写过一 Go 小程序,每天处理 2T 左右的加密后的 SQL 数据,做些统计;性能瓶颈都是 io,跑满网卡,磁盘都是小事。
heimeil
2019-06-18 02:28:36 +08:00
你这有多少行就启动了多少 goroutine,一个 goroutine 的上下文占用差不多 8K+空间,10W 行大概就 800M 了,实际占用 900M 的话,基本都是创建 goroutine 的操作在消耗资源了。

你发券的话,外部请求明显比不上 range arr,只用一个 goroutine 读,再用一个 chan 发送给几个 goroutine 消费就行了,没必要开海量的 goroutine,开多了反而就出问题了。
skiy
2019-06-18 09:32:31 +08:00
哈哈。我用了这么久的 GO,都不敢贴代码。
viger
2019-06-18 09:42:22 +08:00
不想吐槽你的代码逻辑,只想吐槽一下你这代码风格。
因为超过 80 行的函数真心不想看。
佩服楼上几位居然能坚持看完的。
建议看完《代码大全》再来贴代码。
elementpps1
2019-06-18 09:42:27 +08:00
学习了
zarte
2019-06-18 09:58:54 +08:00
你要吧 php 的拿出来对比吧
richzhu
2019-06-18 10:44:13 +08:00
@heimeil 哇塞老哥基础知识好稳,嫉妒一个
echo404
2019-06-18 11:05:53 +08:00
@DefoliationM 多谢指点,昨天晚上太忙了,没来得急回复
echo404
2019-06-18 11:06:21 +08:00
@xdeng 多谢指点,待会试试

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/574828

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX