pgmp provides a commandline utility through raco. We could instrument and profile the module by running the command raco pgmp --profile example-parser.rkt. To optimize and run the module, we can henceforth use racket -t example-parser.rkt. case will automatically load and use the profile data from the instrumented run.
Alternatively, pgmp also provides API functions to instrument and profile one module from another. We can use the API to instrument and profile the module, then optimize and run the module via dynamic-require.
> (run-with-profiling `(submod "example-parser.rkt" main)) > (save-profile "example-parser.rkt") > (dynamic-require `(submod "example-parser.rkt" main) 0)
In this tutorial, we present the API defined in pgmp/api/exact via an example profiled-guided macro. We define if-r, a macro that reorders its branches based on which branch is executed most frequently.
> (require pgmp)
> (define-syntax (if-r stx) (define profile-query (let ([f (load-profile-query-weight stx)]) (lambda (x) (or (f x) 0)))) (syntax-case stx () [(_ test t f) (let ([t-prof (profile-query #'t)] [f-prof (profile-query #'f)]) (if (< t-prof f-prof) #'(if (not test) f t) #'(if test t f)))]))
First, the function load-profile-query-weight, which executes at compile-time, loads any profile information associated with (syntax-source stx) that has been saved from previous executions and returns a query function. The query function may return #f, but in this example we ignore the distinction between #f and 0 by always returning 0 when the query returns #f.
> (syntax->datum (expand-once #'(if-r (subject-contains-ci email "PLDI") (flag email 'important) (flag email 'spam))))
'(if (subject-contains-ci email "PLDI")
(flag email 'important)
(flag email 'spam))
We see that when we expand if-r before any profile information is generated and saved, the test and branches stay in the original order. While expanding is useful for debugging, it can expose implementation specific details, so do not rely on the output of expand being in a specific form.
Before if-r will reorder its branches, we need to generate some profile information. While we could use run-with-profiling and save-profile to actually profile the profile and save the profiling data, for this example we instead use the API to generate some new profile points example profile data.
> (define make-profile-point (make-profile-point-factory "my-first-pgmp")) > (define profile-point-t (make-profile-point syntax)) > (define profile-point-f (make-profile-point syntax))
We use make-profile-point-factory to create a function that generates fresh profile points using the prefix "my-first-pgmp", and define two new profile points profile-point-t and profile-point-f. The profile point generator expects a piece of syntax. For this example, we assume we have some appropriate piece of syntax syntax which we use where needed places. In practice, this piece of syntax will be something in scope when defining a new profile-guidedmacro. If you are following along in the REPL, use some arbitrary piece of syntax such as #'void.
Next, we generate some profile information and save it to the file expected by load-profile-query-weight. We use profile-file to generate the filename based on syntax. The generated profile information claims that the expression associated with profile-point-t was executed 10 times, while the expression associated with profile-point-f was executed 20 times. Therefore, if-r should reorder the branches.
> (require racket/serialize)
> (with-output-to-file (profile-file syntax) (lambda () (write (serialize (list (cons profile-point-t 10) (cons profile-point-f 20))))) #:exists 'replace)
Finally, we annotate branches with the generated profile points, so profile-query will find the profile information for the generated profile points. We also annotate the whole (if-r ...) with syntax so load-profile-query-weight will load the right file.
> (syntax->datum (expand-once (quasisyntax/loc syntax (if-r (subject-contains-ci email "PLDI") #,(annotate-syn profile-point-t (flag email 'important)) #,(annotate-syn profile-point-f (flag email 'spam))))))
'(if (not (subject-contains-ci email "PLDI"))
((lambda () (flag email 'spam)))
((lambda () (flag email 'important))))
Note that the branches have been eta-expanded by annotate-syn. This is an implementation detail used to coerce errortrace into accepting the new profile point.