2009-10-14 10 views
47

Da quando ho saputo di -j ho usato -j8 allegramente. L'altro giorno stavo compilando un'installazione di atlante e la marca falliva. Alla fine l'ho rintracciato su cose che sono state fatte fuori ordine - e ha funzionato bene una volta che sono tornato al make-up con il singoletto. Questo mi rende nervoso. Che tipo di condizioni devo osservare quando scrivo i miei file creati per evitare di fare qualcosa di inaspettato con make -j?GNU make's -j opzione

+4

Sei sicuro che sia fare è colpa? La scrittura di makefile corretti è soggetta a errori. –

+1

Meh, anche i makefile generati dagli autotools sono difettosi. Prova a compilare GCC con -j2 o versioni successive. – LiraNuna

risposta

36

Penso che make -j rispetterà le dipendenze specificate nel Makefile; Ad esempio, se si specifica che objA dipende da objB e objC, allora make non inizierà a lavorare su objA fino a quando objB e objC non sono completi.

Molto probabilmente il tuo Makefile non sta specificando l'ordine necessario delle operazioni abbastanza rigorosamente, ed è solo una fortuna che funzioni per te nel caso a thread singolo.

+10

Questo è corretto. Lavoro su una base di codice di circa 20 milioni di righe, principalmente in C con un piccolo C++. È diviso in centinaia di componenti, la metà dei quali usa make, e metà dei quali usa jam. Compro sempre le parallele con l'opzione -j; altrimenti, le build richiederebbero ore. Jam genera le sue dipendenze, quindi i componenti che lo usano riescono sempre. Ma i componenti che usano i makefile fatti a mano soffocano occasionalmente, invariabilmente a causa di dipendenze inadeguate. –

+0

Ho visto molto sul web che '-j' si riferisce a quante CPU si desidera compilare. Ho eseguito un test sul mio 8 core machine e 'make' funziona fino a includere' -j8' (oltre questo è il 100% di utilizzo su tutti i core, simile a '-j8'). Ad ogni incremento intero vedrei un altro utilizzo core aumentare di circa * 100% *. Diresti che è una buona regola per sapere cosa specificare come valore '-j'? – Jacksonkr

+0

L'unica buona regola empirica che conosco è fare un "make clean; time make -jn" sul tuo programma, con vari valori di (n), e vedere per quanto tempo ogni build richiede, e quindi andare con qualsiasi impostazione completata in la minor quantità di tempo. Ci sono troppe variabili (velocità della CPU, velocità della RAM, dimensione della RAM, velocità del disco rigido, overhead del processo del sistema operativo, ecc.) Per poter fare generalizzazioni utili. –

22

In breve: assicurarsi che le dipendenze siano corrette e complete.

Se si utilizza una marca con thread singolo, è possibile ignorare ciecamente le dipendenze implicite tra i target. Quando si utilizza parallelo, non è possibile fare affidamento sulle dipendenze implicite. Dovrebbero essere tutti esplicitati. Questa è probabilmente la trappola più comune. In particolare se si utilizzano obiettivi .phony come dipendenze.

This collegamento è un buon primer su alcuni dei problemi con parallelo fare.

+0

+1 Per il link corretto. Ora mi sento di potermi fidare di make -j e di come risolvere i problemi quando si presentano. Vale la pena leggerlo. –

7

Se si dispone di una marca ricorsiva, le cose possono rompersi facilmente. Se non stai facendo una marca ricorsiva, finché le dipendenze sono corrette e complete, non dovresti incontrare alcun problema (salva un bug in make). Vedere Recursive Make Considered Harmful per una descrizione molto più approfondita dei problemi con la marca ricorsiva.

8

Ecco un esempio di un problema che ho riscontrato quando ho iniziato a utilizzare build paralleli. Ho un obiettivo chiamato "fresh" che uso per ricostruire il target da zero (una build "fresca"). In passato, ho codificato il target "fresco" semplicemente indicando "clean" e quindi "build" come dipendenze.

build: ## builds the default target 
clean: ## removes generated files 
fresh: clean build ## works for -j1 but fails for -j2 

che ha funzionato bene fino a quando ho iniziato ad usare in parallelo costruisce, ma con in parallelo costruisce, si tenta di fare entrambe le cose "pulito" e "costruire" contemporaneamente. Così ho cambiato la definizione di "fresco" come segue al fine di garantire l'ordine corretto delle operazioni.

fresh: 
    $(MAKE) clean 
    $(MAKE) build 

Questo è fondamentalmente solo una questione di specificare correttamente le dipendenze. Il trucco è che le build parallele sono più rigide su questo rispetto alle build a thread singolo. Il mio esempio dimostra che un elenco di dipendenze per una determinata destinazione non indica necessariamente l'ordine di esecuzione.

+2

Marca ricorsiva, yuk !. Il modo corretto per dire che make _please fa sempre clean prima di build_ è ovviamente 'build: clean'. – bobbogo

+1

@bobbogo: non voglio sempre fare pulizia prima della compilazione, che nella maggior parte dei casi non è necessario. L'obiettivo "fresco" che ho descritto è fondamentalmente un semplice script che esegue un "make clean" seguito da un "make build" (per quelle rare volte in cui voglio farlo). Non vedo alcun danno nell'eseguire rendere ricorsivamente in questo scenario. – nobar

+2

Come proteggere con un condizionale: 'ifeq ($ (MAKECMDGOALS), fresco); costruire: pulito; endif' (sostituire i punti e virgola con le nuove linee)? – eriktous

1

È consigliabile disporre di un test automatico per testare l'opzione -j di TUTTI i file di creazione. Anche i migliori sviluppatori hanno problemi con l'opzione -j di make. I problemi più comuni sono i più semplici.

myrule: subrule1 subrule2 
    echo done 

subrule1: 
    echo hello 

subrule2: 
    echo world 

Nel normale modo, vedrete ciao -> mondo -> fatto. Con make -j 4, potreste vedere mondo -> ciao -> fatto

Dove ho visto questo accade di più è con la creazione di directory di output.Per esempio:

build: $(DIRS) $(OBJECTS) 
    echo done 

$(DIRS): 
    [email protected] -p [email protected] 

$(OBJECTS): 
    $(CC) ... 
0

solo pensato che vorrei aggiungere alla risposta di subsetbrew in quanto non mostra chiaramente l'effetto. Tuttavia l'aggiunta di alcuni comandi di sospensione lo fa. Bene, funziona su Linux.

poi eseguire make mostra le differenze con:

  • fanno
  • fanno -j4

all: toprule1 

toprule1: botrule2 subrule1 subrule2 
    @echo toprule 1 start 
    @sleep 0.01 
    @echo toprule 1 done 

subrule1: botrule1 
    @echo subrule 1 start 
    @sleep 0.08 
    @echo subrule 1 done 

subrule2: botrule1 
    @echo subrule 2 start 
    @sleep 0.05 
    @echo subrule 2 done 

botrule1: 
    @echo botrule 1 start 
    @sleep 0.20 
    @echo "botrule 1 done (good prerequiste in sub)" 

botrule2: 
    @echo "botrule 2 start" 
    @sleep 0.30 
    @echo "botrule 2 done (bad prerequiste in top)"