From 7283fac95704f4e05e2ad322f30095984fabdbea Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Thu, 6 Feb 2020 01:01:05 +0100 Subject: [PATCH] speed up pixel fetching --- main.go | 27 ++++++++++++---------- pixelflut/fetch.go | 56 ++++++++-------------------------------------- pixelflut/net.go | 45 ++++++++++++++++++++++++++++++++++++- pixelflut/send.go | 2 +- 4 files changed, 69 insertions(+), 61 deletions(-) diff --git a/main.go b/main.go index 934629e..6cd7b95 100644 --- a/main.go +++ b/main.go @@ -52,18 +52,21 @@ func main() { fetchedImg := image.NewNRGBA(img.Bounds().Add(offset)) if *fetchImgPath != "" { - // using img.SubImage to distribute tasks is nice, as we can also parallelize command generation easily! - // @cleanup: use a box tiling algo instead of hardcoding - b := fetchedImg.Bounds() - b1 := image.Rectangle{b.Min, b.Size().Div(2)} - b2 := b1.Add(image.Pt(b1.Dx(), 0)) - b3 := b1.Add(image.Pt(0, b1.Dy())) - b4 := b1.Add(b1.Size()) - go pixelflut.FetchImage(fetchedImg.SubImage(b1).(*image.NRGBA), *address) - go pixelflut.FetchImage(fetchedImg.SubImage(b2).(*image.NRGBA), *address) - go pixelflut.FetchImage(fetchedImg.SubImage(b3).(*image.NRGBA), *address) - go pixelflut.FetchImage(fetchedImg.SubImage(b4).(*image.NRGBA), *address) - *connections -= 4 + fetchCmds := pixelflut.CmdsFetchImage(fetchedImg.Bounds()) + fetchMessages := fetchCmds.Chunk(1) + + { + // @cleanup: encapsulate this in separate function exported from pixelflut + conn, err := net.Dial("tcp", *address) + if err != nil { + log.Fatal(err) + } + // defer conn.Close() + + go pixelflut.FetchPixels(fetchedImg, conn) + go pixelflut.Bomb2(fetchMessages[0], conn) + } + *connections -= 1 } // Generate and split messages into equal chunks diff --git a/pixelflut/fetch.go b/pixelflut/fetch.go index 55c3814..b8fcc2e 100644 --- a/pixelflut/fetch.go +++ b/pixelflut/fetch.go @@ -1,57 +1,19 @@ package pixelflut import ( - "bufio" - "encoding/hex" "fmt" "image" - "image/color" - "log" - "net" - "net/textproto" - "strings" ) -func FetchImage(img *image.NRGBA, address string) { - // FIXME @speed: this is unusably s l o w w w - // bottleneck seems to be our pixel reading/parsing code. cpuprofile! - // -> should buffer it just as in bomb() - - conn, err := net.Dial("tcp", address) - if err != nil { - log.Fatal(err) - } - defer conn.Close() - - reader := bufio.NewReader(conn) - tp := textproto.NewReader(reader) - - b := img.Bounds() - for { - for x := b.Min.X; x < b.Max.X; x++ { - for y := b.Min.Y; y < b.Max.Y; y++ { - // request pixel - fmt.Fprintf(conn, "PX %d %d\n", x, y) - if err != nil { - log.Fatal(err) - } - - // read pixel - // @speed try to run this in a separate goroutine? - // we probably need to buffer the responses then - res, err := tp.ReadLine() - if err != nil { - log.Fatal(err) - } - res2 := strings.Split(res, " ") - col, _ := hex.DecodeString(res2[3]) - img.Set(x, y, color.NRGBA{ - uint8(col[0]), - uint8(col[1]), - uint8(col[2]), - 255, - }) - } +func CmdsFetchImage(bounds image.Rectangle) (cmds Commands) { + cmds = make([][]byte, bounds.Size().X*bounds.Size().Y) + numCmds := 0 + for x := bounds.Min.X; x < bounds.Max.X; x++ { + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + cmd := fmt.Sprintf("PX %d %d\n", x, y) + cmds[numCmds] = []byte(cmd) + numCmds++ } } + return cmds } diff --git a/pixelflut/net.go b/pixelflut/net.go index 8b42d4c..00041ea 100644 --- a/pixelflut/net.go +++ b/pixelflut/net.go @@ -1,10 +1,20 @@ package pixelflut import ( - "net" + "bufio" + "encoding/hex" + "image" + "image/color" "log" + "net" + "net/textproto" + "strconv" + "strings" ) +// @speed: add some performance reporting mechanism on these functions when +// called as goroutines + // Bomb writes the given message via plain TCP to the given address, // forever, as fast as possible. func Bomb(message []byte, address string) { @@ -14,6 +24,11 @@ func Bomb(message []byte, address string) { } defer conn.Close() + Bomb2(message, conn) +} + +// @cleanup: find common interface instead of Bomb2 +func Bomb2(message []byte, conn net.Conn) { for { _, err := conn.Write(message) if err != nil { @@ -21,3 +36,31 @@ func Bomb(message []byte, address string) { } } } + +func FetchPixels(target *image.NRGBA, conn net.Conn) { + reader := bufio.NewReader(conn) + tp := textproto.NewReader(reader) + + for { + // @speed: textproto seems not the fastest, buffer text manually & split at \n ? + res, err := tp.ReadLine() + if err != nil { + log.Fatal(err) + } + + // @speed: Split is ridiculously slow due to mallocs! + // chunk last 6 chars off -> color, remove first 3 chars, find space in + // remainder, then Atoi() xy? + res2 := strings.Split(res, " ") + x, _ := strconv.Atoi(res2[1]) + y, _ := strconv.Atoi(res2[2]) + col, _ := hex.DecodeString(res2[3]) + + target.Set(x, y, color.NRGBA{ + uint8(col[0]), + uint8(col[1]), + uint8(col[2]), + 255, + }) + } +} diff --git a/pixelflut/send.go b/pixelflut/send.go index c4644b3..6a240da 100644 --- a/pixelflut/send.go +++ b/pixelflut/send.go @@ -24,7 +24,7 @@ func CommandsFromImage(img image.Image, offset image.Point) (commands Commands) // @incomplete: also send alpha? -> bandwidth tradeoff // @speed: this sprintf call is quite slow.. cmd := fmt.Sprintf("PX %d %d %.2x%.2x%.2x\n", - x + offset.X, y + offset.Y, c.R, c.G, c.B) + x+offset.X, y+offset.Y, c.R, c.G, c.B) commands[numCmds] = []byte(cmd) numCmds++ }