racket/pkgs/racket-doc/scribblings/style/some-performance.scrbl
2016-03-09 14:36:25 -06:00

164 lines
6.3 KiB
Racket

#lang scribble/base
@(require "shared.rkt")
@; -----------------------------------------------------------------------------
@title{Language and Performance}
When you write a module, you first pick a language. In Racket you can
choose a lot of languages. The most important choice concerns @rkt/base[]
vs @rkt[].
For scripts, use @rkt/base[]. The @rkt/base[] language loads significantly
faster than the @rkt[] language because it is much smaller than the
@rkt[].
If your module is intended as a library, stick to @rkt/base[]. That way
script writers can use it without incurring the overhead of loading all of
@rkt[] unknowingly.
Conversely, you should use @rkt[] (or even @rkt/gui[]) when you just want a
convenient language to write some program. The @rkt[] language comes with
almost all the batteries, and @rkt/gui[] adds the rest of the GUI base.
@; -----------------------------------------------------------------------------
@section{Library Interfaces}
Imagine you are working on a library. You start with one module, but before
you know it the set of modules grows to a decent size. Client programs are
unlikely to use all of your library's exports and modules. If, by default,
your library includes all features, you may cause unnecessary mental stress
and run-time cost that clients do not actually use.
In building the Racket language, we have found it useful to factor
libraries into different layers so that client programs can selectively
import from these bundles. The specific Racket practice is to use the most
prominent name as the default for the module that includes everything. When
it comes to languages, this is the role of @rkt[]. A programmer who wishes
to depend on a small part of the language chooses to @rkt/base[] instead;
this name refers to the basic foundation of the language. Finally, some of
Racket's constructs are not even included in @rkt[]---consider
@racketmodname[racket/require] for example---and must be required
explicitly in programs.
Other Racket libraries choose to use the default name for the small
core. Special names then refer to the complete library.
We encourage library developers to think critically about these
decisions and decide on a practice that fits their taste and
understanding of the users of their library. We encourage developers
to use the following names for different places on the "size"
hierarchy:
@itemlist[
@item{@racket[library/kernel], the bare minimal conceievable for the
library to be usable;}
@item{@racket[library/base], a basic set of functionality.}
@item{@racket[library], an appropriate "default" of functionality
corresponding to either @racket[library/base] or @racket[library/full].}
@item{@racket[library/full], the full library functionality.}
]
Keep two considerations in mind as you decide which parts of your library
should be in which files: dependency and logical ordering. The smaller
files should depend on fewer dependencies. Try to organize the levels so
that, in principle, the larger libraries can be implemented in terms of the
public interfaces of the smaller ones.
Finally, the advice of the previous section, to use @rkt/base[] when
building a library, generalizes to other libraries: by being more
specific in your dependencies, you are a responsible citizen and
enable others to have a small (transitive) dependency set.
@; -----------------------------------------------------------------------------
@section{Macros: Space and Performance}
Macros copy code. Also, Racket is really a tower of macro-implemented
languages. Hence, a single line of source code may expand into a rather
large core expression. As you and others keep adding macros, even the
smallest functions generate huge expressions and consume a lot of space.
This kind of space consumption may affect the performance of your project
and is therefore to be avoided.
When you design your own macro with a large expansion, try to factor it
into a function call that consumes small thunks or procedures.
@compare[
@racketmod0[#:file
@tt{good}
racket
...
(define-syntax (search s)
(syntax-parse s
[(_ x (e:expr ...)
(~datum in)
b:expr)
#'(sar/λ (list e ...)
(λ (x) b))]))
(define (sar/λ l p)
(for ((a '())) ((y l))
(unless (bad? y)
(cons (p y) a))))
(define (bad? x)
... many lines ...)
...
]
@; -----------------------------------------------------------------------------
@(begin
#reader scribble/comment-reader
[racketmod0 #:file
@tt{bad}
racket
...
(define-syntax (search s)
(syntax-parse s
[(_ x (e:expr ...)
(~datum in)
b:expr)
#'(begin
(define (bad? x)
... many lines ...)
(define l
(list e ...))
(for ((a '())) ((x l))
(unless (bad? x)
(cons b a))))]))
]
)
]
As you can see, the macro on the left calls a function with a list of the
searchable values and a function that encapsulates the body. Every
expansion is a single function call. In contrast, the macro on the right
expands to many nested definitions and expressions every time it is used.
@; -----------------------------------------------------------------------------
@section{Unsafe: Beware}
Racket provides a number of unsafe operations that behave
like their related, safe variants but only when given valid inputs.
They differ in that they eschew checking for performance reasons
and thus behave unpredictably on invalid inputs.
As one example, consider @racket[fx+] and @racket[unsafe-fx+].
When @racket[fx+] is applied to a non-@racket[fixnum?], it raises
an error. In contrast, when @racket[unsafe-fx+] is applied to a non-@racket[fixnum?],
it does not raise an error. Instead it either returns a strange result
that may violate invariants of the run-time system and may cause
later operations (such as printing out the value) to crash Racket itself.
Do not use unsafe operations in your programs unless you are writing
software that builds proofs that the unsafe operations receive only
valid inputs (e.g., a type system like Typed Racket) or you are building
an abstraction that always inserts the right checks very close to
the unsafe operation (e.g., a macro like @racket[for]). And even in these
situations, avoid unsafe operations unless you have done a careful performance
analysis to be sure that the performance improvement outweighs
the risk of using the unsafe operations.