42 - Goroutines - NumCPU y GOMAXPROCS

La eficiencia en la ejecución de programas escritos en Go empleando Goroutines se ve beneficiada cuando se ejecutan en equipos con múltiples procesadores físicos.

Si queremos conocer la cantidad de procesadores físicos de nuestra computadora podemos acceder a la función NumCPU que se encuentra en el paquete Runtime:

Programa: ejercicio166.go

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Println("Cantidad de procesadores:", runtime.NumCPU())
}

Por ejemplo en un procesador Intel I7 tiene 8 núcleos.

Cualquier programa que utiliza Goroutines utiliza la mayor cantidad de núcleos para que se ejecuten en forma paralela. Veremos con un ejemplo la diferencia de tiempo en la ejecución de ordenamientos de vectores utilizando un solo procesador o varios procesadores.

La función GOMAXPROCS( número ) del paquete runtime nos permite indicar la cantidad de procesadores que utilizará el programa. Por defecto cualquier programa en Go esta configurado para utlizar todas las CPU disponibles, pero nosotros podemos cambiar dicho valor.

Problema 1:

Definir dos vectores de 50000 elementos con valores aleatorios. Ordenar los vectores empleando Goroutines y verificar si hay diferencias en tiempo de ejecución utilizando 1 CPU o 2 CPU.

Programa: ejercicio167.go

package main

import (
    "fmt"
    "math/rand"
    "time"
    "sync"
    "runtime"
)

var wg sync.WaitGroup

func cargar(vec *[50000]int, aleatorio *rand.Rand) {
    for f := 0; f < len(vec); f++ {
        vec[f] = aleatorio.Intn(100)
    }
}

func ordenar(vec *[50000]int) {
    for k := 1; k < len(vec); k++ {
        for f:= 0; f < len(vec)-k; f++ {
            if vec[f] > vec[f+1] {
                aux := vec[f]
                vec[f] = vec[f+1]
                vec[f+1] = aux
            }
        }
    }
    wg.Done()
}

func diferenciaTiempo(hora1, hora2 time.Time) time.Duration {
    diferencia := hora2.Sub(hora1)	
    return diferencia
}

func main() {
    runtime.GOMAXPROCS(1)
    aleatorio := rand.New(rand.NewSource(time.Now().UnixNano()))
    var vec1 [50000] int
    var vec2 [50000] int
    cargar(&vec1, aleatorio)
    cargar(&vec2, aleatorio)
    
    var hora1, hora2 time.Time	
    hora1 = time.Now()
    wg.Add(2)	
    go ordenar(&vec1)
    go ordenar(&vec2)
    wg.Wait()
    hora2 = time.Now()
    di := diferenciaTiempo(hora1, hora2)
    fmt.Println("Cantidad de segundos de diferencia:", di.Seconds())	
}

Lo primero que hacemos en la main es definir que nuestro programa utilizará solo 1 CPU para las Gorutines:

    runtime.GOMAXPROCS(1)

Definimos los dos vectores y los cargamos con valores aleatorios:

    aleatorio := rand.New(rand.NewSource(time.Now().UnixNano()))
    var vec1 [50000] int
    var vec2 [50000] int
    cargar(&vec1, aleatorio)
    cargar(&vec2, aleatorio)

Como necesitamos saber cuanto tiempo se tarda en realizar los dos procesos procedemos a obtener la hora actual previa a las llamadas de ordenamiento:

    var hora1, hora2 time.Time	
    hora1 = time.Now()

También mediante un WaitGroup identificamos cuando finalizan las dos Goroutines:

    wg.Add(2)	

Llamamos mediante Goroutines para que se ordenen los dos vectores:

    go ordenar(&vec1)
    go ordenar(&vec2)

Cuando el WaitGroup identifica que han finalizado la ejecución de las dos Goroutines procedemos a obtener la diferencia de segundos que han pasado entre la hora actual y la hora tomada antes de llamar a ordenar:

    wg.Wait()
    hora2 = time.Now()
    di := diferenciaTiempo(hora1, hora2)
    fmt.Println("Cantidad de segundos de diferencia:", di.Seconds())	
runtime.GOMAXPROCS(1)

Es decir que en mi equipo ( Intel I7) tarda utilizando un solo núcleo un poco más de 7 segundos.

Cambiemos la línea donde indicamos la cantidad de núcleos por un 2:

func main() {
    runtime.GOMAXPROCS(2)

Compilamos y ejecutamos nuevamente el programa:

runtime.GOMAXPROCS(2)

Es decir que en mi equipo ( Intel I7) tarda utilizando 2 núcleos un poco más de 3 segundos.

El tiempo se ha reducido prácticamente a la mitad debido a que los ordenamientos de los vectores se ha desarrollado en forma paralela.

Por defecto tengamos en cuenta que esta configurado el programa en Go para utilizar todos los núcleos del equipo y no se requiere llamar a la función GOMAXPROCS, salvo que necesitemos que se ejecute en menos núcleos.