Compare commits

...

1 Commits

Author SHA1 Message Date
Matthew Butterick 2db86cc0b4 improve parallel rendering 5 years ago

@ -21,36 +21,43 @@
(define (preheat-cache starting-dir) (define (preheat-cache starting-dir)
(unless (and (path-string? starting-dir) (directory-exists? starting-dir)) (unless (and (path-string? starting-dir) (directory-exists? starting-dir))
(raise-argument-error 'preheat-cache "directory" starting-dir)) (raise-argument-error 'preheat-cache "directory" starting-dir))
(define worker-places (for/list ([i (in-range (processor-count))])
(place ch ;; if a file is already in the cache, no need to hit it again.
(let loop () ;; this allows partially completed preheat jobs to resume.
(define result (with-handlers ([exn:fail? (λ (e) #false)]) (define uncached-paths
(path->hash (place-channel-get ch))))
(place-channel-put ch result)
(loop)))))
(define paths-that-should-be-cached
(for/list ([path (in-directory starting-dir)] (for/list ([path (in-directory starting-dir)]
#:when (for/or ([proc (in-list (list preproc-source? #:when (for/or ([proc (in-list (list preproc-source?
markup-source? markup-source?
markdown-source? markdown-source?
pagetree-source?))]) pagetree-source?))])
(proc path))) (proc path))
#:unless (path-cached? path))
path)) path))
;; if a file is already in the cache, no need to hit it again. (define worker-evts
;; this allows partially completed preheat jobs to resume. (for/list ([wpidx (in-range (processor-count))])
(define uncached-paths (filter-not path-cached? paths-that-should-be-cached)) (define wp
(place ch
(let loop ()
(define path (place-channel-put/get ch (list 'want-job)))
(place-channel-put ch (list 'job-finished path
(with-handlers ([exn:fail? (λ (e) #f)])
(path->hash path))))
(loop))))
(handle-evt wp (λ (val) (list* wpidx wp val)))))
;; compile the paths in groups, so they can be incrementally saved. (let loop ([paths uncached-paths][actives null])
;; that way, if there's a failure, the progress is preserved. (unless (and (null? paths) (null? actives))
;; but the slowest file in a group will prevent further progress. (match (apply sync worker-evts)
(for ([path-group (in-list (slice-at uncached-paths (length worker-places)))]) [(list wpidx wp 'want-job)
(for ([path (in-list path-group)] (match paths
[wp (in-list worker-places)]) [(? null?) (loop null actives)]
(message (format "caching: ~a" (find-relative-path starting-dir path))) [(cons path rest)
(place-channel-put wp path)) (place-channel-put wp path)
(for ([path (in-list path-group)] (message (format "caching on core ~a: ~a" (add1 wpidx) (find-relative-path starting-dir path)))
[wp (in-list worker-places)]) (loop rest (cons wpidx actives))])]
(match (place-channel-get wp) [(list wpidx wp 'job-finished path result)
[#false (message (format "compile failed: ~a" path))] (if result
[result (cache-ref! (paths->key path) (λ () result))])))) (cache-ref! (paths->key path) (λ () result))
(message (format "caching failed on core ~a: ~a" (add1 wpidx) (find-relative-path starting-dir path))))
(loop paths (remq wpidx actives))]))))

@ -4,6 +4,7 @@
racket/match racket/match
racket/place racket/place
racket/list racket/list
racket/dict
sugar/test sugar/test
sugar/define sugar/define
sugar/file sugar/file
@ -56,53 +57,78 @@
(reset-mod-date-hash!) (reset-mod-date-hash!)
(cond (cond
[parallel? [parallel?
(define worker-places
(for/list ([i (in-range (processor-count))]) (define source-paths
(place ch (let ()
(let loop () (define flattened-paths
(match-define (list path poly-target) (place-channel-get ch)) (remove-duplicates
(define render-result (sort
(let render ([failures 0][exn-msg #f]) (let loop ([paths paths])
(cond (if (null? paths)
[(= 3 failures) exn-msg] null
[else (match (->complete-path (car paths))
(with-handlers ([exn:fail? [(? pagetree-source? pt) (append (loop (pagetree->paths pt)) (loop (cdr paths)))]
(λ (e) [path (cons path (loop (cdr paths)))])))
(sleep 0.01) string<?
(render (add1 failures) (exn-message e)))]) #:key path->string)))
(parameterize ([current-poly-target poly-target]) (for*/list ([p (in-list flattened-paths)]
(match-define-values (_ _ ms _) [maybe-source-path (in-value (->source-path p))]
(time-apply render-from-source-or-output-path (list path))) #:when (and maybe-source-path (file-exists? maybe-source-path)))
ms))]))) maybe-source-path)))
(place-channel-put ch render-result)
(loop))))) ;; initialize the workers
(define worker-evts
(define flattened-paths (for/list ([wpidx (in-range (processor-count))])
(let loop ([paths paths]) (define wp (place ch
(if (null? paths) (let loop ()
null (match-define (cons path poly-target)
(match (->complete-path (car paths)) (place-channel-put/get ch (list 'wants-job)))
[(? pagetree-source? pt) (append (loop (pagetree->paths pt)) (loop (cdr paths)))] (parameterize ([current-poly-target poly-target])
[path (cons path (loop (cdr paths)))])))) (place-channel-put/get ch (list 'wants-lock (->output-path path)))
(match-define-values (_ _ ms _)
(define source-paths (for*/list ([p (in-list flattened-paths)] (time-apply render-from-source-or-output-path (list path)))
[maybe-source-path (in-value (->source-path p))] (place-channel-put ch (list 'finished-job path ms)))
#:when (and maybe-source-path (file-exists? maybe-source-path))) (loop))))
maybe-source-path)) (handle-evt wp (λ (val) (list* wpidx wp val)))))
(for ([source-path-group (in-list (slice-at (shuffle source-paths) (length worker-places)))]) (define poly-target (current-poly-target))
(for ([source-path (in-list source-path-group)]
[(wp wpidx) (in-indexed worker-places)]) ;; `locks` and `blocks` are (listof (cons/c evt? path?))
(place-channel-put wp (list source-path (current-poly-target)))) (let loop ([source-paths source-paths][locks-in null][blocks-in null])
(for ([source-path (in-list source-path-group)] ;; try to unblock blocked workers
[(wp wpidx) (in-indexed worker-places)]) (define-values (locks blocks)
(match (place-channel-get wp) (for/fold ([locks locks-in]
[(? number? ms) [blocks null])
(message (format "rendered parallel on core ~a /~a ~a" ([block (in-list blocks-in)])
(add1 wpidx) (match-define (cons wp path) block)
(find-relative-path (current-project-root) (->output-path source-path)) (cond
(if (< ms 1000) (format "(~a ms)" ms) (format "(~a s)" (/ ms 1000.0)))))] [(member path (dict-values locks))
[exn-msg (raise (exn:fail exn-msg (current-continuation-marks)))])))] (values locks (cons block blocks))]
[else
(place-channel-put wp 'lock-approved)
(values (cons block locks) blocks)])))
;; no source paths means all jobs have been assigned
;; no locks means no jobs are in progress
;; therefore we must be done.
(unless (and (null? source-paths) (null? locks))
(match (apply sync worker-evts)
[(list wpidx wp 'wants-job)
(match source-paths
[(? null?) (loop null locks blocks)]
[(cons path rest)
(place-channel-put wp (cons path poly-target))
(loop rest locks blocks)])]
[(list wpidx wp 'finished-job path ms)
(message
(format "rendered parallel on core ~a /~a ~a"
(add1 wpidx)
(find-relative-path (current-project-root) (->output-path path))
(if (< ms 1000) (format "(~a ms)" ms) (format "(~a s)" (/ ms 1000.0)))))
(loop source-paths (match (assoc wp locks)
[#false locks]
[lock (remove lock locks)]) blocks)]
[(list wpidx wp 'wants-lock path)
(loop source-paths locks (append blocks (list (cons wp path))))])))]
[else (for-each render-from-source-or-output-path paths)])) [else (for-each render-from-source-or-output-path paths)]))
(define (pagetree->paths pagetree-or-path) (define (pagetree->paths pagetree-or-path)
@ -271,22 +297,28 @@
(define template-path (or maybe-template-path (get-template-for source-path output-path))) (define template-path (or maybe-template-path (get-template-for source-path output-path)))
(unless template-path (unless template-path
(raise-argument-error 'render-markup-or-markdown-source (format "valid template path~a" (if (has-inner-poly-ext? source-path) (format " for target ~a" (current-poly-target)) "")) template-path)) (raise-argument-error 'render-markup-or-markdown-source (format "valid template path~a" (if (has-inner-poly-ext? source-path) (format " for target ~a" (current-poly-target)) "")) template-path))
(render-from-source-or-output-path template-path) ; because template might have its own preprocessor source ;; use a temp file so that multiple (possibly parallel) renders
;; do not compete for write access to the same template
(define temp-template (make-temporary-file "pollentmp~a"
(or (->source-path template-path) template-path)))
(render-from-source-or-output-path temp-template) ; because template might have its own preprocessor source
(parameterize ([current-output-port (current-error-port)] (parameterize ([current-output-port (current-error-port)]
[current-namespace (make-base-namespace)]) [current-namespace (make-base-namespace)])
(define outer-ns (namespace-anchor->namespace render-module-ns)) (define outer-ns (namespace-anchor->namespace render-module-ns))
(namespace-attach-module outer-ns 'pollen/setup) (namespace-attach-module outer-ns 'pollen/setup)
(eval (with-syntax ([MODNAME (gensym)] (begin0
[SOURCE-PATH-STRING (->string source-path)] (eval (with-syntax ([MODNAME (gensym)]
[TEMPLATE-PATH-STRING (->string template-path)]) [SOURCE-PATH-STRING (->string source-path)]
#'(begin [TEMPLATE-PATH-STRING (->string temp-template)])
(module MODNAME pollen/private/render-helper #'(begin
#:source SOURCE-PATH-STRING (module MODNAME pollen/private/render-helper
#:template TEMPLATE-PATH-STRING #:source SOURCE-PATH-STRING
#:result-id result) #:template TEMPLATE-PATH-STRING
(require 'MODNAME) #:result-id result)
result))))) (require 'MODNAME)
result)))
(delete-file temp-template))))
(define (templated-source? path) (define (templated-source? path)
(or (markup-source? path) (markdown-source? path))) (or (markup-source? path) (markdown-source? path)))

Loading…
Cancel
Save