ap-pauloafonso / ratio-spoof

A bittorrent ratio spoof tool
MIT License
90 stars 13 forks source link

Alternatives to `flag.String`? #5

Closed damianopetrungaro closed 3 years ago

damianopetrungaro commented 3 years ago

When reading a flag passed to a binary the flag package gives us a couple of APIs to let this happen.

You did it in this way, which is totally cool!

Another alternative is to use the StringVar exported function.

This function expects a pointer to be passed as the first argument, which is set to the given default value, and when flag.Parse gets called will try to swap it with that given one.

What is the difference between the two usages? Well pretty much it's the same one, but with a tiny detail:

The compiler may move to the heap the variable used with StringVar.

case 1: torrentPath is passed as a pointer but created as a string on the main stack:

package main

import (
    "flag"
    "fmt"
)

func main() {
    var torrentPath string
    flag.StringVar(&torrentPath, "t", "", "torrent path")
    flag.Parse()
    fmt.Println(torrentPath)
}

/*
    go build -gcflags '-m' main.go
    # command-line-arguments
    main.go:10:16: inlining call to flag.StringVar
    main.go:10:16: inlining call to flag.newStringValue
    main.go:11:12: inlining call to flag.Parse
    main.go:12:13: inlining call to fmt.Println
    main.go:9:6: moved to heap: torrentPath
    main.go:12:13: torrentPath escapes to heap
    main.go:12:13: []interface {} literal does not escape
*/

case 2: torrentPath is passed as a pointer created as such on the main stack:

package main

import (
    "flag"
    "fmt"
)

func main() {
    var torrentPath *string
    flag.StringVar(torrentPath, "t", "", "torrent path")
    flag.Parse()
    fmt.Println(torrentPath)
}

/*
    go build -gcflags '-m' main.go
    # command-line-arguments
    main.go:10:16: inlining call to flag.StringVar
    main.go:10:16: inlining call to flag.newStringValue
    main.go:11:12: inlining call to flag.Parse
    main.go:12:13: inlining call to fmt.Println
    main.go:12:13: []interface {} literal does not escape
*/

case 3: torrentPath is returned as a pointer from flag.String:


package main

import (
    "flag"
)

func main() {
    torrentPath := flag.String("t", "", "torrent path")
    flag.Parse()
    print(torrentPath)
}

/*
    go build -gcflags '-m' main.go
    # command-line-arguments
    main.go:8:28: inlining call to flag.String
    main.go:9:12: inlining call to flag.Parse
*/

Note: result remains the same even when not inlining the functions calls.

damianopetrungaro commented 3 years ago

Ergo: Well done :D You used the most efficient one!

Remember that optimizing those tiny details is only really needed if benchmarking your application you are using too much memory, as well as showing that such optimization will lead to better performances.

But since I am mentoring you I wanted to show you how the go compiler optimize this :)

ap-pauloafonso commented 3 years ago

Oh, nice take, i didn't know about the other way of doing it (e.g using StringVar ). It was coincidence that i choose the faster one ;) Also this -gcflags '-m' main.go is new to me, you can see where the variables are located (heap or stack) right?, seems really nice, i will definitely check it out. thank you.