在以下情况下,为什么Go程序的性能比预期的要差得多? [关闭]

I was solving a simple question in C, Go, and Python and comparing the results. The solution is simply supposed to have two equations inside an if-else block. The following are the code for my solution in:

C

python

go

I was comparing the way the three languages deal with floating point results and hence made this script to generate the test cases and this to compare the results, two at a time.

The weird thing is the time it takes to run the first 3 scripts. The C program obviously runs within a couple of seconds. Python takes around 2.5-3 seconds. However, Go is taking around 24-25 seconds to run the program on the test cases generated by the generate_test_cases.py script.

I thought Go would be somewhere between C and Python as far as running time is concerned. Am I doing something inefficiently with my Go code, and if so, what?

P.S. I also ran the above three programs without the file handling operations, and still the same result, i.e., Go takes an extraordinarily long time compared to the other two.

Like Max, my strong suspicion was that the slowness in Go was related to poor I/O performance. I tested this hypothesis:

package main

import "fmt"
import "os"
import "time"

func main(){
    now := time.Now()
    input,_ := os.Open("testing/test_cases.txt")
    defer input.Close()
    output,_ := os.Create("testing/Goutput.txt")
    defer output.Close()

    var ncases int
    var p float64
    fmt.Fscanf(input,"%d",&ncases)

    fmt.Println("Opened files in ", time.Since(now), "seconds")
    now = time.Now()

    cases := make([]float64, ncases)
    fmt.Println("Made array in ", time.Since(now), "seconds")
    now = time.Now()

    for i := 0; i < ncases; i++ {
        fmt.Fscanf(input,"%f",&cases[i])
    }

    fmt.Println("Read data in ", time.Since(now), "seconds")
    now = time.Now()

    for i := 0; i < ncases; i++ {
        p = cases[i]
        if p >= 0.5 {
            cases[i] = 10000 * (1-p) * (2*p-1) + 10000
        } else {
            cases[i] = p*(1-2*p)*10000 + 10000
        }
    }

    fmt.Println("Processed data in ", time.Since(now), "seconds")
    now = time.Now()

    for i := 0; i < ncases; i++ {
        fmt.Fprintln(output, cases[i])
    }

    fmt.Println("Output processed data in ", time.Since(now), "seconds")
}

Running it produced this output:

Opened files in  2.011228ms seconds
Made array in  109.904us seconds
Read data in  4.524544608s seconds
Processed data in  10.083329ms seconds
Output processed data in  1.703542918s seconds

So it appears that on my machine, all of the math occurs in about 10ms, but the I/O is slow, confirming the hypothesis. As pointed out by Janne in the comments, there are likely faster options than fmt.

Update: For example, wrapping input and output with bufio's Readers and Writers:

binput := bufio.NewReader(input)
boutput := bufio.NewWriter(output)

and using binput and boutput for buffered I/O, your original version runs in 2.1 seconds on my machine, somewhat faster than Python's 2.7.

Update 2: I noticed that I was getting different results by just switching to buffered I/O.

  1. It turns out that you also need to adjust your format strings to include the , as you do in the C version. I think this is actually more correct either way, but it looks like you can get away with it while unbuffered.

  2. It's also important to Flush() your buffered output, which I did but didn't mention before.

Here's my complete buffered solution:

package main

import "fmt"
import "os"
import "bufio"
import "time"

func main(){
    now := time.Now()

    nbinput, _ := os.Open("testing/test_cases.txt")
    defer nbinput.Close()

    nboutput, _ := os.Create("testing/Goutput.txt")
    defer nboutput.Close()

    binput := bufio.NewReader(nbinput)
    boutput := bufio.NewWriter(nboutput)

    var ncases int
    var gain, p float64
    fmt.Fscanf(binput,"%d
",&ncases)

    for i := 0; i < ncases; i++ {
        fmt.Fscanf(binput, "%f
", &p)
        if p >= 0.5 {
            gain = 10000 * (1-p) * (2*p -1)
        } else {
            gain = p*(1-2*p)*10000
        }
        fmt.Fprintln(boutput, gain+10000)
    }
    boutput.Flush()
    fmt.Println("Took ", time.Since(now), "seconds")
}

I guess following lines work slower in go.

    fmt.Fscanf(input,"%f",&p)
    fmt.Fprintln(output,gain+10000)

Go's magic happens when you do IO. They look like synchronous but in fact they are async. Go rotine does async request and returns control to scheduler. Scheduler looks for another goroutine waiting for taking control but there is only one waiting io. So scheduler loops doing nothing.

If you had 2, 10 or 100 concurrent goroutines then will see a better performance.