Dave Cheney

History, Economics and Technology

How the Go language improves expressiveness without sacrificing runtime performance

  • Edit
  • Delete
  • Tags
  • Autopost

This week there was a discussion on the golang-nuts mailing list about an idiomatic way to update a slice of structs. For example, consider this struct representing a set of counters.

type E struct {
        A, B, C, D int
}

var e = make([]E, 1000)

Updating these counters may take the form

for i := range e {
        e[i].A += 1
        e[i].B += 2
        e[i].C += 3
        e[i].D += 4
}

Which is good idiomatic Go code. It's pretty fast too

BenchmarkManual   500000              4642 ns/op

However there is a problem with this example. Each access the ith element of e requires the compiler to insert an array bounds checks. You can avoid 3 of these checks by referencing the ith element once per iteration.

for i := range e {
        v := &e[i]
        v.A += 1
        v.B += 2
        v.C += 3
        v.D += 4
}

By reducing the number of subscript checks, the code now runs considerably faster.

BenchmarkUnroll  1000000              2824 ns/op

If you are coding a tight loop, clearly this is the more efficient method, but it comes with a cost to readability, as well as a few gotchas. Someone else reading the code might be tempted to move the creation of v into the for declaration, or wonder why the address of e[i] is being taken. Both of these changes would cause an incorrect result. Obviously tests are there to catch this sort of thing, but I propose there is a better way to write this code, one that doesn't sacrifice performance, and expresses the intent of the author more clearly.

func (e *E) update(a, b, c, d int) {
        e.A += a
        e.B += b
        e.C += c
        e.D += d
}

for i := range e {
        e[i].update(1, 2, 3, 4)
}

Because E is a named type, we can create an update() method on it. Because update is declared on with a receiver of *E, the compiler automatically inserts the (&e[i]).update() for us. Most importantly because of the simplicity of the update() method itself, the inliner can roll it up into the body of the calling loop, negating the method call cost. The result is very similar to the hand unrolled version.

BenchmarkUpdate   500000              2996 ns/op

In conclusion, as Cliff Click observed, there are Lies, Damn Lies and Microbenchmarks. This post is an example of a least one of the three. My intent in writing was not to spark a language war about who can update a struct the fastest, but instead argue that Go lets you write your code in a more expressive manner without having to trade off performance.

You can find the source code for the benchmarks presented in this post on Github,

Filed under  //

  • go
  • golang

Introducing gmx, runtime instrumentation for Go applications

  • Edit
  • Delete
  • Tags
  • Autopost

What is gmx ?

gmx is an experimental package for instrumenting Go applications. gmx is similar to Java's jmx and provides a simple method of querying the internal state of your Go application by invoking anonymous functions bound to published keys. Here is an example using the included client, gmxc.

% ./gmxc -p 16378 runtime.version runtime.numcpu os.args 
os.args: [./godoc -v -http=:8080]
runtime.numcpu: 4
runtime.version: weekly.2012-01-27 11688+

How can I use gmx in my applications ?

In the example above, a stock godoc was instrumented by importing the github.com/davecheney/gmx package into main.

package main

import _ "github.com/davecheney/gmx"

The runtime and os instruments are provided by default by gmx.

How can I export my own values via gmx ?

You can publish gmx instruments at any point in your application. The most logical place is inside your package's init function.

package foo

import "github.com/davecheney/gmx"

var c1 = 1
var c2 = 2

func init() {
        // publish c1 via a closure
        gmx.Publish("c1", func() interface{} {
                return c1
        })
        // publish c2 via a function
        gmx.Publish("c2", getC2)
}

func getC2() interface{} {
        return c2
}

Using gmxc the values can be queried at runtime:

./gmxc -p 16467 c1 c2
c2: 2
c1: 1

Who is gmx aimed at ?

If you are developing an application in Go, especially a daemon or some other long running process, then gmx may appeal to you. If you live on the other side of the DevOps table, gmx will hopfully allow you to gain a greater understanding of the internal workings of applications you have been charged with caring for.

If you find gmx useful for you, please let me know. I plan to continue to develop the default instrumentation bundled with gmx and am always open to pull requests for additional functionality.

You can find the source to gmx on github, https://github.com/davecheney/gmx.

 

Filed under  //

  • gmx
  • golang

Why Go gets exceptions right

  • Edit
  • Delete
  • Tags
  • Autopost

How does Go get exceptions right? Why, by not having them in the first place.

First, a little history.

Before my time, there was C, and errors were your problem. This was generally okay, because if you owned an 70's vintage mini computer, you probably had your share of problems. Because C was a single return language, things got a bit complicated when you wanted to know the result of a function that could sometimes go wrong. IO is a perfect example of this, or sockets, but there are also more pernicious cases like converting a string to it's integer value. A few idioms grew to handle this problem. For example, if you had a function that would mess around with the contents of a struct, you could pass a pointer to it, and the return code would indicate if the fiddling was successful. There are other idioms, but I'm not a C programmer, and that isn't the point of this article.

Next came C++, which looked at the error situation and tried to improve it. If you had a function which would do some work, it could return a value or it could throw an exception, which you were then responsible for catching and handling. Bam! Now C++ programmers can signal errors without having to conflate their single return value. Even better, exceptions can be handled anywhere in the call stack. If you don't know how to handle that exception it'll bubble up to someone who does. All the nastyness with errno and threads is solved. Achievement unlocked!

Sorta.

The downside of C++ exceptions is you can't tell (without the source and the impetus to check) if any function you call may throw an exception. In addition to worrying about resource leaks and destructors, you have to worry about RAII and transactional semantics to ensure your methods are exception safe in case they are somewhere on the call stack when an exception is throw. In solving one problem, C++ created another.

So the designers of Java sat down, stroked their beards and decided that the problem was not exceptions themselves, but the fact that they could be thrown without notice; hence Java has checked exceptions. You can't throw an exception inside a method without annotating that method's signature to indicate you may do so, and you can't call a method that may throw an exception without wrapping it in code to handle the potential exception. Via the magic of compile time bondage and discipline the error problem is solved, right?

This is about the time I enter the story, the early millennium, circa Java 1.4. I agreed then, as I do now, that the Java way of checked exceptions was more civilised, safer, than the C++ way. I don't think I was the only one. Because exceptions were now

gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.