Zero 「Stay hungry. Stay foolish.」

Go 学习笔记


Composite Types

  1. The zero value of a slice type is nil. If you need to test whether a slice is empty, use len(s) == 0, not s == nil.

    s = []int{} // len(s) == 0, s ! nil
  2. Since we know the final size of names from the outset, it is more efficient to allocate an array of the required size up front.

  3. Most operations on maps, including lookup, delete, len, and range loops, are safe to per- form on a nil map reference, since it behaves like an empty map. But storing to a nil map causes a panic:

    ages["carol"] = 21 // panic: assignment to entry in nil ma
  4. if age, ok := ages[“bob”]; !ok { /_ … _/ }
  5. Go does not provide a set type, but since the keys of a map are distinct, a map can serve this purpose.
  6. Field order is significant to type identity. Interchanged Name and Address, we would be defining a different struct type

    type Employee struct { ID int Name, Address string}
  7. If a field is omitted in this kind of literal, it is set to the zero value for its type.

    anim := gif.GIF{LoopCount: nframes}
  8. pp := &Point{1, 2}`
  9. If all the fields of a struct are comparable, the struct itself is comparable, so two expressions of that type may be compared using == or !=
  10. Comparable struct types, like other comparable types, may be used as the key type of a map.
  11. Unfortunately, there’s no corresponding shorthand for the struct literal syntax, so neither of these will compile:

    type Circle struct { Point Radius int }

    w = Wheel{8, 8, 5, 20} // compile error: unknown fields

    w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} // compile error: unknown fields

    w = Wheel{Circle{Point{8, 8}, 5}, 20}

  12. Because the name of the field is implic- itly determined by its type, so too is the visibility of the field
  13. Composition is central to object-oriented programming in Go.
  14. JSON, XML, GOOGLE Protocol Buffer, asn.1
    encoding/json, encoding/asn1, ecoding/xml
    data, err := json.Marshal(movies)
    data, err := json.MarshalIndent(movies)
  1. Marshaling uses the Go struct field names as the field names for the JSON objects (through reflection). Only exported fields are marshaled, which is why we chose capitalized names for all the Go field names.


  1. The type of a function is sometimes called its signature. Two functions have the same type or signature if they have the same sequence of parameter types and the same sequence of result types. The names of parameters and results don’t affect the type, nor does whether or not they were declared using the factored form.

  2. Go has no concept of default parameter values, nor any way to specify arguments by name

  3. Arguments are passed by value, so the function receives a copy of each argument; modifica- tions to the copy do not affect the caller. However, if the argument contains some kind of ref- erence, like a pointer, slice, map, function, or channel, then the caller may be affected by any modifications the function makes to variables indirectly referred to by the argument.

  4. Many programming language implementations use a fixed-size function call stack; sizes from 64KB to 2MB are typical. Fixed-size stacks impose a limit on the depth of recursion, so one must be careful to avoid a stack overflow when traversing large data structures recursively; fixed-size stacks may even pose a security risk. In contrast, typical Go implementations use variable-size stacks that start small and grow as needed up to a limit on the order of a gigabyte. This lets us use recursion safely and without worrying about overflow.

  5. We must ensure that resp.Body is closed so that network resources are properly released even in case of error.
  6. Go’s garbage collector recycles unused memory, but do not assume it will release unused operating system resources like open files and network connections. They should be closed explicitly.
  7. bare returns are best used sparingly.
  8. an error may be nil or non-nil, that nil implies success and non-nil implies failure
  9. A more convenient way to achieve the same effect is to call log.Fatalf. As with all the log functions, by default it prefixes the time and date to the error message.
  10. The call to os.RemoveAll may fail, but the program ignores it because the operating system periodically cleans out the temporary directory.

  11. strings.Map applies a function to each character of a string, joining the results to make another string. 12.When an anonymous function requires recursion, as in this example, we must first declare a variable, and then assign the anonymous function to that variable

  12. All function values created by this loop ‘‘capture’’ and share the same variable—an addressable storage location, not its value at that particular moment. The value of dir is updated in suc- cessive iterations, so by the time the cleanup functions are called, the dir variable has been updated several times by the now-completed for loop.

    for _, dir := range tempDirs() {
        os.MkdirAll(dir, 0755)
        rmdirs = append(rmdirs, func() {
            os.RemoveAll(dir) // NOTE: incorrect!
  13. Any number of calls may be deferred; they are executed in the reverse of the order in which they were deferred.
  14. func trace() func() {
        start := time.Now()
        fmt.Println("enter bigSlowOperation")
        return func() {
    	    fmt.Println("exit bigSlowOperation")
    func bigSlowOperation() {
        defer trace()()
        time.Sleep(10 * time.Second)
  15. One solution is to move the loop body, including the defer statement, into another function that is called on each iteration.

for _, filename := range filenames {
    f, err := os.Open(filename)
    if err != nil {
        return err
    defer f.Close() // NOTE: risky; could run out of file descriptors
// ...process f...


for _, filename := range filenames {
    if err := doFile(filename); err != nil {
        return err

func doFile(filename string) error {
    f, err := os.Open(filename)
    if err != nil {
        return err
    defer f.Close()
    // ...process f...
  1. During a typical panic, normal execution stops, all deferred function calls in that goroutine are executed, and the program crashes with a log message.
  2. Go’s panic mechanism runs the deferred functions before it unwinds the stack.
  3. If the built-in recover function is called within a deferred function and the function contain- ing the defer statement is panicking, recover ends the current state of panic and returns the panic value

func Parse(input string) (s *Syntax, err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("internal error: %v", p)
// ...parser...
  1. Recovering indiscriminately from panics is a dubious practice because the state of a package’s variables after a panic is rarely well defined or documented


  1. Since the receiver name will be frequently used, it’s a good idea to choose something short and to be consistent across methods. A com- mon choice is the first letter of the type name, like p for Point.
  2. The type of an anonymous field may be a pointer to a named type, in which case fields and methods are promoted indirectly from the pointed-to object. Adding another level of indi- rection lets us share common structures and vary the relationships between objects dynami- cally

    type ColoredPoint struct {
        Color color.RGBA
    p := ColoredPoint{&Point{1, 1}, red}
    q := ColoredPoint{&Point{5, 4}, blue}
    fmt.Println(p.Distance(*q.Point)) // "5"
    q.Point = p.Point                 // p and q now share he same Point
    fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
  1. thanks to embedding, it’s possible and sometimes useful for unnamed struct types to have methods too.

     var cache = struct {
         mapping map[string]string }{
         mapping: make(map[string]string),
     func Lookup(key string) string {
         v := cache.mapping[key]
         return v
  1. Method values are useful when a package’s API calls for a function value, and the client’s desired behavior for that function is to call a method on a specific receiver.

     type Rocket struct { /* ... */ }
     func (r *Rocket) Launch() { /* ... */ }
     r := new(Rocket)
     time.AfterFunc(10 * time.Second, func() { r.Launch() })

     time.AfterFunc(10 * time.Second, r.Launch)
  1. Another consequence of this name-based mechanism is that the unit of encapsulation is the package, not the type as in many other languages. The fields of a struct type are visible to all code within the same package. Whether the code appears in a function or a method makes no difference.

     type Buffer struct {
         buf     []byte
         initial [64]byte
         /* ... */
     // Grow expands the buffer's capacity, if necessary,
     // to guarantee space for another n bytes. [...]
     func (b *Buffer) Grow(n int) {
         if b.buf == nil {
             b.buf = b.initial[:0] // use preallocated space initially
         if len(b.buf)+n > cap(b.buf) {
             buf := make([]byte, b.Len(), 2*cap(b.buf) + n)
             copy(buf, b.buf)
             b.buf = buf

  1. Encapsulation is not always desirable. By revealing its representation as an int64 number of nanoseconds, time.Duration lets us use all the usual arithmetic and comparison operations with durations, and even to define constants of this type:
   const day = 24 * time.Hour
   fmt.Println(day.Seconds()) // "86400"


  1. Many object-oriented languages have some notion of interfaces, but what makes Go’s inter- faces so distinctive is that they are satisfied implicitly. In other words, there’s no need to declare all the interfaces that a given concrete type satisfies; simply possessing the necessary methods is enough. This design lets you create new interfaces that are satisfied by existing concrete types without changing the existing types, which is particularly useful for types defined in packages that you don’t control.

  2. By now you’ve probably noticed the naming convention for many of Go’s single-method interfaces
  3. A type satisfies an interface if it possesses all the methods the interface rPequires.
  4. The assignability rule for interfaces is very simple: an expression may be assigned to an interface only if its type satisfies the interface
  5. in fact the type interface{}, which is called the empty interface type, is indispensable. Because the empty interface type places no demands on the types that satisfy it, we can assign any value to the empty interface.
var any interface{}
any = true
any = 12.34
any = "hello"
any = map[string]int{"one": 1}
any = new(bytes.Buffer)
  1. Conceptually, a value of an interface type, or interface value, has two components, a concrete type and a value of that type. These are called the interface’s dynamic type and dynamic value.
  2. For a statically typed language like Go, types are a compile-time concept, so a type is not a value.
  3. fmt uses reflection to obtain the name of the interface’s dynamic type.fmt uses reflection to obtain the name of the interface’s dynamic type.

  4. Conceptually, a value of an interface type, or interface value, has two components, a concrete type and a value of that type. These are called the interface’s dynamic type and dynamic value.
    const debug = false
     func main() {
         var buf *bytes.Buffer
         if debug {
             buf = new(bytes.Buffer) // enable collection of output
         f(buf) // NOTE: subtly incorrect!
         if debug {
             // ...use buf...
    // If out is non-nil, output will be written to it. func f(out io.Writer)
    func f(out io.Writer) { // 接口也是有值,包括两部分,type和value,传nil指针会复制type值而value保持nil,但此时接口值不为空
         // something...
         if out != nil {

  1. Sorting with sort.Interface
package sort
type Interface interface {
    Len() int
    Less(i, j int) bool // i, j are indices of sequence elements
    Swap(i, j int)

type StringSlice []string
func(p StringSlice)

  1. Although sorting a sequence of length n requires O(n log n) comparison operations, testing whether a sequence is already sorted requires at most n−1 comparisons.


     switch x := x.(type) { /* ... */ }

  1. Interfaces are only needed when there are two or more concrete types that must be dealt with in a uniform way.

Go routines and channels

  1. Go enables two styles of concurrent programming. communicating sequential processes or CSP, a model of concurrency in which values are passed between independent activities (goroutines) but variables are for the most part confined to a single activity.
  2. In Go, each concurrently executing activity is called a goroutine
  3. The differences between threads and goroutines are essentially quantitative, not qualitative
  4. To create a channel, we use the built-in make function:
ch := make(chan int) // ch has type 'chan int'
  1. A channel created with a simple call to make is called an unbuffered channel, but make accepts an optional second argument, an integer called the channel’s capacity. If the capacity is non- zero, make creates a buffered channel
  2. Communication over an unbuffered channel causes the sending and receiving goroutines to synchronize. Because of this, unbuffered channels are sometimes called synchronous channels. When a value is sent on an unbuffered channel, the receipt of the value happens before the reawakening of the sending goroutine.
  3. In discussions of concurrency, when we say x happens before y, we don’t mean merely that x occurs earlier in time than y; we mean that it is guaranteed to do so and that all its prior effects, such as updates to variables, are complete and that you may rely on them.
  4. When x neither happens before y nor after y, we say that x is concurrent with y. This doesn’t mean that x and y are necessarily simultaneous, merely that we cannot assume anything about their ordering.
  5. After a channel has been closed, any further send operations on it will panic. After the closed channel has been drained, that is, after the last sent element has been received, all subsequent receive operations will proceed without blocking but will yield a zero value. Closing the natu- rals channel above would cause the squarer’s loop to spin as it receives a never-ending stream of zero values, and to send these zeros to the printer.
  6. There is no way to test directly whether a channel has been closed, but there is a variant of the receive operation that produces two results: the received channel element, plus a boolean value, conventionally called ok, which is true for a successful receive and false for a receive on a closed and drained channel. Using this feature, we can modify the squarer’s loop to stop when the naturals channel is drained and close the squares channel in turn.
  7. Because the syntax above is clumsy and this pattern is common, the language lets us use a range loop to iterate over channels too. This is a more convenient syntax for receiving all the values sent on a channel and terminating the loop after the last one.
  8. Attempting to close an already-closed channel causes a panic, as does closing a nil channel.
  9. When a channel is supplied as a function parameter, it is nearly always with the intent that it be used exclusively for sending or exclusively for receiving. To document this intent and prevent misuse, the Go type system provides unidirectional chan- nel types that expose only one or the other of the send and receive operations. The type chan<- int, a send-only channel of int, allows sends but not receives. Conversely, the type <-chan int, a receive-only channel of int, allows receives but not sends. (The position of the <- arrow relative to the chan keyword is a mnemonic.) Violations of this discipline are detected at compile time.
  10. A buffered channel has a queue of elements. The queue’s maximum size is determined when it is created, by the capacity argument to make.
  11. A send operation on a buffered channel inserts an element at the back of the queue, and a receive operation removes an element from the front. If the channel is full, the send operation blocks its goroutine until space is made available by another goroutine’s receive. Conversely, if the channel is empty, a receive operation blocks until a value is sent by another goroutine.
  12. Channels are deeply connected to goroutine scheduling, and without another goroutine receiving from the channel, a sender—and perhaps the whole program—risks becoming blocked forever. If all you need is a simple queue, make one using a slice.
  13. Had we used an unbuffered channel, the two slower goroutines would have gotten stuck trying to send their responses on a channel from which no goroutine will ever receive. This sit- uation, called a goroutine leak, would be a bug. Unlike garbage variables, leaked goroutines are not automatically collected, so it is important to make sure that goroutines terminate them- selves when no longer needed.

func mirroredQuery() string {
responses := make(chan string, 3)
go func() { responses <- request("") }()
go func() { responses <- request("") }() go func() { responses <- request("") }() return <-responses // return the quickest response
     func request(hostname string) (response string) { /* ... */ }

  1. Failure to allocate sufficient buffer capacity would cause the program to deadlock.
  2. It starts all the goroutines, one per file name, but doesn’t wait for them to finish.
  3. the single variable f is shared by all the anonymous function values and updated by successive loop iterations. By the time the new goroutines start executing the lit- eral function, the for loop may have updated f and started another iteration or (more likely) finished entirely, so when these goroutines read the value of f, they all observe it to have the value of the final element of the slice.

     for _, f := range filenames {
         go func() {
    thumbnail.ImageFile(f) // NOTE: incorrect!
// ... }()
  1. This function has a subtle bug. When it encounters the first non-nil error, it returns the error to the caller, leaving no goroutine draining the errors channel. Each remaining worker goroutine will block forever when it tries to send a value on that channel, and will never terminate. This situation, a goroutine leak (§8.4.4), may cause the whole program to get stuck or to run out of memory.

     // makeThumbnails4 makes thumbnails for the specified files in parallel.
     // It returns an error if any step failed.
     func makeThumbnails4(filenames []string) error {
         errors := make(chan error)
         for _, f := range filenames {
             go func(f string) {
        _, err := thumbnail.ImageFile(f)
        errors <- err }(f)
        for range filenames {
            if err := <-errors; err != nil {
                return err // NOTE: incorrect: goroutine leak!
        return nil
  1. This demands a special kind of counter, one that can be safely manipulated from multiple goroutines and that provides a way to wait until it becomes zero. This counter type is known as sync.WaitGroup, and the code below shows how to use it:
// makeThumbnails6 makes thumbnails for each file received from the channel. // It returns the number of bytes occupied by the files it creates.
func makeThumbnails6(filenames <-chan string) int64 {
    sizes := make(chan int64)
    var wg sync.WaitGroup // number of working goroutines
    for f := range filenames {
// worker
go func(f string) {
    defer wg.Done()
    thumb, err := thumbnail.ImageFile(f)
    if err != nil {
    info, _ := os.Stat(thumb) // OK to ignore error
sizes }(f)
<- info.Size()
// closer
go func() {
var total int64
for size := range sizes {
total += size
return total
  1. Unbounded parallelism is rarely a good idea since there is always a limiting factor in the system, such as the number of CPU cores for compute-bound workloads, the number of spindles and heads for local disk I/O operations, the bandwidth of the network for streaming downloads, or the serving capacity of a web service
  2. We can limit parallelism using a buffered channel of capacity n to model a concurrency primi- tive called a counting semaphore.

     // tokens is a counting semaphore used to
     // enforce a limit of 20 concurrent requests.
var tokens = make(chan struct{}, 20)
func crawl(url string) []string {
    tokens <- struct{}{} // acquire a token
    list, err := links.Extract(url)
    <-tokens // release the token
    if err != nil {
return list
  1. The time.Tick function returns a channel on which it sends events periodically, acting like a metronome.
  2. A select waits until a communication for some case is ready to proceed. It then performs that communication and executes the case’s associated statements; the other communications do not happen. A select with no cases, select{}, waits forever.
  3. The time.After function immediately returns a channel, and starts a new goroutine that sends a single value on that channel after the speci- fied time. goroutine 泄露风险
  4. If multiple cases are ready, select picks one at random, which ensures that every channel has an equal chance of being selected.
  5. The time.Tick function behaves as if it creates a goroutine that calls time.Sleep in a loop, When the countdown function above returns, it stops receiving events from tick, but the ticker goroutine is still there, trying in vain to send on a channel from which no goroutine is receiving—a goroutine leak
  6. The Tick function is convenient, but it’s appropriate only when the ticks will be needed throughout the lifetime of the application,Otherwise, we should use this pattern:
ticker := time.NewTicker(1 * time.Second)
<-ticker.C // receive from the ticker's channel
ticker.Stop() // cause the ticker's goroutine to terminate
  1. Sometimes we want to try to send or receive on a channel but avoid blocking if the channel is not ready—a non-blocking communication. A select statement can do that too. A select may have a default, which specifies what to do when none of the other communications can proceed immediately. The select statement below receives a value from the abort channel if there is one to receive; otherwise it does nothing. This is a non-blocking receive operation; doing it repeatedly is called polling a channel
select {
case <-abort:
         fmt.Printf("Launch aborted!\n")
// do nothing
  1. The zero value for a channel is nil. Perhaps surprisingly, nil channels are sometimes useful. Because send and receive operations on a nil channel block forever, a case in a select statement whose channel is nil is never selected. This lets us use nil to enable or disable cases that cor- respond to features like handling timeouts or cancellation, responding to other input events, or emitting output
  2. There is no way for one goroutine to terminate another directly, since that would leave all its shared variables in undefined states.
  3. Moreover, when a goroutine receives a value from the abort channel, it consumes that value so that other goroutines won’t see it. For cancellation, what we need is a reliable mechanism to broadcast an event over a channel so that many goroutines can see it as it occurs and can later see that it has occurred.
  4. Recall that after a channel has been closed and drained of all sent values, subsequent receive operations proceed immediately, yielding zero values. We can exploit this to create a broad- cast mechanism: don’t send a value on the channel, close it.

     var done = make(chan struct{})
     func cancelled() bool {
         select {
case <-done: return true
             return false
} }
  1. Of course, when main returns, a program exits, so it can be hard to tell a main function that cleans up after itself from one that does not. There’s a handy trick we can use during testing: if instead of returning from main in the event of cancellation, we execute a call to panic, then the runtime will dump the stack of every goroutine in the program. If the main goroutine is the only one left, then it has cleaned up after itself. But if other goroutines remain, they may not have been properly cancelled, or perhaps they have been cancelled but the cancellation takes time; a little investigation may be worthwhile. The panic dump often contains sufficient information to distinguish these cases.

Concurrency with shared variables

  1. A data race occurs whenever two goroutines access the same variable concurrently and at least one of the accesses is a write. It follows from this definition that there are three ways to avoid a data race. The first way is not to write the variable. The second way to avoid a data race is to avoid accessing the variable from multiple goroutines. The third way to avoid a data race is to allow many goroutines to access the variable, but only one at a time.
  2. A semaphore that counts only to 1 is called a binary semaphore.

var (
    sema = make(chan struct{}, 1) // a binary semaphore guarding balance
    balance int

func Deposit(amount int) {
    sema <- struct{}{} // acquire token
    balance = balance + amount
    <-sema // release token
func Balance() int {
    sema <- struct{}{} // acquire token
    b := balance
    <-sema // release token
return b
  1. This pattern of mutual exclusion is so useful that it is supported directly by the Mutex type from the sync package.

import "sync"
var (
    mu      sync.Mutex // guards balance
    balance int
func Deposit(amount int) {
    balance = balance + amount
func Balance() int {
    b := balance
    return b
  1. Furthermore, a deferred Unlock will run even if the critical section panics, which may be important in programs that make use of recover
  2. because mutex locks are not re-entrant—it’s not possible to lock a mutex that’s already locked—this leads to a deadlock where nothing can proceed, and Withdraw blocks forever.
// NOTE: incorrect!
func Withdraw(amount int) bool {
    defer mu.Unlock()
    if Balance() < 0 {
        return false // insufficient funds
    return true
  1. A common solution is to divide a function such as Deposit into two: an unexported function, deposit, that assumes the lock is already held and does the real work, and an exported func- tionDepositthatacquiresthelockbeforecallingdeposit
  2. we need a special kind of lock that allows read-only operations to proceed in parallel with each other, but write operations to have fully exclusive access. This lock is called a multiple readers, single writer lock, and in Go it’s provided by sync.RWMutex
var mu sync.RWMutex
var balance int
func Balance() int {
    mu.RLock() // readers lock
    defer mu.RUnlock()
    return balance
  1. RLock can be used only if there are no writes to shared variables in the critical section.
  2. Synchronization primitives like channel communications and mutex operations cause the processor to flush out and commit all its accumulated writes so that the effects of goroutine execution up to that point are guaranteed to be visible to goroutines running on other processors.
  3. But in the absence of explicit synchronization using a channel or mutex, there is no guarantee that events are seen in the same order by all goroutines. Although goroutine A must observe the effect of the write x = 1 before it reads the value of y, it does not necessarily observe the write to y done by goroutine B, so A may print a stale value of y.
  4. It is good practice to defer an expensive initialization step until the moment it is needed. Ini- tializing a variable up front increases the start-up latency of a program and is unnecessary if execution doesn’t always reach the part of the program that uses that variable.
func loadIcons() {
    icons = map[string]image.Image{
        "spades.png":   loadIcon("spades.png"),
        "hearts.png":   loadIcon("hearts.png"),
        "diamonds.png": loadIcon("diamonds.png"),
        "clubs.png":    loadIcon("clubs.png"),
} }
// NOTE: not concurrency-safe!
func Icon(name string) image.Image {
    if icons == nil {
        loadIcons() // one-time initialization
    return icons[name]

The simplest correct way to ensure that all goroutines observe the effects of loadIcons is to synchronize them using a mutex. However, the cost of enforcing mutually exclusive access to icons is that two goroutines can- not access the variable concurrently, even once the variable has been safely initialized and will never be modified again. This suggests a multiple-readers lock.

var mu sync.RWMutex // guards icons
     var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
    if icons != nil {
        icon := icons[name]
        return icon
    // acquire an exclusive lock
    if icons == nil { // NOTE: must recheck for nil
    icon := icons[name]
    return icon

The pattern above gives us greater concurrency but is complex and thus error-prone. Fortunately, the sync package provides a specialized solution to the problem of one-time ini- tialization: sync.Once. Conceptually, a Once consists of a mutex and a boolean variable that records whether initialization has taken place; the mutex guards both the boolean and the client’s data structures. The sole method, Do, accepts the initialization function as its argu- ment. Let’suseOncetosimplifytheIconfunction:

var loadIconsOnce sync.Once
var icons map[string]image.Image
// Concurrency-safe.
func Icon(name string) image.Image {
    return icons[name]
  1. Even with the greatest of care, it’s all too easy to make concurrency mistakes. Fortunately, the Go runtime and toolchain are equipped with a sophisticated and easy-to-use dynamic analysis tool, the race detector.
  2. Just add the-race flag to your go build,go run,orgo testcommand. This causes the compiler to build a modified version of your application or test with additional instrumentation that effectively records all accesses to shared variables that occurred during execution, along with the identity of the goroutine that read or wrote the variable. In addition, the modified program records all synchronization events, such as go statements, channel operations, and calls to (sync.Mutex).Lock, (sync.WaitGroup).Wait, and so on. (The complete set of synchronization events is specified by the The Go Memory Model document that accompanies the language specification.) . However, it can only detect race conditions that occur during a run; it cannot prove that none will ever occur. For best results, make sure that your tests exercise your packages using concurrency. Due to extra bookkeeping, a program built with race detection needs more time and memory to run, but the overhead is tolerable even for many production jobs.