首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
beego
V2EX  ›  Go

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

  •  
  •   echo404 · 34 天前 · 5616 次点击
    这是一个创建于 34 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    第 1 条附言  ·  33 天前
    多谢各位老哥的指定,根据各位的建议改了代码,现在 10W 数据整理起来在 60S 左右,内存消耗在 10M。下面是更新代码:
    ```
    func main() {
    //解析参数
    filePath := flag.String("f", "", "文件路径")
    tplId := flag.String("t", "", "模版 ID")
    flag.Parse()

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

    //读取文件
    start := time.Now()
    paramsChan := make(chan string, runtime.NumCPU())
    go readFile(*filePath, *tplId, pk, paramsChan)

    //发送数据
    var failNum int64
    var successNum int64
    var wg sync.WaitGroup
    for s := range paramsChan {
    wg.Add(1)
    go func(s string) {
    defer wg.Done()
    res, err := sendMsg(s)
    if res {
    atomic.AddInt64(&successNum, 1)
    } else {
    if err != nil {
    fmt.Println(err)
    }
    atomic.AddInt64(&failNum, 1)
    }
    }(s)
    }
    wg.Wait()

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

    func readFile(filePath string, tplId string, pk *rsa.PrivateKey, paramsChan chan string) {
    csvFile, err := os.Open(filePath)
    check(err)
    defer csvFile.Close()
    csvReader := csv.NewReader(csvFile)
    limit := make(chan struct{}, runtime.NumCPU())
    for {
    row, err := csvReader.Read()
    if err == io.EOF {
    break
    } else if err != nil {
    fmt.Printf("读取 csv 错误: %s\n", err)
    }
    limit <- struct{}{}
    go func() {
    defer func() {
    <-limit
    }()
    params, err := getQuery(row, tplId, pk)
    if err != nil {
    fmt.Println(err)
    }
    paramsChan <- params
    }()
    }
    for i := 0; i < cap(limit); i++ {
    limit <- struct{}{}
    }
    close(paramsChan)
    }
    ```
    38 回复  |  直到 2019-06-24 16:57:28 +08:00
        1
    rrfeng   34 天前   ♥ 1
    无脑太菜。等下再看。
        2
    littlewing   34 天前
    php 是最好的语言
        3
    echo404   34 天前 via iPhone
    @rrfeng 菜成这样还有救么?
        4
    DefoliationM   34 天前 via Android   ♥ 1
    你这前面加个 go 然后后面又 wait,你还不如直接把 go 和 wait 都去了
        5
    DefoliationM   34 天前 via Android
    你一个函数里最后写一个 wait 就行了 一个里面定义两次,太菜了,不多说
        6
    richzhu   34 天前
    老哥,你的性能应该是卡在 ReadAll 处,不要用 ReadAll,改成按行读取试试呢,还有,你这里的等待组,和 goroutine 组合的用法有点够浪啊😈
        7
    xdeng   34 天前   ♥ 1
    试下 runtime.GOMAXPROCS(runtime.NumCPU() * 8)
        8
    rrfeng   34 天前   ♥ 1
    @echo404
    好好想一想哪里该用协程并发,哪里不该用。

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

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

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

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

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

    你发券的话,外部请求明显比不上 range arr,只用一个 goroutine 读,再用一个 chan 发送给几个 goroutine 消费就行了,没必要开海量的 goroutine,开多了反而就出问题了。
        14
    skiy   34 天前
    哈哈。我用了这么久的 GO,都不敢贴代码。
        15
    viger   34 天前
    不想吐槽你的代码逻辑,只想吐槽一下你这代码风格。
    因为超过 80 行的函数真心不想看。
    佩服楼上几位居然能坚持看完的。
    建议看完《代码大全》再来贴代码。
        16
    elementpps1   34 天前
    学习了
        17
    zarte   34 天前
    你要吧 php 的拿出来对比吧
        18
    richzhu   34 天前
    @heimeil 哇塞老哥基础知识好稳,嫉妒一个
        19
    echo404   34 天前
    @DefoliationM 多谢指点,昨天晚上太忙了,没来得急回复
        20
    echo404   34 天前
    @xdeng 多谢指点,待会试试
        21
    echo404   34 天前
    @rrfeng 多谢指点,待会试试
        22
    echo404   34 天前
    @harryge 10W 大概 5M 左右,也不算大文件吧
        23
    echo404   34 天前
    @mengzhuo 确实菜,所以需要学习和老哥们指点啊
        24
    echo404   34 天前
    @heimeil 多谢指点,因为我用 PHP 处理,整理请求在 75S 左右,请求耗时在 40S 左右,所以我一开始就觉得处理数据的需要用并发。
        25
    echo404   34 天前
    @viger emmm,老哥我刚看了一下,代码在 70 行。不过确实写得菜
        26
    tt67wq   34 天前
    时间都花在文件 io 上了吧
        27
    reus   34 天前   ♥ 4
    ```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
        28
    MarlonFan   34 天前
    @reus 我也是这种套路.. 我们是在哪里看过一样的东西么...
        29
    echo404   34 天前
    @reus 大佬,这段代码中最后一个 for 循环的作用是什么呢?为了让主进程等待最后几个 goroutine 执行完毕么?
        30
    moliliang   34 天前
    阿西吧。。 这代码怕是骗金币的吧。。😁
        31
    useben   34 天前
    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 以后先检查下代码逻辑再提出疑问吧
        32
    echo404   33 天前
    @useben 没有串吧? wait 在 for 循环外部
        33
    CEBBCAT   33 天前 via Android   ♥ 1
    能够在解决问题后把解法一并贴出的坛友越来越少了,赞楼主👍

    贴代码可以用 gist
        34
    Cellei   33 天前
    赞一个
        35
    reus   33 天前
    @MarlonFan 出现好多年的模式了
        36
    reus   33 天前
    @echo404
        37
    kwoktung   28 天前 via Android
    @echo404 怎么监控内存占用
        38
    echo404   28 天前
    @kwoktung 我是直接 top 看的
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3660 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 22ms · UTC 09:45 · PVG 17:45 · LAX 02:45 · JFK 05:45
    ♥ Do have faith in what you're doing.