Archivi delle etichette: pdflatex

Compilazione LaTeX in pool

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.