raco make: improve parallelism

The `require` macro now logs "prefetch" messages when it sees a `require`
with multiple module paths. The prefix information is approximate, since
parsing a `require` subform might depend on imports from a previous subform,
but in the common case, there are many obvious module paths to prefetch.

The parallel mode of `raco make` watches for prefetch messages and
records the suggested "prefetch" paths so they can be compiled by other
processes.
This commit is contained in:
Matthew Flatt 2014-05-26 18:17:45 +01:00
parent 5c38034300
commit 9e3b984463
5 changed files with 120 additions and 16 deletions

View File

@ -5,6 +5,7 @@
compiler/compiler compiler/compiler
dynext/file dynext/file
setup/parallel-build setup/parallel-build
setup/path-to-relative
racket/match) racket/match)
(module test racket/base) (module test racket/base)
@ -98,14 +99,18 @@
dest)))))))] dest)))))))]
;; Parallel make: ;; Parallel make:
[else [else
(define path-cache (make-hash))
(or (parallel-compile-files (or (parallel-compile-files
source-files source-files
#:worker-count (worker-count) #:worker-count (worker-count)
#:handler (lambda (type work msg out err) #:handler (lambda (id type work msg out err)
(define (->rel p)
(path->relative-string/library p #:cache path-cache))
(match type (match type
['done (when (verbose) (printf " Made ~a\n" work))] ['start (when (verbose) (printf " ~a making ~a\n" id (->rel work)))]
['output (printf " Output from: ~a\n~a~a" work out err)] ['done (when (verbose) (printf " ~a made ~a\n" id (->rel work)))]
[else (printf " Error compiling ~a\n~a\n~a~a" work msg out err)])) ['output (printf " ~a output from: ~a\n~a~a" id work out err)]
[else (printf " ~a error compiling ~a\n~a\n~a~a" id work msg out err)]))
#:options (let ([cons-if-true (lambda (bool carv cdrv) #:options (let ([cons-if-true (lambda (bool carv cdrv)
(if bool (if bool
(cons carv cdrv) (cons carv cdrv)

View File

@ -852,7 +852,18 @@ corresponds to the default @tech{module name resolver}.
of a submodule, and it's intended for use in @racket[(submod "." ....)] of a submodule, and it's intended for use in @racket[(submod "." ....)]
and @racket[(submod ".." ....)] forms.} and @racket[(submod ".." ....)] forms.}
} As @racket[require] prepares to handle a sequence of
@racket[require-spec]s, it logs a ``prefetch'' message to the
@tech{current logger} at the @racket['info] level, using the name
@racket['module-prefetch], and including message data that is a list
of two elements: a list of @tech{module paths} that appear to be
imported, and a directory path to use for relative module paths. The
logged list of module paths may be incomplete, but a compilation
manager can use approximate prefetch information to start on
compilations in parallel.
@history[#:changed "6.0.1.10" @elem{Added prefetch logging.}]}
@defform[(local-require require-spec ...)]{ @defform[(local-require require-spec ...)]{

View File

@ -366,7 +366,36 @@
(syntax/loc stx (syntax/loc stx
(#%require new-in ...)))] (#%require new-in ...)))]
[(_ in ...) [(_ in ...)
(syntax/loc stx (begin (require in) ...))])))) ;; Prefetch on simple module paths:
(let ([prefetches
(let loop ([in (syntax->list #'(in ...))])
(cond
[(null? in) null]
[(let ([a (syntax->datum (car in))])
(and (module-path? a) a))
=> (lambda (a)
(cons a (loop (cdr in))))]
[else
(let ([a (syntax->list (car in))])
(if (and a
(let ([i (car a)])
(and (identifier? i)
(or (free-identifier=? #'for-something #'for-syntax)
(free-identifier=? #'for-something #'for-template)
(free-identifier=? #'for-something #'for-label)))))
(loop (append (cdr a) (cdr in)))
(loop (cdr in))))]))])
(unless (null? prefetches)
(log-message (current-logger)
'info
'module-prefetch
(format "module-prefetch: ~s in: ~s"
prefetches
(current-load-relative-directory))
(list prefetches (current-load-relative-directory))
#f))
(syntax/loc stx
(begin (require in) ...)))]))))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; require transformers ;; require transformers

View File

@ -12,6 +12,7 @@
racket/future racket/future
compiler/find-exe compiler/find-exe
racket/place racket/place
syntax/modresolve
(for-syntax racket/base)) (for-syntax racket/base))
@ -95,6 +96,9 @@
;(when last (printer (current-output-port) "made" "~a" (cc-name cc))) ;(when last (printer (current-output-port) "made" "~a" (cc-name cc)))
#t] #t]
[else (eprintf "Failed trying to match:\n~e\n" result-type)]))] [else (eprintf "Failed trying to match:\n~e\n" result-type)]))]
[(list _ (list 'ADD fn))
;; Currently ignoring queued individual files
#f]
[else [else
(match work (match work
[(list-rest (list cc file last) message) [(list-rest (list cc file last) message)
@ -197,29 +201,40 @@
(field [results (void)]) (field [results (void)])
(inspect #f) (inspect #f)
(define seen
(for/hash ([k (in-list filelist)])
(values k #t)))
(define/public (work-done work wrkr msg) (define/public (work-done work wrkr msg)
(define id (send wrkr get-id))
(match msg (match msg
[(list result-type out err) [(list result-type out err)
(match result-type (match result-type
[(list 'LOCK fn) (lm/lock lock-mgr fn wrkr) #f] [(list 'LOCK fn) (lm/lock lock-mgr fn wrkr) #f]
[(list 'UNLOCK fn) (lm/unlock lock-mgr fn) #f] [(list 'UNLOCK fn) (lm/unlock lock-mgr fn) #f]
[(list 'ERROR msg) (handler 'error work msg out err) [(list 'ERROR msg) (handler id 'error work msg out err)
(set! results #f) (set! results #f)
#t] #t]
['DONE ['DONE
(define (string-!empty? s) (not (zero? (string-length s)))) (define (string-!empty? s) (not (zero? (string-length s))))
(if (ormap string-!empty? (list out err)) (if (ormap string-!empty? (list out err))
(handler 'output work "" out err) (handler id 'output work "" out err)
(handler 'done work "" "" "")) (handler id 'done work "" "" ""))
#t])] #t])]
[(list 'ADD fn)
(unless (hash-ref seen fn #f)
(set! filelist (cons fn filelist))
(set! seen (hash-set seen fn #t)))
#f]
[else [else
(handler 'fatalerror (format "Error matching work: ~a queue ~a" work filelist) "" "") #t])) (handler id 'fatalerror (format "Error matching work: ~a queue ~a" work filelist) "" "") #t]))
(define/public (get-job workerid) (define/public (get-job workerid)
(match filelist (match filelist
[(cons hd tail) [(cons hd tail)
(define-values (dir file b) (split-path hd)) (define-values (dir file b) (split-path hd))
(set! filelist tail) (set! filelist tail)
(handler workerid 'start hd "" "" "")
(values hd (list (->bytes hd) (dir->bytes dir) (->bytes file) null))] (values hd (list (->bytes hd) (dir->bytes dir) (->bytes file) null))]
[(list) null])) [(list) null]))
(define/public (has-jobs?) (not (null? filelist))) (define/public (has-jobs?) (not (null? filelist)))
@ -252,6 +267,7 @@
(define cop (current-output-port)) (define cop (current-output-port))
(define cep (current-error-port)) (define cep (current-error-port))
(define (send/recv msg) (send/msg msg) (recv/req)) (define (send/recv msg) (send/msg msg) (recv/req))
(define (send/add fn) (send/msg (list 'ADD fn)))
(define (send/resp type) (define (send/resp type)
(send/msg (list type (get-output-string out-str-port) (get-output-string err-str-port)))) (send/msg (list type (get-output-string out-str-port) (get-output-string err-str-port))))
(define (pp x) (fprintf cep "COMPILING ~a ~a ~a ~a\n" worker-id name file x)) (define (pp x) (fprintf cep "COMPILING ~a ~a ~a ~a\n" worker-id name file x))
@ -289,7 +305,13 @@
[current-error-port err-str-port] [current-error-port err-str-port]
;[manager-compile-notify-handler pp] ;[manager-compile-notify-handler pp]
) )
(cmc (build-path dir file)))
;; Watch for module-prefetch events, and queue jobs in response
(define t (start-prefetch-thread send/add))
(cmc (build-path dir file))
(kill-thread t))
(send/resp 'DONE))] (send/resp 'DONE))]
[x (send/error (format "DIDNT MATCH A ~v\n" x))] [x (send/error (format "DIDNT MATCH A ~v\n" x))]
[else (send/error (format "DIDNT MATCH A\n"))])))) [else (send/error (format "DIDNT MATCH A\n"))]))))
@ -304,3 +326,39 @@
(setup-fprintf (current-output-port) #f "--- parallel build using ~a jobs ---" worker-count) (setup-fprintf (current-output-port) #f "--- parallel build using ~a jobs ---" worker-count)
(define collects-queue (make-object collects-queue% collects-tree setup-fprintf append-error '())) (define collects-queue (make-object collects-queue% collects-tree setup-fprintf append-error '()))
(parallel-build collects-queue worker-count)) (parallel-build collects-queue worker-count))
(define (start-prefetch-thread send/add)
(define pf (make-log-receiver (current-logger) 'info 'module-prefetch))
(thread
(lambda ()
(let loop ()
(let ([v (sync pf)])
(define l (vector-ref v 2))
(when (and (list? l)
(= 2 (length l))
(list? (car l))
(path? (cadr l))
(andmap module-path? (car l)))
(define dir (cadr l))
(define (quote? p) (and (pair? p) (eq? (car p) 'quote)))
(define (submod? p) (and (pair? p) (eq? (car p) 'submod)))
;; Add prefetch modules to work queue --- but skip the first one,
;; because it's going to be compiled immediately, anyway:
(for/fold ([prev #f]) ([p (in-list (reverse (car l)))])
(cond
[(or (quote? p)
(and (submod? p) (quote? (cadr p))))
;; skip `quote` module paths
prev]
[else
(when prev
(define path
(let loop ([prev prev])
(cond
[(submod? prev)
(loop (cadr prev))]
[else (resolve-module-path prev (build-path dir "dummy.rkt"))])))
(when (path? path)
(send/add path)))
p])))
(loop))))))

View File

@ -35,7 +35,8 @@
(define (path-ss->rkt p) (define (path-ss->rkt p)
(let-values ([(base name dir?) (split-path p)]) (let-values ([(base name dir?) (split-path p)])
(if (regexp-match #rx"[.]ss$" (path->bytes name)) (if (and (path? name)
(regexp-match #rx"[.]ss$" (path->bytes name)))
(path-replace-suffix p #".rkt") (path-replace-suffix p #".rkt")
p))) p)))