刚学 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 是最好语言?(手动狗头)

7960 次点击
所在节点    Go 编程语言
38 条回复
echo404
2019-06-18 11:06:55 +08:00
@rrfeng 多谢指点,待会试试
echo404
2019-06-18 11:07:34 +08:00
@harryge 10W 大概 5M 左右,也不算大文件吧
echo404
2019-06-18 11:08:38 +08:00
@mengzhuo 确实菜,所以需要学习和老哥们指点啊
echo404
2019-06-18 11:11:27 +08:00
@heimeil 多谢指点,因为我用 PHP 处理,整理请求在 75S 左右,请求耗时在 40S 左右,所以我一开始就觉得处理数据的需要用并发。
echo404
2019-06-18 11:13:23 +08:00
@viger emmm,老哥我刚看了一下,代码在 70 行。不过确实写得菜
tt67wq
2019-06-18 11:19:12 +08:00
时间都花在文件 io 上了吧
reus
2019-06-18 11:26:23 +08:00
```go

package main

import (
"bytes"
"fmt"
"runtime"
"sync/atomic"
)

func main() {
sem := make(chan struct{}, runtime.NumCPU())
array := bytes.Repeat([]byte("a"), 1000_0000)
var c int64
for _, b := range array {
b := b
sem <- struct{}{}
go func() {
defer func() {
<-sem
}()
_ = b
if n := atomic.AddInt64(&c, 1); n%10000 == 0 {
fmt.Printf("%d\n", n)
}
}()
}
for i := 0; i < cap(sem); i++ {
sem <- struct{}{}
}
}


```

给你看一个并发模式,1 千万个任务,最多有 runtime.NumCPU() 个同时跑,而不是像你那样,不停开 1 千万个 goroutine
MarlonFan
2019-06-18 11:57:51 +08:00
@reus 我也是这种套路.. 我们是在哪里看过一样的东西么...
echo404
2019-06-18 12:53:56 +08:00
@reus 大佬,这段代码中最后一个 for 循环的作用是什么呢?为了让主进程等待最后几个 goroutine 执行完毕么?
moliliang
2019-06-18 16:35:19 +08:00
阿西吧。。 这代码怕是骗金币的吧。。😁
useben
2019-06-18 17:00:01 +08:00
go func() {
for _, row := range arr {
wg.Add(1)
go func(row []string) {
defer wg.Done()
params, err := getQuery(row, *tplId, pk)
if err != nil {
fmt.Println(err)
}
paramsChan <- params
}(row)
}
wg.Wait()
close(paramsChan)
}()
这是认真的吗。。。

每个 go 串起来了。。。

and 以后先检查下代码逻辑再提出疑问吧
echo404
2019-06-18 17:54:02 +08:00
@useben 没有串吧? wait 在 for 循环外部
CEBBCAT
2019-06-18 18:56:48 +08:00
能够在解决问题后把解法一并贴出的坛友越来越少了,赞楼主👍

贴代码可以用 gist
Cellei
2019-06-19 09:02:06 +08:00
赞一个
reus
2019-06-19 14:54:12 +08:00
@MarlonFan 出现好多年的模式了
reus
2019-06-19 14:54:28 +08:00
@echo404
kwoktung
2019-06-24 15:54:27 +08:00
@echo404 怎么监控内存占用
echo404
2019-06-24 16:57:28 +08:00
@kwoktung 我是直接 top 看的

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

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

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

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

© 2021 V2EX