Thursday, April 4, 2013

Esoterica

I occasionally get email about things I never thought I would have to remember.  Recently, I was asked how the LMI K-machine booted.  The short answer is this:

In the K-machine schematics, in the NuBus Spy Data Paths, there is a switch near the upper right labeled AUTOBOOT.  If it is on, the machine boots from the boot prom.  Otherwise, the machine powers up in a halted state and some other device on the bus is expected to boot it externally.

Writing is hard.  My original post went into detail about we used a serial cable from an LMI Lambda to a NuBus debug card in the test rack.  You could write the K-machine memory from the NuBus to load a bootable program, then you could single step the machine through the debug path.

(Ref:  The code for the K-machine remote debugger.)

The nitty gritty details are there in the code.

Monday, March 18, 2013

About nothing

Here's a way to quickly compute the leftmost digit of a number:

(define (leftmost-digit radix n)
  (if (> radix n)
      n
      (let ((leftmost-digit-pair (leftmost-digit (* radix radix) n)))
 (if (> radix leftmost-digit-pair)
     leftmost-digit-pair
     (quotient leftmost-digit-pair radix)))))

It's harder to compute the remaining digits.


Monday, January 14, 2013

Memory management

My college professors constantly encouraged us to "Go back to first principles."

Consider a computing task that runs for some amount of time and then halts.  If a task dynamically allocates more memory than is available, it must re-use some (or crash!)  This is irrespective of the means of re-use, whether manual deallocation as in malloc/free or automatic deallocation with a garbage collector.

The amount of allocated memory at any point in the program is the difference between the total amount allocated up to that point and the total amount deallocated up to that same point.

allocationfinal = allocationtotal - freetotal

or

freetotal = allocationtotal - allocationfinal

If we free memory in discrete amounts, then the average amount freed each time is simply the total amount freed divided by the number of times we freed memory (by definition). The average amount retained each time is easily computed by subtracting the amount freed from the memory size. This is all simple arithmetic.

In particular,

freetotal / deallocation count = freemean (by definition)

and at deallocation,

memory size - freemean = retainedmean (ditto)

The total amount of memory that a task uses might vary when the task runs at different times and different memory settings. A task could use reflection to adjust memory consumption according to resources, or the task might change resource consumption because of external circumstances such as time or memory alignment. But we expect that simple deterministic tasks will consume resources in a repeatable manner. If that is true, then the total allocation and the amount of reachable storage at any time should not depend upon the amount of memory. If you reduce the amount of memory, you'll just need to recycle it that much more to make up the difference.

For a fixed amount of freed memory, the deallocation count times freemean is constant. So for some task that frees a certain amount of memory, the product of the deallocation count and freemean will lie on a hyperbola. The product of the deallocation count and the memory size will not lie exactly on a hyperbola, but it will be pretty close, especially if the memory size is quite a bit larger than retainedmean. Again, this is simply the consequence of arithmetic. Whether we use a garbage collector to deallocate or some other memory management technique doesn't matter.

In the Economics of Garbage Collection, Singer and Jones investigate the issue of garbage collection from a microeconomics background. They introduce what they call the allocation curve of the program. A benchmark program is run with several different heap sizes and the number of garbage collections is counted for each run. The red curves in this figure show the results:
We see the expected pseudo-hyperbolas. (The blue lines are elasticity, which is not relevant here.)

Singer and Jones created these charts by empirically measuring execution of benchmark programs. But it is pointless to "measure" an arithmetic relationship. The positions of the points on the charts are deterministic located at the point where the deallocation count times the amount freed is constant. If a point does not fall on the expected line, this can only be because the total allocation or the retainedmean have changed. One of the desiderata of a good GC benchmark is having the total allocation and retainedmean be invariant across different heap sizes. The benchmarks used by Singer and Jones are not invariant across different heap sizes (or the pseudo-hyperbolas would be perfect), but the variations are generally small, so the charts are pretty close.

If we partition our allocation into "small" and "large" allocations, then we will have a pseudo-hyperbola for each case. Singer and Jones's figure 5 illustrates this:

Singer and Jones note that some benchmarks have a pronounced "knee" in the curve. In this blogpost I show how the appearance of a "knee" is an artifact of presentation, and not a property of the data.

Singer and Jones note that the upper extreme of the curve is the point at which the amount of memory a program has available is equal to what the program needs. No deallocation need be done. The extreme lower end is the point where the amount of memory given is just under the maximum amount of live storage needed. Singer and Jones note that the curve approaches an asymptote, but they do not identify the asymptote as the value of retainedmean. (Of course the curve does not reach the asymptote unless the peak memory usage is the same as the mean.)

In this post, I plot the GC count vs. the memory size for various runs of MIT Scheme. The plot is in log-log space, so the product of the GC count and memory size falls on a line rather than a hyperbola. The axes are swapped from the convention of Singer and Jones, so the asymptote is vertical rather than horizontal. A non-zero value of retainedmean displaces the hyperbola to the right and makes the upper end approach a vertical asymptote rather than intersect the axis. The blue "unadjusted" line is simply a plot of GC count * memory size. The green "adjusted" line is GC count * (memory size - retainedmean).



Monday, August 20, 2012

A little puzzle

I set my cell phone display-up on a flat surface (a table) and I turn on Google Maps.  I rotate the phone so that it "points north" (that is, the phone is on its back, the display is facing up, the top edge of the display is facing north, the left and right edges are parallel to rotation of the earth).

On Google Maps there is a little arrow-shaped icon that represents the position and orientation of the phone.  Naturally, it points at the top of the map, which is at the top of the display, and therefore the little arrow points north as well.

If I now rotate my phone slightly clockwise, what does the arrow do?

      a) move in the same direction as the phone (clockwise)
      b) move in the opposite direction as the phone (counter-clockwise)
      c) not rotate

and how fast?

    a) twice the speed as the phone rotates
    b) exactly the same speed as the phone
    c) half the speed as the phone rotates
    d) it doesn't rotate

Try to solve this in your head, then check with your phone.


Thursday, July 26, 2012

The ability to write nested loops is not a sufficient condition of employment

But it ought to be a necessary one.

Suppose you were helping me write a TicTacToe program. I have this Java code so far:
package com.vaporware.tictactoe;

import com.vaporware.common.logging.FormattingLogger;

class Board {
  Piece cells [][];

  Board() {
    this.cells = new Piece [3][3];
  }

  boolean xWins () {
    return wins(Player.X);
  }

  boolean oWins () {
    return wins(Player.O);
  }

  boolean wins (Player player) {
    // FINISH ME!
    return false;
  }
}
Assume further that there is a getOwner() method on a Piece that returns a Player.
Can we finish the wins (Player player) method?

Tuesday, June 5, 2012

Curious

(define (fmod numerator denominator)
  (- numerator
     (* (floor (/ numerator denominator))
 denominator)))

(define (test-fmod numerator denominator)
  (let ((answer (fmod numerator denominator)))
    (if (> answer denominator)
 (begin (display "Whoops: ")
        (display (list numerator denominator answer))
        (newline)))))

1 ]=> (do ((i 0 (+ i 1))) ((>= i 1000)) (test-fmod (random 1.0) (random 1.0)))

; Value: #t

1 ]=> (test-fmod .59 .01)
Whoops: (.59 .01 1.0000000000000009e-2)
;Unspecified return value


Friday, April 27, 2012

Package system horrors

Arcane Sentiment mentioned how the Lisp Machine used to handle package prefixes in this post, and it reminded me of an interesting problem.


When we were building the LMI K-machine we had to cross-compile from the existing Lisp Machine. Naturally, the cross-compiler would read the source code and intern the source code symbols as part of the process. But the K-machine had a different package setup. New packages weren't a particular problem, but the locations of some symbols were. In addition, some things that were implemented as macros in the Lisp Machine were implemented as compiler intrinsic special forms on the K-machine, and vice versa.


The compiler naturally uses symbol identity to determine how to handle a special form. If you want to compile a conditional, for example, the CAR of the form had better be the symbol CL:IF. A symbol named "IF" in a different package won't generally work.


Well, that's not exactly correct... When the Lisp Machine was built, there was no CL package. There was no Common Lisp. So when the CL package was added, it was necessary to make sure that the symbol named "IF" in the CL package was the same EQ symbol as the one the compiler used for conditionals. There are a couple of ways to arrange for this. The symbol could be explicitly interned in the CL package, or it could be visible to the CL package via inheritance.


We wanted to change some of these things on the K-machine, but the cross-compiler was running on the LMI Lambda and when it read the source code it would intern the symbols in the Lambda's packages. Munging the existing Lambda packages was out of the question. The ‘solution’ was to play some nasty tricks with the entire package system. We created two different packages, both named "COMMON-LISP". Some of the symbols were common between them, but others were not. The package inheritance was different, too. The trick was to dynamically switch how package names were resolved to package objects.


So if you have two different “package systems”, how do you refer explicitly to “the symbol IF in the other "COMMON-LISP" package? With a reader hack, of course. We made the triple colon ::: prefix indicate which package system to use to resolve the package name.


This kind of worked, but it was a horror. Most of the system didn't care, the rest didn't know, but if you accidentally had the wrong package system in place when you read a file, you would definitely trash the entire machine and need to reboot. We had a few helper functions that would try to ensure that the correct package system was selected before doing anything (for instance, when calling the cross-compiler, we'd switch out, and when it returned we'd switch back. Works great until you enter a debug repl...). The big problem was that it was impossible to ‘close’ over the correct context, so you could not in general guarantee seamless operation. (For example, if you referred to the K-machine's CL:READ symbol, you probably wanted to switch to the K-machine package set, but there was no way to do this automatically.)