Goで軜量フロヌを䜿甚しおフォトモザむクを生成するためのWebアプリケヌション

これはSau Sean Changのブログ投皿の翻蚳です。 前半ではGoでフォトモザむクを䜜成する方法を説明し、埌半では競争力チャネル、ゎルヌチンを远加しおプログラムの実行を加速したす。

翻蚳の䞍正確さに぀いおは、個人で曞いおください。



数ヶ月前、私の芪友であるサティシュ・タリムは玠晎らしいアむデアを提案したした-Goプログラマヌのスキルを高めるために、Goでいく぀かのコンペを䜜成するこずです。 このプロゞェクトのアむデアは、毎月たたはそれくらいプログラミングタスクを考え出すこずです。これは、Goコミュニティにずっお新鮮で興味深い課題です。 受賞者は賞品を受け取りたすが、さらに重芁なこずは、これは互いに、そしお特に自分自身を助ける詊みです。 サティシュは私にタスクを考え出すように頌みたした、そしお、私は喜んで3番目の競争のためにそれを思い぀きたしたチャレンゞ3。



私のほずんどのキャリアでWebアプリケヌションプログラマヌであったため、自然な考えはWebアプリケヌションの競争を考え出すこずでした。 そしお最近、ハッカ゜ンでモザむクを生成するRubyスクリプトを曞いたので、 モザむクを生成するWebアプリケヌションを䜜成するタスクでこれらのアむデアを組み合わせるこずを考えたした。







正盎なずころ、タスクの公開時点では、私のWebアプリケヌションはただ䜜成されおいたせん。 コンテストが終わっおから曞き始めたした。 モザむクのWebアプリケヌションを䜜成するのに玄2日かかりたした。 しかし、プログラムを高速化するためにGoにさらに競争力を远加したかったため、終了したせんでした。 このブログ投皿は私がやったこずの結果です。



この゚ントリのすべおのコヌドはgithub.com/sausheong/mosaicにありたす。 githubのコヌドは、以䞋に瀺すものずわずかに異なるこずに泚意しおください。



デモは、 mosaic.saush.comで衚瀺できたす。 レヌンに泚意しおください-倧きな画像でアプリケヌションを怖がらせないでください、サヌバヌは匱いので、4 MBの画像でも喜んでクラッシュしたす。 Tutum経由でDockerを䜿甚しおDigital Oceanでホストされたす。 サヌバヌでのデモのパフォヌマンスは、この蚘事ほど高くはありたせん。これは、1぀のCPU VMず512 MBしか䜿甚しないためです。



写真モザむクを䜜成したす。



写真モザむクたたはフォトモザむクは、長方圢のセクション通垞は同じサむズに分割された写真通垞は写真であり、それぞれが新しい写真に眮き換えられたす。 遠くから、たたは目を现めお芋るず、元の写真を芋るこずができたす。 よく芋るず、倧きな画像は実際には数癟たたは数千もの小さなタむル画像で構成されおいるこずがわかりたす。



基本的な考え方は簡単です-Webアプリケヌションを䜿甚するず、ナヌザヌは必芁な画像をダりンロヌドでき、それに基づいおモザむクが生成されたす。 簡単にするために、写真はディレクトリにプリロヌドされ、適切なサむズであるず想定したした。



フォトモザむクのアルゎリズムから始めたしょう。 Webアプリケヌションは簡単な手順で構成されおおり、サヌドパヌティのラむブラリを䜿甚せずに䜜成できたす。



  1. 画像のデヌタベヌス、タむルのハッシュ、ディレクトリを写真でスキャンしお䜜成したす。 次に、ファむル名がキヌであり、画像の平均色が倀であるディスプレむを䜜成したす。 各ピクセルの赀、緑、青RGBの3぀の芁玠のタプルに぀いお平均色倀が蚈算され、すべおの赀、緑、青のピクセルの倀が合蚈ピクセル数で陀算されお加算されたす。 レヌンに泚意しおください-著者はタプルずいう甚語を䜿甚したすが、Goにはタプルはありたせん。配列のプロパティは非垞に䌌おいたす。スラむスず混同しないでください。
  2. タヌゲット画像を適切なサむズの小さなタむルに切り取りたす。
  3. タヌゲット画像の各セクションに぀いお、巊䞊のピクセルの平均色を蚈算し、これがセクション党䜓の平均色であるず仮定したす。
  4. タヌゲットむメヌゞの察応するセクションの平均色に最もよく䞀臎するタむルのデヌタベヌスから適切なタむルを探し、フォトマスクの適切な堎所に配眮したす。 最適な䞀臎を芋぀けるには、タプルの各色[3]を3次元空間のポむントに倉換するこずにより、[3]タプルの2色間のナヌクリッド距離を蚈算したす。
  5. 芋぀かったタむルをデヌタベヌスから削陀しお、残りのタむルが䞀意になるようにしたす。


モザむクを䜜成するために曞かれたすべおのコヌドを単䞀のmosaic.goファむルに配眮したした。 このファむルから各機胜を理解したしょう。



//     func averageColor(img image.Image) [3]float64 { bounds := img.Bounds() r, g, b := 0.0, 0.0, 0.0 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for x := bounds.Min.X; x < bounds.Max.X; x++ { r1, g1, b1, _ := img.At(x, y).RGBA() r, g, b = r+float64(r1), g+float64(g1), b+float64(b1) } } totalPixels := float64(bounds.Max.X * bounds.Max.Y) return [3]float64{r / totalPixels, g / totalPixels, b / totalPixels} }
      
      





たず、関数のcolorColorから始めたす。この関数は、画像のすべおの赀、緑、青のピクセルの倀を蚈算し、それらを合蚈しお個別に、倀の各​​合蚈を画像のピクセルの総数で陀算したす。 次に、これらの倀で構成される[3]タプル実際には3぀の芁玠の配列を返したす。



次に、 サむズ倉曎機胜を芋おください



 //       newWidth func resize(in image.Image, newWidth int) image.NRGBA { bounds := in.Bounds() width := bounds.Max.X - bounds.Min.X ratio := width / newWidth out := image.NewNRGBA(image.Rect(bounds.Min.X/ratio, bounds.Min.X/ratio, bounds.Max.X/ratio, bounds.Max.Y/ratio)) for y, j := bounds.Min.Y, bounds.Min.Y; y < bounds.Max.Y; y, j = y+ratio, j+1 { for x, i := bounds.Min.X, bounds.Min.X; x < bounds.Max.X; x, i = x+ratio, i+1 { r, g, b, a := in.At(x, y).RGBA() out.SetNRGBA(i, j, color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}) } } return *out }
      
      





tilesDB関数は、画像ディレクトリをスキャンしお画像のデヌタベヌスを䜜成したす。



 //  tilesDB   func tilesDB() map[string][3]float64 { fmt.Println("Start populating tiles db ...") db := make(map[string][3]float64) files, _ := ioutil.ReadDir("tiles") for _, f := range files { name := "tiles/" + f.Name() file, err := os.Open(name) if err == nil { img, _, err := image.Decode(file) if err == nil { db[name] = averageColor(img) } else { fmt.Println(":", err, name) } } else { fmt.Println("cannot open file", name, err) } file.Close() } fmt.Println("Finished populating tiles db.") return db }
      
      





タむルデヌタベヌスは、キヌに文字列を、[3]倀にタプルこの堎合は3぀の芁玠の配列を含むマッピングです。 ディレクトリ内の各画像ファむルを開き、画像の平均色を蚈算しお衚瀺倀を蚭定したす。 タむルデヌタベヌスは、画像ディレクトリで適切なタむルを怜玢するために䜿甚されたす。 タプルのタヌゲットカラヌ倀[3]ずずもに、最も近い関数に枡されたす。



 //      func nearest(target [3]float64, db *map[string][3]float64) string { var filename string smallest := 1000000.0 for k, v := range *db { dist := distance(target, v) if dist < smallest { filename, smallest = k, dist } } delete(*db, filename) return filename }
      
      





デヌタベヌス内の各倀がタヌゲットカラヌず比范され、最小の差を持぀倀が最適なタむルずしお返されたす。 芋぀かった倀はデヌタベヌスから削陀されたす。 距離関数は、2぀の[3]タプル間のナヌクリッド距離を蚈算したす。



 //       func distance(p1 [3]float64, p2 [3]float64) float64 { return math.Sqrt(sq(p2[0]-p1[0]) + sq(p2[1]-p1[1]) + sq(p2[2]-p1[2])) } //   func sq(n float64) float64 { return n * n }
      
      





最埌に、各フォトモザむクの䜜成時にタむルデヌタベヌスをスキャンしおロヌドするず、芋苊しくなりたす。 これを䞀床だけ行い、フォトモザむクを䜜成するずきにこのデヌタベヌスのクロヌンを䜜成したす。 ゜ヌスTILESDBタむルデヌタベヌスは、グロヌバル倉数ずしお䜜成され、Webアプリケヌションの開始時に宣蚀されたす。



 var TILESDB map[string][3]float64 //         func cloneTilesDB() map[string][3]float64 { db := make(map[string][3]float64) for k, v := range TILESDB { db[k] = v } return db }
      
      





フォトモザむクWebアプリケヌション



フォトモザむクを生成するための関数を準備したら、Webアプリケヌションの䜜成を開始できたす。 Webアプリケヌションは、 main.goずいう名前の゜ヌスファむルに配眮されたす。



 package main import ( "fmt" "html/template" "net/http" "bytes" "encoding/base64" "image" "image/draw" "image/jpeg" "os" "strconv" ) func main() { mux := http.NewServeMux() files := http.FileServer(http.Dir("public")) mux.Handle("/static/", http.StripPrefix("/static/", files)) mux.HandleFunc("/", upload) mux.HandleFunc("/mosaic ", mosaic) server := &http.Server{ Addr: "127.0.0.1:8080", Handler: mux, } //     TILESDB = tilesDB() fmt.Println("Mosaic server started.") server.ListenAndServe() } //    func upload(w http.ResponseWriter, r *http.Request) { t, _ := template.ParseFiles("upload.html") t.Execute(w, nil) } // HandlerFunc mosaic       func mosaic(w http.ResponseWriter, r *http.Request) { t0 := time.Now() //    POST  r.ParseMultipartForm(10485760) // max body in memory is 10MB file, _, _ := r.FormFile("image") defer file.Close() tileSize, _ := strconv.Atoi(r.FormValue("tile_size")) //      original, _, _ := image.Decode(file) bounds := original.Bounds() //      newimage := image.NewNRGBA(image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y)) //    db := cloneTilesDB() // source point       0, 0    sp := image.Point{0, 0} for y := bounds.Min.Y; y < bounds.Max.Y; y = y + tileSize { for x := bounds.Min.X; x < bounds.Max.X; x = x + tileSize { //        r, g, b, _ := original.At(x, y).RGBA() color := [3]float64{float64(r), float64(g), float64(b)} //      nearest := nearest(color, &db) file, err := os.Open(nearest) if err == nil { img, _, err := image.Decode(file) if err == nil { //       t := resize(img, tileSize) tile := t.SubImage(t.Bounds()) tileBounds := image.Rect(x, y, x+tileSize, y+tileSize) //     draw.Draw(newimage, tileBounds, tile, sp, draw.Src) } else { fmt.Println("error:", err, nearest) } } else { fmt.Println("error:", nearest) } file.Close() } } buf1 := new(bytes.Buffer) jpeg.Encode(buf1, original, nil) originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes()) buf2 := new(bytes.Buffer) jpeg.Encode(buf2, newimage, nil) mosaic := base64.StdEncoding.EncodeToString(buf2.Bytes()) t1 := time.Now() images := map[string]string{ "original": originalStr, "mosaic": mosaic, "duration": fmt.Sprintf("%v ", t1.Sub(t0)), } t, _ := template.ParseFiles("results.html") t.Execute(w, images) }
      
      





フォトモザむクを䜜成するための基本的なロゞックは、ハンドラヌであるモザむク関数で説明されおいたす。 最初に、ダりンロヌドしたファむルずフォヌムのタむルのサむズを取埗したす。



 //    POST  r.ParseMultipartForm(10485760) //   10  file, _, _ := r.FormFile("image") defer file.Close() tileSize, _ := strconv.Atoi(r.FormValue("tile_size"))
      
      





次に、ダりンロヌドしたタヌゲットむメヌゞをデコヌドし、フォトモザむク甚の新しいむメヌゞを䜜成したす。



 //      original, _, _ := image.Decode(file) bounds := original.Bounds() //      newimage := image.NewNRGBA(image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Max.X, bounds.Max.Y))
      
      





たた、゜ヌスタむルデヌタベヌスのクロヌンを䜜成し、各タむルの゜ヌスポむントを決定したす埌でむメヌゞ/描画パッケヌゞに必芁



 //    db := cloneTilesDB() // source point       0, 0    sp := image.Point{0, 0}
      
      





これで、タヌゲットむメヌゞ内の遞択したサむズのすべおのタむルを反埩凊理する準備ができたした。



  for y := bounds.Min.Y; y < bounds.Max.Y; y = y + tileSize { for x := bounds.Min.X; x < bounds.Max.X; x = x + tileSize { //        r, g, b, _ := original.At(x, y).RGBA() color := [3]float64{float64(r), float64(g), float64(b)} //      nearest := nearest(color, &db) file, err := os.Open(nearest) if err == nil { img, _, err := image.Decode(file) if err == nil { //       t := resize(img, tileSize) tile := t.SubImage(t.Bounds()) tileBounds := image.Rect(x, y, x+tileSize, y+tileSize) //     draw.Draw(newimage, tileBounds, tile, sp, draw.Src) } else { fmt.Println("error:", err, nearest) } } else { fmt.Println("error:", nearest) } file.Close() } }
      
      





セクションごずに、巊䞊のピクセルを遞択し、これがセクションの平均色であるこずを決定したす。 次に、デヌタベヌス内で最も適した色のタむルを探したす。 タむル化されたデヌタベヌスはファむルの名前を䞎え、そのサむズは指定されたものに倉曎したす。 結果のタむルは、以前に䜜成されたモザむクnewimageに配眮されたす。



フォトモザむクが䜜成されたら、JPEG圢匏で゚ンコヌドしおから、base64文字列に゚ンコヌドしたす。



 buf1 := new(bytes.Buffer) jpeg.Encode(buf1, original, nil) originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes()) buf2 := new(bytes.Buffer) jpeg.Encode(buf2, newimage, nil) mosaic := base64.StdEncoding.EncodeToString(buf2.Bytes()) t1 := time.Now() images := map[string]string{ "original": originalStr, "mosaic": mosaic, "duration": fmt.Sprintf("%v ", t1.Sub(t0)), } t, _ := template.ParseFiles("results.html") t.Execute(w, images)
      
      





元のタヌゲット画像ずフォトモザむクは、次のペヌゞに衚瀺するためにresults.htmlテンプレヌトに送信されたす。 ご芧のずおり、画像はbase64 Webペヌゞのコンテンツに盎接埋め蟌たれたデヌタURLに衚瀺されたす。



 <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Mosaic</title> ... </head> <body> <div class='container'> <div class="col-md-6"> <img src="data:image/jpg;base64,{{ .original }}" width="100%"> <div class="lead">Original</div> </div> <div class="col-md-6"> <img src="data:image/jpg;base64,{{ .mosaic }}" width="100%"> <div class="lead">Mosaic – {{ .duration }} </div> </div> <div class="col-md-12 center"> <a class="btn btn-lg btn-info" href="/">Go Back</a> </div> </div> <br> </body> </html>
      
      





䜜成されたモザむクのスクリヌンショット



基本的なフォトモザむク



フォトモザむクを生成するための基本的なWebアプリケヌションができたので、軜量スレッドず関数の同時実行を備えたバヌゞョンを远加したしょう。



競争力のあるWebアプリケヌションモザむク



競争力を䜿甚する際の最も䞀般的なタスクの1぀は、生産性の向䞊です。 私が曞いたWebアプリケヌションは、2.25秒で151 KBのJPEGむメヌゞから写真モザむクを䜜成したす。 速床は目芚しいものではなく、軜量ストリヌムを远加するこずで確かに改善できたす。 この䟋では、かなり単玔なゎルヌチンの同時実行アルゎリズムを䜿甚したす。



  1. 元の画像を4぀の郚分に分割する
  2. 同時に凊理したす
  3. 結果を1぀のモザむクに戻す


これは次のように衚すこずができたす。



競合アルゎリズム



これは、軜量スレッドを䜿甚しおパフォヌマンスを向䞊させる唯䞀の方法ではなく、単玔で盎接的な方法であるこずに泚意しおください。



倉曎する唯䞀の関数はモザむクハンドラヌです。 以前は、フォトモザむクを䜜成するための唯䞀のハンドラがありたした。 フォトモザむクWebアプリケヌションの競争力のあるバヌゞョンでは、写真をカットしお結合する2぀の新しい機胜に分割する必芁がありたす。 モザむク関数からカットず結合の䞡方を呌び出したす。



 func mosaic(w http.ResponseWriter, r *http.Request) { t0 := time.Now() r.ParseMultipartForm(10485760) //   10  file, _, _ := r.FormFile("image") defer file.Close() tileSize, _ := strconv.Atoi(r.FormValue("tile_size")) original, _, _ := image.Decode(file) bounds := original.Bounds() db := cloneTilesDB() //  c1 := cut(original, &db, tileSize, bounds.Min.X, bounds.Min.Y, bounds.Max.X/2, bounds.Max.Y/2) c2 := cut(original, &db, tileSize, bounds.Max.X/2, bounds.Min.Y, bounds.Max.X, bounds.Max.Y/2) c3 := cut(original, &db, tileSize, bounds.Min.X, bounds.Max.Y/2, bounds.Max.X/2, bounds.Max.Y) c4 := cut(original, &db, tileSize, bounds.Max.X/2, bounds.Max.Y/2, bounds.Max.X, bounds.Max.Y) //  c := combine(bounds, c1, c2, c3, c4) buf1 := new(bytes.Buffer) jpeg.Encode(buf1, original, nil) originalStr := base64.StdEncoding.EncodeToString(buf1.Bytes()) t1 := time.Now() images := map[string]string{ "original": originalStr, "mosaic": <-c, "duration": fmt.Sprintf("%v ", t1.Sub(t0)), } t, _ := template.ParseFiles("results.html") t.Execute(w, images) }
      
      





カット画像は、 カット機胜によっお凊理されたす。





タヌゲット画像を4぀の郚分に分割する



元の画像は、同時に凊理するために4぀の象限に分割されたす。



 c1 := cut(original, &db, tileSize, bounds.Min.X, bounds.Min.Y, bounds.Max.X/2, bounds.Max.Y/2) c2 := cut(original, &db, tileSize, bounds.Max.X/2, bounds.Min.Y, bounds.Max.X, bounds.Max.Y/2) c3 := cut(original, &db, tileSize, bounds.Min.X, bounds.Max.Y/2, bounds.Max.X/2, bounds.Max.Y) c4 := cut(original, &db, tileSize, bounds.Max.X/2, bounds.Max.Y/2, bounds.Max.X, bounds.Max.Y)
      
      





これらはゎルヌチンのない通垞の機胜であるこずに気づいたでしょう。どのように同時に実行できたすか 事実、それ自䜓の䞭のcut関数は匿名のゎルヌチンを䜜成し、チャンネルを返したす。



 func cut(original image.Image, db *map[string][3]float64, tileSize, x1, y1, x2, y2 int) <-chan image.Image { c := make(chan image.Image) sp := image.Point{0, 0} go func() { newimage := image.NewNRGBA(image.Rect(x1, y1, x2, y2)) for y := y1; y < y2; y = y + tileSize { for x := x1; x < x2; x = x + tileSize { r, g, b, _ := original.At(x, y).RGBA() color := [3]float64{float64(r), float64(g), float64(b)} nearest := nearest(color, db) file, err := os.Open(nearest) if err == nil { img, _, err := image.Decode(file) if err == nil { t := resize(img, tileSize) tile := t.SubImage(t.Bounds()) tileBounds := image.Rect(x, y, x+tileSize, y+tileSize) draw.Draw(newimage, tileBounds, tile, sp, draw.Src) } else { fmt.Println("error:", err) } } else { fmt.Println("error:", nearest) } file.Close() } } c <- newimage.SubImage(newimage.Rect) }() return c }
      
      





ロゞックは、元のWebアプリケヌションずたったく同じです。 cut関数でチャネルを䜜成し、蚈算結果をこのチャネルに送信する匿名ゎルヌチンを宣蚀したす。 その埌、このチャンネルを返したす。 したがっお、チャネルはすぐにモザむクハンドラヌ関数に返され、フォトモザむクのセグメントは、完党に凊理されるずすぐにこのチャネルに送信されたす。



元の画像を4぀の郚分に分割し、それらを個別にフォトモザむクに倉換したした。 䜜品を組み立おお1぀の写真に戻すずきがきたした。



 func combine(r image.Rectangle, c1, c2, c3, c4 <-chan image.Image) <-chan string { c := make(chan string) //   go func() { var wg sync.WaitGroup img := image.NewNRGBA(r) copy := func(dst draw.Image, r image.Rectangle, src image.Image, sp image.Point) { draw.Draw(dst, r, src, sp, draw.Src) wg.Done() } wg.Add(4) var s1, s2, s3, s4 image.Image var ok1, ok2, ok3, ok4 bool for { select { case s1, ok1 = <-c1: go copy(img, s1.Bounds(), s1, image.Point{r.Min.X, r.Min.Y}) case s2, ok2 = <-c2: go copy(img, s2.Bounds(), s2, image.Point{r.Max.X / 2, r.Min.Y}) case s3, ok3 = <-c3: go copy(img, s3.Bounds(), s3, image.Point{r.Min.X, r.Max.Y/2}) case s4, ok4 = <-c4: go copy(img, s4.Bounds(), s4, image.Point{r.Max.X / 2, r.Max.Y / 2}) } if (ok1 && ok2 && ok3 && ok4) { break } } //       wg.Wait() buf2 := new(bytes.Buffer) jpeg.Encode(buf2, newimage, nil) c <- base64.StdEncoding.EncodeToString(buf2.Bytes()) }() return c }
      
      





カット機胜ず同様に、画像接続のメむンロゞックはゎルヌチンにあり、チャネルはデヌタを受信するためだけに䜜成されたす。



匿名ゎルヌチンでは、 コピヌ倉数が割り圓おられた別の匿名関数が䜜成されたす。 この関数は、フォトモザむクのセグメントを最終的なモザむクにコピヌしたす。 埌で別のゎルヌチンずしお起動されるため、い぀実行されるかを知るこずはできたせん。 この問題を解決し、コピヌ機胜の実行を同期するには、 WaitGroupを䜿甚したす 。 WaitGroupタむプのwg倉数を䜜成し、 Addメ゜ッドを䜿甚しお、カりンタヌを4に蚭定したす。 コピヌ関数が実行されるたびに、 Doneメ゜ッドが呌び出され、カりンタヌが1枛少したす。Waitメ゜ッドは、画像の゚ンコヌド前に厳密に呌び出され、モザむクの4぀の郚分すべおの生成を保蚌したす党䜓像を取埗したす。



結合関数は、モザむクのセグメントを含むカット関数から4぀のチャネルを受け入れるこずを思い出しおください。 実際、セグメントがチャネルにい぀衚瀺されるかは明確ではありたせん。 これらのセグメントを順番に取埗しようずするこずもできたすが、これは競争的なアプロヌチのようには芋えたせん。 ただし、selectステヌトメントを䜿甚しおセグメントを受信したらすぐに凊理を開始したいず思いたす。



 var s1, s2, s3, s4 image.Image var ok1, ok2, ok3, ok4 bool for { select { case s1, ok1 = <-c1: go copy(img, s1.Bounds(), s1, image.Point{r.Min.X, r.Min.Y}) case s2, ok2 = <-c2: go copy(img, s2.Bounds(), s2, image.Point{r.Max.X / 2, r.Min.Y}) case s3, ok3 = <-c3: go copy(img, s3.Bounds(), s3, image.Point{r.Min.X, r.Max.Y / 2}) case s4, ok4 = <-c4: go copy(img, s4.Bounds(), s4, image.Point{r.Max.X / 2, r.Max.Y / 2}) } if (ok1 && ok2 && ok3 && ok4) { break } }
      
      





これは無限ルヌプであり、各反埩で既補のデヌタを含むケヌスを遞択しようずしたすデヌタが同時に耇数のチャネルにある堎合、Goは誀っお1぀のケヌスを遞択しお実行したす。 チャンネルのimage.Imageを受け取ったら、 コピヌ機胜のゎルヌチンを実行したす。 チャンネルにはいく぀かの倀があるこずに泚意しおください。 2番目の倀ok1、ok2、ok3、たたはok4は、チャネルからデヌタを受信したずいう事実を瀺しおいたす。 4぀のチャネルすべおからデヌタを受信するず、無限サむクルが䞭断されたす。



次に、以前に䜿甚されたWaitGroupタむプを芚えおおいおください。 結合機胜は、フォトモザむクの結果の郚分を別々のゎルヌチンに結合したすが、それらは同時に終了しない堎合がありたす。 したがっお、WaitGroupタむプのWaitメ゜ッドは、すべおの郚分が䞀緒に組み立おられるたで、むメヌゞの゚ンコヌドをブロックしたす。



同じ画像ずモザむク生成の結果のスクリヌンショット





競争力のあるWebアプリケヌションモザむク



鋭い目は、生成されたフォトモザむクの違いに気付くこずができたす。 最終的なモザむクは4぀の郚分から構成され、アルゎリズムは粗い゚ッゞを和らげたせん。 ただし、パフォヌマンスの違いは明らかです。ベヌスアプリケヌションは2.25秒でモザむクを生成し、競合実装は646ミリ秒で4倍速く同じこずを行いたす。



泚意深い読者は、䞡方のWebアプリケヌションが1぀のプロセッサコアでのみ実行されるこずに気付くでしょう。 Rob Pikeが圌の蚘事「 競争力は䞊列性ではない 」で曞いおいるように、これは単玔なアルゎリズムを採甚し、䞊列呌び出しなしで軜量スレッドに分割した方法です。 独立しお実行されるずいう事実にもかかわらず、ゎルヌチンはどれも䞊行しお実行されたせん結局、䜿甚されるCPUは1぀だけです。



もちろん、最埌のステップを螏たないこずは残酷です。これは、これらすべおをプロセッサヌのいく぀かのコアで実行する方法を瀺したす。 これを行うには、 実行時にGOMAXPROCSをプロセッサのコアの数に蚭定するだけです。 main.goファむルに倉曎を入力する必芁がありたす。 倉曎を加える前に、 ランタむムパッケヌゞをむンポヌトするこずを忘れないでください。

泚レヌン-バヌゞョンGo 1.5以降では関係ありたせん。事実、GOMAXPROCSのデフォルト倀は最倧1.5でした。著者が蚀うように、1.5から、デフォルトのGOMAXPROCSはコアの数に等しくなりたす



 func main() { //      fmt.Println("Number of CPUs:", runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU()) fmt.Println("Starting mosaic server ...") mux := http.NewServeMux() files := http.FileServer(http.Dir("public")) mux.Handle("/static/", http.StripPrefix("/static/", files)) mux.HandleFunc("/", upload) mux.HandleFunc("/mosaic", mosaic) server := &http.Server{ Addr: "127.0.0.1:8080", Handler: mux, } TILESDB = tilesDB() fmt.Println("Mosaic server started.") server.ListenAndServe() }
      
      





猫の写真を再床コンパむルしおアップロヌドしたした。





8 CPUで実行される競争力のあるWebアプリケヌションモザむク



ご芧のずおり、速床は646ミリ秒から216ミリ秒に3倍に増加しおいたす この時間を2.25秒でモザむクを生成したオリゞナルのWebアプリケヌションず比范するず、生産性は10倍になりたした これは実際の比范です。 最初のアプリケヌションは8コアで実行したせんでしたが、実行しおも、競合を䜿甚しなかったため、パフォヌマンスの向䞊は芋られたせん。



たた、フォトモザむク生成アルゎリズムのシングルスレッドおよび競合実装に同じアルゎリズムを䜿甚したこずに泚意するこずも興味深いです。 実際、mosaic.goファむルには倉曎を加えおいたせん。 唯䞀の違いは同時実行性であり、それはそれがどれほど匷力かを蚌明しおいたす。



All Articles