Friday, February 28, 2014

A Speed Comparison Born From Curiosity (GoLang and Ruby)

I was a little curious about the speed of a Raspberry Pi versus my older model MacBook Pro.

The Raspberry Pi is the B model with 512 MB of RAM and a 700 MHz processor which Linux is reporting as an ARM6-compatible processor (CPU: ARMv6-compatible processor [410fb767] revision 7 (ARMv7), cr=00c5387d).

The MacBook Pro is a 2.4 GHz core 2 duo running 10.9.1 with 4 GB RAM.

I wasn't looking for anything necessarily in depth as a benchmark. I simply whipped up a quick counting loop in Go (1.2 release) and compiled it for Darwin and another for ARM. Ran each with the time command. Here's the source:

package main

import "fmt"

func f() {
        for i := 0; i < 1000000; i++ {
                fmt.Println(i)
        }
}

func main() {
        f()
}

Results?

On the Mac:

real 0m9.875s
user 0m0.922s
sys 0m1.709s

On the Raspberry Pi:

real 12m41.821s
user 1m56.220s
sys 3m43.170s

...holy crap.

I mean, I expected a difference, but I guess I didn't expect quite that much of a difference.

Wow.

If I round the Mac to 10 seconds and the Pi to 760 seconds, that's saying the Mac is 76 times faster at executing this loop using Go.

When I wanted to play with programming I originally I was going to use Ruby. What if I created a stupid simple Ruby loop that counts to the same number? The source:

for n in 1...1000000 do
        puts "#{n}"
end

Yes, I know there's a preferred syntax that I didn't follow for Ruby, I was just throwing a quick throwaway loop at 2 in the morning. Don't judge me.

The results?

On the Mac:

real    0m16.523s
user 0m5.210s
sys 0m3.543s

On the Raspberry Pi:

real 9m23.992s
user 2m10.790s
sys 1m55.610s

Well, this is interesting.

The Mac was about 17 seconds. The Pi was about 560 seconds. That means the Mac was about 33 times faster.

The Mac was 76 times faster with Go but only 33 times faster with Ruby.

Does this really mean anything? Not really. There are other things at play that could affect the speed of the applications; these platforms are really very different beasts. All you can really say is that the Pi is relatively slow compared to an older-generation Mac. Kind of expected. The only surprise is the magnitude of difference.

Multiple runs of the application and script weren't even entirely consistent; probably due to caching, or background tasks eating processor cycles when I wasn't aware of it (if I really wanted to control conditions I'd run this several more times, recording each time, and make sure my OS was doing as little as possible while conducting the test and not having, say, a web browser open.)

(NINJA EDIT - this may be more due to implementation of the code used to write output to the console and to what degree I/O is blocked in the process. See near the end of the post for my second NINJA EDIT.)

Just for giggles, I tried running the tests on another Macintosh; this one a MacBook Pro with 16 gig of RAM and a 2.6 GHz core i7 processor. The Go executable clocked in with:

real 0m4.559s
user 0m0.587s
sys 0m1.049s

...and the Ruby script hit with:

real 0m6.170s
user 0m2.945s
sys 0m1.956s

Anyone have an explanation for why I got these results? Feel free to leave a comment.

NINJA EDIT:
After further goofing around and posting about this on Stack Exchange, I had speculated that output to the console was somehow blocked for Ruby, and the answers seemed to confirm that (at least for Ruby. Ruby I/O is apparently a blocking operation most of the time?) I should have thought of this before, but they suggested that the best way to get the run-time was to redirect output to /dev/null.

So, using "time <executable> >& /dev/null", I got the following results:

On the 4 GB, 2.4 GHz Mac:
The Go executable:
 real 0m1.416s
user 0m0.752s
sys 0m0.575s

The Ruby script:
real 0m1.242s
user 0m1.170s
sys 0m0.030s

Now for the Raspberry Pi:
The Go executable:
real 0m17.345s
user 0m14.620s
sys 0m2.700s

The Ruby script:
real 0m34.479s
user 0m34.360s
sys 0m0.060s

Well...wow? That "puts" operation must really be hindering the Ruby script. Ouch! And that Go executable...Just wow. That is really, really fast, compared to when I run it to the console instead of redirecting output.


Monday, February 24, 2014

Updating Go (GoLang) From Source

I previously posted an article explaining how to install Go on a Raspberry Pi with cross-platform support. If you're like me (and depending on the platform), you're probably used to installing programs through packages, app stores, or single-file MSI installers or setup executables. Or on Linux platforms like Raspbian you first turn to the repos used by apt. Don't feel bad. These methods were invented to prevent DLL Hell, bit rot, or dependency problems. Sometimes they make the chore of keeping your system up to date a little easier.

The method I used was the tried-and-true UNIX way of doing things..."Compile From Source," sometimes referred to as the "Dammit Why Won't This Work" method. I have to admit, though, Go actually worked pretty well, meaning the maintainers of the code base are doing a really find job at taking care of their stuff.

But what if you want to update to the latest version? The version I installed in the post was a development version, meaning that as the devs are updating the code base with latest features and fixes, that was what was getting compiled on my Pi.

But what if I wanted to update? Or use the official stable release version, which is the version that is more or less tested to be...well, feature stable?

Turns out it wasn't that hard to get up to date.

First, I run screen. Mainly because I'm SSH'd into the Raspberry Pi, and since the steps can take awhile, if something disconnects me I don't want the process to be interrupted. Then I start the process thus:

cd go/src
hg pull
hg update release
./all.bash

This step took awhile as the Raspberry Pi worked on compiling the Go source.

Now for the cross-compile tools.

source golang-crosscompile/crosscompile.bash
go-crosscompile-build-all

This step will take longer than the initial compile. That's why I used screen...I can use Ctrl-a/d to detach from my session and check the progress later. 

Once the compiles are all finished, though, you should be able to

go version

...and have the prompt reply with:

go version go1.2 linux/arm

Monday, February 17, 2014

First Time Playing With Version Control (Git)

Version control. Not typically covered in beginner programming courses. I'm not sure if it's ever really covered unless you get a programming job with a team where you're forced to use some method of sharing and distributing your work, and even then I've run into stories where startups come up with some organically grown in-house methods of avoiding proper version control.

What is version control? It's more or less as the name implies. Version control applications allow you to save different versions of your application source so you can roll back changes to different points in time. Screw up a new set of patches? Roll it back. Want to experiment with an experimental feature? Branch the source to create a parallel version of your application. If it works, you can merge that branch with the "known good" version. Or if it's a one off, you can create the new function and roll everything back if you change your mind (although the branching method is preferred.) Version control makes it simple to track your progress and the history of your application development.

Since I was playing with Go starting from scratch it may be a good time to at least try to expose myself to the basics of Git. The Raspbian install already had Git installed from repo, probably as a requirement for another package. The Git site has instructions for installation on your platform of choice (although if you're running Linux, I'd suggest a Google search for installing Git on your distribution of choice.)

Actually using version control is a little irritating in that it adds more steps, but isn't horribly cumbersome. Once I had created a source code file, I ran:

git init

...from the root of the directory I am creating my source code in. This creates a local Git repo in a hidden (on Linux and Unix systems) .git directory. Every change you commit is placed into a structure under the .git directory, making it really convenient to back up or start your version control from scratch if you decide to do so for some reason.

Then you tell Git that you want to add files to the local repo.

git add .

That tells it to just add everything, folders and files and all. You can add specific files by changing the period to a filename. You can get a status using

git status

...which in my case replied with:

# On branch master
#
# Initial commit
#
# Changes to be committed: # (use "git rm --cached <file>..." to unstage)
#
# new file: hello_world.go
#


This tells me I'm working on the branch called master and it's ready to add the file hello_world.go to the local repo. It hasn't done it yet, though. First, I configure a couple of items that get added to my commit metadata:

git config --global user.email "me@me.com"
git config --global user.net "My Name"
git commit

The commit command tells git to actually put the files in to the version control system. Git opens a simple text editor and at the top I enter a line to describe my first commit message. When I exit, the console replies with:

[master (root-commit) ac7121a] Created first hello world source file
1 file changed, 9 insertions(+)
create mode 100644 hello_world.go



The comment is at the top of the commit; "Created first hello world source file." Now I make a few alterations (experimenting with the workspace of a Go project, figuring out the directory structure), necessitating telling Git I added a few items.

git add .
git commit -m "Changed directory tree structure"

The "-m" adds the comment in the command line rather than bringing up a text editor. I'm not entirely sure that the "add ." does anything significant when I just created some directories since Git only replies with,

[master 708b35d] Changed directory tree structure
1 file changed, 9 insertions(+)
create mode 100644 src/hello_world.go

I made a few more changes while experimenting with my first Go program. Then I went back to the root of my project tree and ran a few Git commands.

cd ..
git add .
git status


# On branch master
# Changes not staged for commit: # (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: hello_world.go
#
no changes added to commit (use "git add" and/or "git commit -a")

git commit -m "Added a bin directory to tree"

At this point nothing seemed to be logged as a change. Using the command 

git log

...seemed to verify this. I theorize that if you alter directories, it doesn't seem to care. If you actually alter files, then Git notices and adds it to the repo. Maybe someone with more experience could validate that this is Git's behavior. 

I then go ahead and create a binary of my Go application then tell Git to commit the latest set of changes. I back out of my binary folder in my workspace and give Git some commands:

cd ..
git add .
git commit -m "Built the binary"

[master 3aeb831] Built the binary
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100755 src/hello_world

git log


commit 3aeb8311138035b4e46b75f9b2251a2c526b39a4
Author: My Name me@me.com
Date: Sun Feb 2 13:14:59 2014 -0500
Built the binary
commit 708b35d1e716feaf0562e8adbcc2e26c53292bb0
Author: My Name me@me.com
Date: Sun Feb 2 12:57:17 2014 -0500
Changed directory tree structure
commit ac7121a7b8a95a08d95a4e421f0dd87fec0a11af
Author: My Name me@me.com
Date: Sun Feb 2 12:41:26 2014 -0500
Created first hello world source file
...here you see that "git log" tells you the history of commits, with the comment, date, and person who made the changes.

This covered the basics of creating a local version repo, committing your changes, and getting a status of your commits. Not too hard; if you're using the command line, it usually means having to remember periodically commit with a quick note of what changes you had made.

Git has several other functions available and this only covered the basics of starting with Git. I haven't played with branching, and nothing here displayed how to roll back changes. A near future blog entry will review another feature of Git, but for now this is enough to absorb into the "what am I doing" part of the brain.

Monday, February 10, 2014

For a Gift I Gave a Memory...and a MacBook

Valentine's Day is coming up. A coworker sent a tweet regarding, from what I can infer, Apple having an ad for iPads as Valentine's gifts. I replied to her with a link to a story about a guy that bought an iPad and carefully wrapped it, dipped it in chocolate, and re-boxed it so it appeared to be an iPad shaped chocolate, only, SURPRISE! It's an actual iPad.

It reminded me of surprising my wife with a new MacBook several years ago, although it wasn't chocolate dipped. For the money spent on these, I wouldn't have had the cojones to try covering something like a laptop with chocolate. I replied to my coworker telling her she should ask me about it sometime, this surprise to my wife, and much to my surprise, she actually did ask a few nights later; usually it seems my coworkers take little interest or followup in my activities so I figured online social graces would be soon forgotten. I guess it's safe to say my coworkers still surprise me once in awhile.

My recollection was...not the best in storytelling. It was jumbled a bit as my memory is much more affiliated with a sieve than an elephant. Afterwards I contacted my wife and asked her recollection of the story. She didn't really tell me much of the actual escapade, but said, "I still have the box and the notes. And the clues."

I didn't know she had kept all of that. I felt another twang in the miss-you center of my brain, followed by a surge in the sadness center of my chest. But it warmed me to know she still had these things.

Soon my email notified me of incoming messages. Scans of the notes I took.

See, when I planned all of this initially, I actually took the time to write an outline of what to do. When I was a kid, on a couple of occasions my Mom bought a toy or...I don't remember what, exactly...and hid it. She would then leave notes in certain spots and give me the initial clue.

It probably says something that I don't remember what she gave me as a reward but I do remember searching around the yard and house trying to solve the clues on these pieces of paper.

What I ended up doing was hiding clues around the high school where my wife worked, and sending her on a scavenger hunt of sorts. Waiting at the end was a shiny new Macbook Pro. She still uses it today.

For reference, here are the notes I had written for outlining the adventure.




My hope is that when the laptop finally reaches the point where it must be replaced, she'll still remember this story, and will remember it fondly.

Love you Norma!

Sunday, February 2, 2014

Installing Go (GoLang), With Cross-Compile Support, on a Raspberry Pi

I've written previously that I was working on playing with programming languages. For reasons I won't bother getting into in this post, I decided I wanted to play with Google's new flagship language, Go.

For a playground in which to experiment, I decided to use a Raspberry Pi running Raspbian 3.10.25+ (Wheezy) at the time of these instructions.

First, there was a version of Go in the apt-get repo. I installed it and discovered it was running version 1.0.x. I love the package management system, since it keeps things up to date without worrying so much about cruft and dependencies. But the downside is that getting new versions of packages means waiting until package maintainers get around to upgrading everything. Once I saw the version of Go installed by apt-get, I ran apt-get remove.

From there I followed Dave Cheney's installation from source instructions. Since I was using a Raspberry Pi with more memory, I didn't need to do the memory split and swap parts of his instructions.

His instructions were to first install prerequisite tools.

sudo apt-get install -y mercurial gcc libc6-dev

Next use Mercurial to get a copy of the Go compiler source.

hg clone -u default https://code.google.com/p/go $HOME/go

This places a copy of the current source under the directory "go" in your home directory. The next step is to actually build Go.

cd go/src
./all.bash

This will build Go and run a series of tests, although they will take somewhere between an hour and two hours to complete. The last messages should contain a reference to all the tests having passed; if they didn't, something went wrong (obviously.)

The next step is to add the path for the binaries. Instead of just exporting the path as mentioned in the instructions, I added it to the end of my .bash_profile.

PATH=$PATH:$HOME/go/bin

At this point you should be able to get a version.

go version

...will give something like:

go version devel +2fcaec19e523 Fri Jan 31 18:09:53 2014 +0400 linux/arm

That version corresponds to version 1.2.x of Go. What I did next was to set up Go for a cross compiling environment. Go actually has decent support for compiling binaries for other platforms without requiring a compilation environment on the target platform. There are problems with this, and it's possible a future release will iron them out.

The steps to create the cross-compile environment has been partially automated, thanks again to instructions from Dave Cheney. His instructions were for version 1.1.x, but seemed to work for 1.2.x.

Quick summary...

Grab support scripts from GitHub. I did this from the go/src directory.

git clone git://github.com/davecheney/golang-crosscompile.git
source golang-crosscompile/crosscompile.bash

Then we run the build the new compiler executables.

go-crosscompile-build-all

After a very long time, you should have a whole bunch of platform-specific packages residing in the go/pkg.

To do the actual cross-compile, you just run go-<platform> on the target .go file. The "source golang-crosscompile..." command above creates a series of aliases to create a command like "go-linux-arm" and "go-darwin-amd64". From the proper workspace directory, I can run something like

go-darwin-amd64 install

...and the proper platform-specific executable is created.

One note to remember is that the crosscompile.bash script doesn't seem to make these changes permanent. You can re-run the script before playing with the cross compile builds, or parse the script and add the proper aliases to your profile for permanent use.