Jan 7, 2012

Across The Years 72 hour: 150 miles

Day 1.. still fresh. Photo by Ray K
Going to keep this report short and sweet.

By the numbers:


Approximate miles per 24 hour split:
* Thursday (9am-9am): 80 miles
* Friday: 30 miles
* Saturday: 40 miles

Approx. time on the course per 24 hour split:
* Thursday: 23 hours
* Friday: 18 hours
* Saturday: 20 hours

Things were pretty good for the first 100K, which I achieved in about 16 hours. Afterwards, I slowed down dramatically. In particular, miles 65-90 were among the slowest I've ever run. 90-110 went OK, and the last 40 in 24 hours amounted to a sleep-deprived deathmarch where I was unable to move faster than about 16 minutes per mile at best, and about 21 average. I finally reached 150 miles with 90 minutes to spare. I went to sleep and didn't wake up until after the race had ended.

It became obvious on day 2 that I wasn't going to reach my original goal of 200 miles. This is due to incredible amount of time it took me to get from 65 to 90 miles. I revised my goal to 150 miles, which seemed easily doable at the time but even that took far more effort than I anticipated.

Thoughts:


By the end of the race I was in total countdown mode, repeating to myself in the wee hours of the morning "6 laps to go, 6 laps to go, 6 laps to go." This mantra didn't keep me going per se, but rather occupied my thoughts in a very difficult period for me.

In fact, through the last 48 hours I would occasionally find myself audibly saying, "this is so hard.." In fact, it occurred to me that this was so hard that the difficulty alone made it worth doing. Yes it's fun to see my runner friends, it's fun to achieve goals, it's fun to hear the hurrahs of all my non-runner facebook friends, it's even fun to be a little competitive.. but as I staggered along muttering at 2am, I suddenly realized that I simultaneously hated and loved how hard it was. I don't want to be melodramatic, but at that point things honestly sucked ass. But there was something intrinsic about the experience of attempting something so difficult that kept me going despite the suffering that would have overwhelmed the more trivial advantages.

People talk about scenery and friends and nice weather and whatever else gets them to do these things, but honestly - you can approach ATY as something so simple - running/walking - and make it difficult enough that the difficulty itself becomes the primary draw rather than the activity or mode. In other words, I go to this thing not because I like to run, but because I like to attempt incredibly difficult tasks.

That was the insight that I had from this race.

On another note, I know this was a good race for me because even thought I feel like it was a positive experience, I am also feeling completely depleted. My aches and pains are going away and my blisters are healing, but my mental state is still such that I am not interested in racing another ultra anytime soon. This will change, of course - I'm sure I'll be ready to rip off a 100-mile finish at Umstead on March 31 - but I also am unwilling to do anything before then, nor anything too soon after.

Jan 5, 2012

Java: Character Encoding in char[] and byte[]

We were dealing with an issue at work where some emails going to Japanese customers were coming through as jibberish. So I went into the code and noticed that, in the processing, at one point the email body gets converted to a byte array and then back as a String. Suspecting that was the problem, I found myself wondering about java's byte[] and the more familiar char[]. There are methods in the java class library for java.lang.String that convert into both  (getBytes() and toCharArray()), so what's the difference between these two primitive types?

The more I thought about it, the more it made sense that the byte array was going to be the issue. At first glance (especially for those of us in the west), a char would seem to be essentially the same as a byte. But that was before I gave more consideration to the incredibly complex and amazing world of character encoding. A byte is by definition 8 bits, so only character sets that use 8 bits can be represented as a byte - limiting you to 255 characters. The primitive data type char on the other hand (and I had to look this up) is a single 16-bit unicode character - two bytes long - and 16 bits is enough to represent 65535 unique characters - plenty for, say - the japanese character set.

To test this theory, I wrote a little program that read a string of japanese text from a file (in Unicode), then converted it to both a byte[] and a char[], then converted those back to two Strings and compared them against the original.


public void testJapaneseCharactersInIsolation() throws Exception {
    \\ jap.txt contains a sentence or two of japanese text.
    FileInputStream file = new FileInputStream ("c:\\jap.txt");
    \\ this would look at my locale and by default try it as UTF-8 
    \\ so we'll instead specifically tell it to read it as UTF-16.
    InputStreamReader isr = new InputStreamReader(file, "UTF-16");
    BufferedReader reader = new BufferedReader(isr);

    String japanese = reader.readLine();
    char[] chars = japanese.toCharArray();
    String japaneseChars = new String(chars);

    byte[] bytes = japanese.getBytes();
    String japaneseBytes = new String(bytes);

    if (japanese.equals(japaneseChars)) {
        System.err.println("chars equals original");
    } else {
        System.err.println("chars fails");
    }
    if (japanese.equals(japaneseBytes)) {
        System.err.println("bytes equals original");
    } else {
        System.err.println("bytes fails");
    }
}

And the output:

chars equals original
bytes fails

Internally, we're actually using ByteArrayOutputStream without specifying any sort of encoding on the toString(), so the effect is as if we were just doing a String foo = (bar.getBytes()) and expecting foo to equals the String bar - which it will for UTF-8 - but not for Unicode. Of course, Sun (now Oracle) is smart enough to assume people like me will want to use ByteArrayOutputStream for Japanese characters, and they support an encoding specification in the toString method of the library. The next step for me would be to write some business logic that specifies the proper encoding for the dialect in use (or, preferably, find a single encoding that will work for everyone.)

By the way - Joel Spolsky is much smarter than me and has written a great article educating developers who introduce bugs by only supporting 8-bit character encoding. It's called "The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)" http://www.joelonsoftware.com/articles/Unicode.html.

Nov 9, 2011

Race Report: Marine Corps Marathon - 4:48:53 (PR)

This report has been sitting as a draft for weeks. What you'll read is a report from a ten-day-later perspective, not a month-later perspective. Sorry to keep you waiting.

Pre-race
Well, it's a full ten days since the Marine Corps Marathon, and I'm just now getting around to writing a report. The result was great - sub-4:49, which is a lot faster than my previous PR of 5:31. I reached my sub-5 A-Goal so strongly that I wonder if I didn't set it too modestly. The race just went really well, so well in fact that I find myself reluctant to write about it - there just seems so much more to write about in a mediocre result, where I can address topics such as what happened? What didn't work? What can I do better? However, in this case - where a race works itself out almost perfectly, I really have little to say. I guess all I could do is play-by-play? And when I have a successful race, anything I find to complain about just seems to be hunting and pecking. Nitpicking.
That was a long paragraph to say.. this will be short. So let's just do the Play-by-Play.
  • I ran the entire first mile - keeps the streak alive.
  • I quickly started walking after the first mile, in the sometimes steep hills of Rosslyn.
  • The "Jog-a-mile, walk a minute or two" plan worked nicely with the early hills, provided I was flexible on the mile part. If a hill happened 0.75 miles after my last walk break, I walked. No big deal.
  • There was a long hill around the Georgetown Reservoir that I probably walked for 3-4 consecutive minutes. Again, no big deal.
At this point I'd like to reiterate what I said in the "Stating My Intentions" post - sub-5, which is about 11:30 per mile. Plan for a positive split, aiming for 10:45 miles in the first half-marathon. Then ease off a bit, and hang on to that sub-11:30 pace as long and as late as I can.
  • My average pace through the first half was right where I said it would be - eleven of the first thirteen miles were within 20 seconds of 10:30, about half above, half below. The two exceptions were a 10:54 in mile 2 (mostly uphill), and a 9:56 in mile 4 (downhill.)
  • I made a conscious effort to ease off at the half-marathon point, and mile splits started varying more widely on the flats of the national mall. Until mile 24, every mile split was within 30 seconds of 11 flat - some were down near 10:30, others were as high as 11:27 - but all were below the 11:30 required for a sub-5. Again, they were evenly distributed within that 60 second range of 11+-30.
  • It became obvious to me by "The Bridge" that not only was I going to break 5 hours, I had a shot at 4:45. But when we got to the very crowded out-and-back portion in Crystal City, I knew that I was starting to slow down more dramatically.
  • Mile 24, on the way back to the Pentagon, was my first mile below 11:30 - it was 11:54. Mile 25 was 11:38, and mile 26 was my slowest at 12:11. Good enough though! Can't complain about that!
So that's how it played out. Pretty much as good as it gets for me. I was able to just zone out and run without issues. I would have preferred not to drop off my pace so much in the last 5K - but, like I said - that's just hunting and pecking.
Post-race, with a PR and a smile.
On Big City Marathons

No doubt about it, MCM is a popular race - to the point that it's crowded. I've heard people talk about this before. There were always people around. 20-30 within a 20-30 foot radius of me - constantly. And, in the last ten miles as people started struggling - slowing down, walking - there was a lot of jockying for position and weaving my way through the groups who were sometimes moving 5-6 abreast. It got really bad in Crystal City with the two-way traffic. I know this reads like I'm complaining and I guess I am, but to complaint is really not my intention. I just feel like it's important to point out - the course and field is such that a mid-pack runner will have a lot of company at MCM.
These are some tough MoFos, who did the entire marathon with these packs. Notice the crowds  - this was at mile 5.
But also, that's one of the draws of these races. I had not run a big-city marathon in a couple of years. Big City Marathons (BCMs?) are .. a pain in the ass, but a worthy pain in the ass. Logistically, they're a nightmare. Took us three hours to get back to our car after the race. You have to deal with hotels, and expos, and a million people.. Plus they're expensive.. It's just so much easier and more pleasant to run on the trails, or to run a small low-key event. But there's something about a BCM that I crave.. to the extent that I start to feel a void if I go long enough without one. Taken in moderation, the crowds are exhilarating. The crowd support at MCM in particular is really good - more so than Chicago. There were people lining the course pretty about 80% the entire race after Rosslyn. And they were all cheering and holding up signs and having a good time.. I really llike that.
In an uncharacteristically non--crowded moment. of the race.. What a course! 
But, like I said, it's just too much effort to do that all the time. It's been two years since my last BCM, and that is probably a good interval. So far, among the BCMs, I've done Las Vegas, Philadelphia, Chicago, New York, and now Washington DC. My next BCM will likely be NYC in 2013.. which is perfectly timed for that two-year interval.
Awesome medal! Maybe second-best after Big Sur!


Splits:
05K 0:33:26 33:26
10K 1:06:22 32:56
15K 1:39:17 32:55
20K 2:12:22 33:05
25K 2:46:46 34:24
30K 3:21:46 35:00
35K 3:56:50 35:04
40K 4:32:39 35:49