speed up pixel fetching

This commit is contained in:
Norwin Roosen 2020-02-06 01:01:05 +01:00
parent 2a10ebd77d
commit 7283fac957
4 changed files with 69 additions and 61 deletions

27
main.go
View File

@ -52,18 +52,21 @@ func main() {
fetchedImg := image.NewNRGBA(img.Bounds().Add(offset)) fetchedImg := image.NewNRGBA(img.Bounds().Add(offset))
if *fetchImgPath != "" { if *fetchImgPath != "" {
// using img.SubImage to distribute tasks is nice, as we can also parallelize command generation easily! fetchCmds := pixelflut.CmdsFetchImage(fetchedImg.Bounds())
// @cleanup: use a box tiling algo instead of hardcoding fetchMessages := fetchCmds.Chunk(1)
b := fetchedImg.Bounds()
b1 := image.Rectangle{b.Min, b.Size().Div(2)} {
b2 := b1.Add(image.Pt(b1.Dx(), 0)) // @cleanup: encapsulate this in separate function exported from pixelflut
b3 := b1.Add(image.Pt(0, b1.Dy())) conn, err := net.Dial("tcp", *address)
b4 := b1.Add(b1.Size()) if err != nil {
go pixelflut.FetchImage(fetchedImg.SubImage(b1).(*image.NRGBA), *address) log.Fatal(err)
go pixelflut.FetchImage(fetchedImg.SubImage(b2).(*image.NRGBA), *address) }
go pixelflut.FetchImage(fetchedImg.SubImage(b3).(*image.NRGBA), *address) // defer conn.Close()
go pixelflut.FetchImage(fetchedImg.SubImage(b4).(*image.NRGBA), *address)
*connections -= 4 go pixelflut.FetchPixels(fetchedImg, conn)
go pixelflut.Bomb2(fetchMessages[0], conn)
}
*connections -= 1
} }
// Generate and split messages into equal chunks // Generate and split messages into equal chunks

View File

@ -1,57 +1,19 @@
package pixelflut package pixelflut
import ( import (
"bufio"
"encoding/hex"
"fmt" "fmt"
"image" "image"
"image/color"
"log"
"net"
"net/textproto"
"strings"
) )
func FetchImage(img *image.NRGBA, address string) { func CmdsFetchImage(bounds image.Rectangle) (cmds Commands) {
// FIXME @speed: this is unusably s l o w w w cmds = make([][]byte, bounds.Size().X*bounds.Size().Y)
// bottleneck seems to be our pixel reading/parsing code. cpuprofile! numCmds := 0
// -> should buffer it just as in bomb() for x := bounds.Min.X; x < bounds.Max.X; x++ {
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
conn, err := net.Dial("tcp", address) cmd := fmt.Sprintf("PX %d %d\n", x, y)
if err != nil { cmds[numCmds] = []byte(cmd)
log.Fatal(err) numCmds++
}
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,
})
}
} }
} }
return cmds
} }

View File

@ -1,10 +1,20 @@
package pixelflut package pixelflut
import ( import (
"net" "bufio"
"encoding/hex"
"image"
"image/color"
"log" "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, // Bomb writes the given message via plain TCP to the given address,
// forever, as fast as possible. // forever, as fast as possible.
func Bomb(message []byte, address string) { func Bomb(message []byte, address string) {
@ -14,6 +24,11 @@ func Bomb(message []byte, address string) {
} }
defer conn.Close() defer conn.Close()
Bomb2(message, conn)
}
// @cleanup: find common interface instead of Bomb2
func Bomb2(message []byte, conn net.Conn) {
for { for {
_, err := conn.Write(message) _, err := conn.Write(message)
if err != nil { 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,
})
}
}