wiki:DeclareOptimize

Version 1 (modified by alms, 6 years ago) (diff)

--

CCL uses a "policy object" to control how various speed/space/safety 
tradeoffs are made; a "policy object" is just a structure-like thing 
whose slots contain functions, most of which (there are a few 
exceptions) take a lexical environment as an argument.  There are 
about a dozen such functions, and there are simple macros - 
POLICY.ALLOW-TAIL-RECURSION-ELIMINATION,  POLICY.OPEN-CODE-INLINE, 
etc - that can be used to access these functions.  There are separate 
policy objects used for interactive compilation and for COMPILE-FILE; 
all are equivalent by default,  but the idea is that people might  want 
to be more aggressive when compiling a file than when defining things 
interactively, or might not.

-Most- speed/safety/space/debugging tradeoffs are made by calling
a function in the current/active policy object with the lexical
environment as an argument; the function typically returns a boolean
result based on the values of the OPTIMIZE quantities in that
environment.  For instance, a tail-call is optimized (the caller's
stack frame is reused) if the POLICY.ALLOW-TAIL-RECURSION-ELIMINATION
function in the current policy returns true.  The value of this function 
could be something like:


  #'(lambda (env)
     (< (debug-optimize-quantity env) 2))

so tail-call optimization will happen (under a policy with this 
function) when DEBUG is less than 2.


Other functions contained in these policy objects
include

 - POLICY.OPEN-CODE-INLINE controls whether certain  fairly
   long instruction sequences are done inline or not.  One case
   of this involves special variable reference:  it takes around 8 
   instructions to determine whether a special variable has a 
   thread-local binding (and whether or not it's bound to a
   real value or unbound), and a call/return to a runtime routine
   (a "subprimitive") adds a few cycles of insult to injury.  If the
   lisp kernel profile shows a lot of time spent in _SPspecrefcheck,
   it might help to spend less time doing special variable references
   inline.  The default policy function says that things should
   be inlined when speed is greater than space;  I can't think of
   too many cases besides special variable reference where this
   matters too much on the x86-64.  (Fixnum operations that produce
   bignum results might create the bignum inline or out-of-line, but
   if this happens that often you're probably losing anyway.)

 - POLICY.INHIBIT-SAFETY-CHECKING can make correct code faster and
   incorrect code crash.  When this is true (by default, only when
   SPEED is 3 and SAFETY is 0), functions that take a fixed number
   of arguments assume that they got the correct number of arguments
   and can crash spectacularly if that's not true; if your program is 
   a lot of mostly-small, simple methods and  functions calling each
   other, then number-of-args checking is probably a small but 
   measurable source of overhead.  Other things that can happen in 
   unsafe code involve the elimination of some type and bounds-
   checking, often involving array references where the compiler has 
   some hint/clue as to the array's type.

 - POLICY.TRUST-DECLARATIONS controls whether type-declarations
   are used; it is true by default when SAFETY is less than 3 and SPEED
   is greater or equal to SAFETY.  If the compiler trusts type 
   declarations, then different things can happen depending on the 
   type in question:

   -- for some types, the declaration is "blindly" trusted: if
   X and Y are declared to be FIXNUMs and declarations are trusted,
   then arithmetic operations involving X and Y will try to do
   fixnum arithmetic without typechecking.  (Part of the rationale
   is that the typechecking in that case is likely to be more
   expensive than the arithmetic); unsafe code can also be
   generated for operations like CAR and CDR on values declared
   to be LISTs.

   -- for other types and primitive operations on them, trusted
   declarations might influence the compiler to generate type-specific
   code, but code is still safe ("trust, but verify") unless
   POLICY.INHIBIT-SAFETY-CHECKING suggests otherwise.

With the exception of cases involving FIXNUM and LIST declarations,
code compiled at default optimization settings is generally as
safe as code compiled at high safety; in practice, people don't
seem to often declare things to be FIXNUMs or LISTs unless they're
pretty sure that that's the case.  (Some of the FIXNUM/LIST stuff
is historical: it was probably once several times more expensive
to be safe than unsafe; now it is probably only 2 or 3 times as 
expensive.)

SAFETY 3 is generally implemented by forcing the compiler to be
stupidly conservative about practically everything: compiler macros
aren't expanded, function calls are (almost always) compiled as 
function calls (even if they involve primitive operations that
could be performed safely.)  I'd like to try to get away from
this implementation strategy, but it's currently the case that
SAFETY 3 sacrifices (in some cases) a lot of performance for
what's in practice only occasionally more safety than is available
under default settings.

Skipping number-of-args checks can lead to horrible hard crashes
that can be hard to debug, partly because stack discipline may
be violated.  When the callee is constant and the arguments
are explicit, mismatches can be warned about; if FUNCALL and
APPLY are used relatively rarely, then the risk of an undetected
number-of-args error is (in some sense) small.