Programming

Euler 9 in Go

For fun I picked one of the Euler algorithms I had played with before and rewrote it in Go. Instead of carrying over the nested-loop search, this version first eliminates c using the fixed sum and solves the Pythagorean equation for b. Only a remains to search.

package main

import (
	"fmt"
	"os"
)

func main() {
	const sum = 1000

	for a := 1; a < sum/3; a++ {
		numerator := sum * (sum - 2*a)
		denominator := 2 * (sum - a)

		if numerator%denominator != 0 {
			continue
		}

		b := numerator / denominator
		c := sum - a - b
		if a < b && b < c {
			fmt.Println(a * b * c)
			return
		}
	}

	fmt.Fprintln(os.Stderr, "no solution")
	os.Exit(1)
}

The divisibility check rejects values of a that would produce a fractional b. The program uses constant space, examines fewer than sum / 3 candidates, and prints 31875000.

Living on Go weekly snapshots

I rebuilt Go this morning because a small program stopped compiling after I pulled the latest sources. That sounds like a complaint, but it was exactly the experiment I wanted. Go is still young enough that a weekly snapshot can change a familiar corner of the language before I have finished becoming familiar with it.

The simplest way I have found to stay sane is to keep the experiment small:

package main

import "fmt"

func main() {
	fmt.Println("hello from this week's Go")
}

I build that first, then my real code. If this fails, my tree or environment is wrong. If it works and the real program does not, I have a migration to understand rather than a mysterious compiler disaster.

The surprising part is how useful the release number becomes. A report that says “tip is broken” has a shelf life measured in minutes. A report that includes the release.r57 family, the compiler, the operating system, and a reduced program gives everybody something concrete to compare. The weekly tags are not decoration; they are coordinates on moving ground.

There is a mechanical reason for the churn. The language, compilers, runtime, and libraries live together and are being designed together. A syntax adjustment can therefore arrive beside the library changes needed to use it. That is convenient for the project and occasionally inconvenient for the person who pulled five minutes before lunch.

I prefer snapshots to copying a random revision from the main repository. A named snapshot gives me a point I can return to and makes two machines easier to compare. I also keep Go code outside the source tree and rebuild from a clean checkout when results become implausible.

That record helps with library changes too. When a package function moves or changes shape, I can read the notes between two known points instead of searching the entire repository history. Small coordinates turn a broad question into a short diff.

The caveat is obvious: weekly does not mean stable. I would not quietly update a production compiler and hope for character-building surprises. For experiments, however, the snapshots make the cost of change visible while the changes are still small. My program broke, I learned why, and lunch was only slightly delayed.

Reflection and a Tiny Config Decoder

My programs often begin with three configuration fields and end with a parser that believes it is a database. I tried using reflection to make one small key-value decoder reusable without building another private language.

The input is deliberately boring:

host=buildbox
workers=4
verbose=true

The destination is a structure. The decoder walks exported fields with the early reflect package, matches lowercased field names, converts the text according to each field’s kind, and sets the value. The exact calls have changed across weekly snapshots, so the important part is the control flow rather than names copied from my May 2011 tree:

parse lines
  -> find structure field
  -> inspect field kind
  -> convert text
  -> set addressable field or return os.Error

Passing a pointer is required because the decoder must modify the original structure. It checks that the argument points to a structure before examining fields. A non-pointer, unknown key, unsupported kind, or failed conversion returns an ordinary failure value with the key name.

I initially ignored unknown keys for forward compatibility. That converted a misspelled workres setting into a silent default of zero workers. Strict input is friendlier here: configuration is written by a person who would rather receive a precise complaint now than investigate strange behavior later.

The decoder supports strings, booleans, and signed integers. That is all this program needs. Slices, nested structures, aliases, default tags, and custom conversion hooks can wait until an actual configuration requires them. Reflection makes adding features easy in the same way an empty notebook makes writing a trilogy easy.

There is runtime cost, but startup configuration is not a hot path. The larger cost is weaker static checking inside the decoder, which I address with table tests over valid and invalid input. The application still uses an ordinary typed structure after startup.

I would not use this mechanism for a protocol or large data stream. Gob already solves typed Go-to-Go encoding, and explicit parsing is clearer for a public format. For a dozen local settings, a constrained reflective decoder removes repetitive assignment while keeping failures visible. The constraint is the feature, not an embarrassing first release.

Panic Is Not an os.Error

Go has both ordinary failure values and panic, which guarantees that programmers will eventually use one where the other belongs. I started by treating panic like an exception mechanism for file failures. That made simple callers mysterious and was the wrong model.

An operation expected to fail should return an os.Error in this snapshot:

func load(name string) ([]byte, os.Error) {
    f, err := os.Open(name, os.O_RDONLY, 0)
    if err != nil {
        return nil, err
    }
    defer f.Close()
    return readAll(f)
}

Missing files, refused connections, and malformed input are normal outcomes at a system boundary. The caller needs to decide what to do, so the value belongs in the function’s contract.

Panic is better reserved for a broken invariant or a condition from which the current operation cannot sensibly continue. During panic, normal execution stops and deferred calls run while the stack unwinds. If nothing recovers, the program terminates with diagnostic information.

recover can intercept that process, but only when called from a deferred function during unwinding. Calling it during ordinary execution does nothing useful. My worker boundary uses the pattern to prevent one internal invariant failure from taking down the entire test harness:

func runJob(job Job, result chan<- Result) {
    defer func() {
        if value := recover(); value != nil {
            result <- failedResult(value)
        }
    }()
    result <- execute(job)
}

This boundary records the failure and marks that job bad. It does not silently resume halfway through execute; its state may be inconsistent. Recovery belongs at a boundary that can abandon the failed unit of work.

I avoid broad recovery in library functions. Converting every panic into os.Error can hide programmer defects, and recovering without reporting the value destroys the most useful evidence. A panic due to an indexing mistake is not suddenly a network timeout because both traveled through one handler.

These names and details reflect the April 2011 toolchain. Panic and recovery semantics have changed during Go’s development, so old examples need their snapshot attached. The design rule is stable enough for me: return ordinary failures, panic on violated assumptions sparingly, and recover only where an entire operation can be discarded cleanly. Anything broader starts to resemble sweeping broken glass under a very concurrent rug.

Cache Lines Beat Clever Loops

I translated a matrix filter from C to Go and then spent an afternoon optimizing arithmetic that was not the main cost. The profile pointed at memory movement, so I reduced the problem to traversal order.

The matrix is stored in one slice in row-major order. These loops calculate the same sum:

for y := 0; y < height; y++ {
    for x := 0; x < width; x++ {
        sum += pixels[y*width+x]
    }
}
for x := 0; x < width; x++ {
    for y := 0; y < height; y++ {
        sum += pixels[y*width+x]
    }
}

The first walks adjacent elements. The second jumps by an entire row. On a large image the row-wise loop is much faster because each fetched cache line supplies several values used immediately. The column-wise loop asks the processor to fetch many lines and uses a small part of each before moving on.

Bounds checks and multiplication were my original suspects. Hoisting the row offset made the code clearer and helped a little:

for y := 0; y < height; y++ {
    row := pixels[y*width:(y+1)*width]
    for x := 0; x < width; x++ {
        sum += row[x]
    }
}

But traversal order remained the large effect. A more sophisticated arithmetic trick could not compensate for unfriendly access.

I benchmark with enough data to exceed the small caches, repeat the operation, and verify the sum so the compiler cannot discard the work. These are 2011 compiler snapshots, so absolute timings and optimization behavior are temporary facts. The memory hierarchy is less temporary.

I alternate test order as well. Running one version first every time can give it consistently colder data and turn benchmark order into a hidden input.

This also affects data structure choice. A slice of small structures may have better locality than a slice of pointers to separately allocated structures. The latter adds pointer chasing and gives the allocator freedom to scatter objects. It may still be right when identity and independent lifetimes matter, but it is not free.

My practical conclusion is embarrassingly physical: visit data in the order it lies in memory. Before unrolling loops or translating a bit trick from C, inspect the access pattern. The processor is very fast when fed and becomes an expensive space heater when asked to wait.