A CPU Bound műveletek végrehajtása során nyilvánvaló, hogy ha párhuzamosságot teszünk (több gorutin hozzáadásával), akkor a teljesítmény javul. De hogyan működik ez az i/o kötött műveletekben? Dolgozzunk ezen.

Webhelyek feltérképezése

A webhelyek feltérképezése i/o kötött művelet, mert többnyire a hálózati és az i/o műveletekre várunk. Nem használjuk ki a szálakat, gorutinjaink az idő nagy részében várakoznak.

package iobench

import (
 "net/http"
 "sync"
)

func generateLinks(url string) []string {
 urls := make([]string, 10)
 for i := 0; i < 10; i++ {
  links[i] = url
 }
 return urls
}

func syncCrawl(urls []string) {
 for _, url := range urls {
  http.Get(url)
 }
}

func concurrentCrawl(urls []string) {
 var wg sync.WaitGroup
 wg.Add(len(urls))
 for _, url := range urls {
  go func(url string) {
   defer wg.Done()
   http.Get(url)
  }(url)
 }
 wg.Wait()
}

A syncCrawl funkcióban szinkronizálási hívást végzünk, így sorrendben fogjuk feltérképezni a webhelyeket.

A concurrentCrawl funkcióban aszinkronhívást végzünk, így a webhelyeket egyidejűleg térképezzük fel a párhuzamosból. Ne feledje, hogy a párhuzamos és párhuzamos programok különböznek egymástól. A párhuzamos programok soron kívül futnak be, többnyire várakoznak, így nem használják aktívan a hardverszálakat. A párhuzamos programok szintén nem futnak be, de párhuzamosan. Gondolj csak arra, hogy te vagy az egyetlen, aki tésztát készít, felforralod a vizet, egyidejűleg működik, és veszel tésztát, közben szószt készítesz, felforr a víz, erre vársz. Más esetben gondolja úgy, hogy 2 fő, és szószt készít, az egyik salátát, a másik pedig tésztát készít.

Benchmarking

Határozzuk meg a kódot. Az i/o működés szimulálására teszek időt.Alvó üzemmódban, mert a valós rendszerben az i/o műveletekre várunk.

package iobench

import (
 "net/http"
 "net/http/httptest"
 "testing"
 "time"
)

func BenchmarkSyncRead(b *testing.B) {
 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  time.Sleep(time.Millisecond) 
  w.WriteHeader(200)
 }))
 defer srv.Close()
 urls := generateLinks(srv.URL)
 b.ResetTimer()
 for i := 0; i < b.N; i++ {
  syncCrawl(urls)
 }
}

func BenchmarkConcurrentRead(b *testing.B) {
 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  time.Sleep(time.Millisecond)
  w.WriteHeader(200)
 }))
 defer srv.Close()
 urls := generateLinks(srv.URL)
 b.ResetTimer()
 for i := 0; i < b.N; i++ {
  concurrentCrawl(urls)
 }
}

Eredmények

Ha korlátozom a cpu-t eggyel, akkor egyszálú környezetben futok. Lásd ezt a párhuzamosságot. Nincs paralizmus, de még mindig kihasználom a koncrreunitást. Amíg egy gorutin a hálózati műveletekre vár, a go futási idő kontextusban váltja a gorutinokat.

További cpu hozzáadása nem segít

Nem használunk aktívan cpu-t. Még ha 5-re állítom is a cpu-t, akkor sem látunk különbséget. Mert a cpu semmiben nem tud segíteni. ezt be tudom bizonyítani.

Lásd az eredmény szinte nincs különbség. Ha CPU-hoz kötött műveleteket végeznék, láthatná a különbséget.