Sommario
Con il linguaggio Go, studieremo un pool di goroutine che si occuperà di snellire un po’ il lavoro di compilazione di molti file sorgenti LaTeX.
Situazione
In una cartella ho salvato 222 file sorgenti LaTeX relativi a schede tecniche (generate in automatico). Il passo successivo è quello di compilare i sorgenti con il compositore pdflatex per produrre i corrispondenti 222 file pdf.
Quale occasione migliore per sperimentare le doti di esecuzione concorrente del linguaggio Go?
Funzionamento
Poiché la procedura in Go non può fare altro che lanciare il comando di sistema esterno — che vedremo or ora — andremo a sovraccaricare le risorse del sistema operativo.
Meglio quindi escludere la soluzione di dedicare ad ogni compilazione una goroutine, per lavorare invece con un piccolo numero di linee di esecuzione indipendente che prelevano uno dopo l’altro nomi di file LaTeX.
Come sanno bene gli utenti TeX, il comando di compilazione da impartire in console, o terminale è il seguente:
$ pdflatex nomedelfile
Naturalmente occorre che sul sistema sia installata una distribuzione TeX come per esempio TeX Live.
Per fare la stessa cosa in Go, il frammento di codice seguente mostra come utilizzare il pacchetto os/exec della ricca libreria del linguaggio per lanciare il comando esterno.
// caricamento pacchetto import "os/exec" // in qualche funzione filename := "nomefile" // nome di esempio // preparazione del comando cmd := Command("pdflatex", filename) // esecuzione del comando esterno err := cmd.Run() if err != nil { fmt.Println(err) }
Pool di goroutine (di nuovo ah ah)
// pool di goroutine package main import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "time" ) const dirpath = "schedetecniche" var cpu int = runtime.NumCPU() var workers int = cpu func main() { t := time.Now() runtime.GOMAXPROCS(cpu) tfiles := getTeXFileNames() done := compileTeXFiles(tfiles) for i := 1; i < len(tfiles)+1; i++ { fmt.Printf("Compilazione [%d] in %v\n", i, <-done) } fmt.Println("Tempo totale: ", time.Since(t)) fmt.Print("Premere invio") var a string fmt.Scanln(&a) } func compile(done chan<- time.Duration, filename string) { opt := fmt.Sprintf("-output-directory=%s", dirpath) file := dirpath + "/" + filename cmd := exec.Command("pdflatex", opt, file) start := time.Now() err := cmd.Run() if err != nil { fmt.Println(err) } done <- time.Since(start) } func compileTeXFiles(f []string) <-chan time.Duration { done := make(chan time.Duration, workers) files := make(chan string, workers) go func() { for _, fn := range f { files <- fn } close(files) }() for i := 0; i < workers; i++ { // goroutine pool go func() { for filename := range files { compile(done, filename) } }() } return done } func getTeXFileNames() (s []string) { s = make([]string, 0, 1000) wf := func(path string, info os.FileInfo, err error) error { fn := info.Name() if ext := filepath.Ext(fn); ext == ".tex" { s = append(s, fn) } return nil } err := filepath.Walk(dirpath, wf) if err != nil { fmt.Println(err) } return }
Un dato
Su una macchina multicore ad 8 thread, il programma descritto è circa il 75% più veloce che non quello con la compilazione sequenziale…
Alla prossima…
Un saluto.
R.