Saturday, October 20, 2012

Mythbuster












I'm asked to use an automated code review tool at work. It's not FindBugs; nevermind the name. Given a repository URL, it checks for architectural integrity, programming practices, and documentation. It reports scores on the academic scale, with zero being the worst and 4.0 being the best possible score. It flags offensive components and presents citations to buttress its arguments.

I'm particularly bothered by one offense against architectural integrity. It says that instantiation of objects inside a loop is inefficient and gives the lowest possible architectural score. The goal is to avoid instantiation of too many objects.

How on earth is a developer supposed to populate a list of objects?

Besides, this might have been a serious issue on the earliest versions of the JVM. But object creation now rivals C in speed, and newer generational garbage collectors are also big improvements.

Rather than rail against the injustice, I decided to create a small benchmark and see for myself.

I started with a simple Person class:

package cast;

import org.apache.commons.lang3.StringUtils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Person
 * @author mduffy
 * @since 9/19/12 7:38 AM
 */
public class Person {
    private static final DateFormat BIRTH_DATE_FORMATTER;
    
    private final String firstName;
    private final String lastName;
    private final Date birthDate;

    static {
        BIRTH_DATE_FORMATTER = new SimpleDateFormat("yyyy-MMM-dd");
        BIRTH_DATE_FORMATTER.setLenient(false);
    }
    
    public Person(String firstName, String lastName, Date birthDate) {
        this.firstName = (StringUtils.isBlank(firstName) ? "" : firstName);
        this.lastName = (StringUtils.isBlank(lastName) ? "" : lastName);
        this.birthDate = ((birthDate == null) ? new Date() : new Date(birthDate.getTime()));
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public Date getBirthDate() {
        return new Date(birthDate.getTime());
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("Person");
        sb.append("{firstName='").append(firstName).append('\'');
        sb.append(", lastName='").append(lastName).append('\'');
        sb.append(", birthDate=").append(BIRTH_DATE_FORMATTER.format(this.birthDate));
        sb.append('}');
        return sb.toString();
    }
}

I needed to keep statistics on wall time and heap used, so I wrote a Statistics class:

package cast;

import org.apache.commons.lang3.StringUtils;

import java.util.Collection;

/**
 * Statistics accumulates simple statistics for a given quantity "on the fly" - no array needed.
 * Resets back to zero when adding a value will overflow the sum of squares.
 * @author mduffy
 * @since 9/19/12 8:16 AM
 */
public class Statistics {
    private String quantityName;
    private int numValues;
    private double x;
    private double xsq;
    private double xmin;
    private double xmax;

    /**
     * Constructor
     */
    public Statistics() {
        this(null);
    }

    /**
     * Constructor
     * @param quantityName to describe the quantity (e.g. "heap size")
     */
    public Statistics(String quantityName) {
        this.quantityName = (StringUtils.isBlank(quantityName) ? "x" : quantityName);
        this.reset();
    }

    /**
     * Reset the object in the event of overflow by the sum of squares
     */
    public synchronized void reset() {
        this.numValues = 0;
        this.x = 0.0;
        this.xsq = 0.0;
        this.xmin = Double.MAX_VALUE;
        this.xmax = -Double.MAX_VALUE;
    }

    /**
     * Add a List of values
     * @param values to add to the statistics
     */
    public synchronized void addAll(Collection values) {
        for (Double value : values) {
            add(value);
        }
    }

    /**
     * Add an array of values
     * @param values to add to the statistics
     */
    public synchronized void allAll(double [] values) {
        for (double value : values) {
            add(value);
        }
    }
    
    /**
     * Add a value to current statistics
     * @param value to add for this quantity
     */
    public synchronized void add(double value) {
        double vsq = value*value;
        ++this.numValues;
        this.x += value;
        this.xsq += vsq; // TODO: how to detect overflow in Java?
        if (value < this.xmin) {
            this.xmin = value;
        }
        if (value > this.xmax) {
            this.xmax = value;
        }
    }

    /**
     * Get the current value of the mean or average
     * @return mean or average if one or more values have been added or zero for no values added
     */
    public synchronized double getMean() {
        double mean = 0.0;
        if (this.numValues > 0) {
            mean = this.x/this.numValues;
        }
        return mean;
    }

    /**
     * Get the current min value
     * @return current min value or Double.MAX_VALUE if no values added
     */
    public synchronized double getMin() {
        return this.xmin;
    }

    /**
     * Get the current max value
     * @return current max value or Double.MIN_VALUE if no values added
     */
    public synchronized double getMax() {
        return this.xmax;
    }

    /**
     * Get the current standard deviation
     * @return standard deviation for (N-1) dof or zero if one or fewer values added
     */
    public synchronized double getStdDev() {
        double stdDev = 0.0;
        if (this.numValues > 1) {
            stdDev = Math.sqrt((this.xsq-this.x*this.x/this.numValues)/(this.numValues-1));
        }
        return stdDev;
    }

    /**
     * Get the current number of values added
     * @return current number of values added or zero if overflow condition is encountered
     */
    public synchronized int getNumValues() {
        return this.numValues;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("Statistics");
        sb.append("{quantityName='").append(quantityName).append('\'');
        sb.append(", numValues=").append(numValues);
        sb.append(", xmin=").append(xmin);
        sb.append(", mean=").append(this.getMean());
        sb.append(", std dev=").append(this.getStdDev());
        sb.append(", xmax=").append(xmax);
        sb.append('}');
        return sb.toString();
    }
}

Then I wrote a test method to exercise creating a million instances and compared the two idioms:

package cast;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * InstantiationInsideLoopTest attempts to answer the question: Is it bad to instantiate an object inside a loop
 * with a modern JVM?
 * @author mduffy
 * @since 9/19/12 7:35 AM
 */
public class InstantiationInsideLoopTest {
    private static final int DEFAULT_INSTANCES = 1000000;
    private static final int DEFAULT_REPETITIONS = 5;
    private static final String[] FIRST_NAMES = {
            "Abel", "Bob", "Charlie", "David", "Edward", "Fred",
    };
    private static final String[] LAST_NAMES = {
            "Smith", "Jones", "Carver", "Brown",
    };

    private Random random;
    private Statistics begHeapStats     = new Statistics("beg heap (MB)");
    private Statistics endHeapStats     = new Statistics("end heap (MB)");
    private Statistics wallTimeStats    = new Statistics("wall time (sec)");


    public static void main(String[] args) {
        int numRepetitions = ((args.length > 0) ? Integer.valueOf(args[0]) : DEFAULT_REPETITIONS);
        int numInstances = ((args.length > 1) ? Integer.valueOf(args[1]) : DEFAULT_INSTANCES);
        boolean isInside = (args.length > 2) && "inside".equalsIgnoreCase(args[2]);
        System.out.println(String.format("# reps: %d # instances: %d inside? %b", numRepetitions, numInstances, isInside));
        InstantiationInsideLoopTest tester = new InstantiationInsideLoopTest();
        if (isInside) {
            tester.instantiateInside(numInstances, numRepetitions);
        } else {
            tester.instantiateOutside(numInstances, numRepetitions);
        }
    }

    public InstantiationInsideLoopTest() {
        this(System.currentTimeMillis());
    }

    public InstantiationInsideLoopTest(long seed) {
        this.random = new Random(seed);
    }

    private void instantiateOutside(int numInstances, int numRepetitions) {
        this.begHeapStats = new Statistics("beg heap (MB)");
        this.endHeapStats = new Statistics("end heap (MB)");
        this.wallTimeStats = new Statistics("wall time (sec)");
        for (int i = 0; i < numRepetitions; ++i) {
            long begTime = System.currentTimeMillis();
            long begHeap = Runtime.getRuntime().freeMemory();
            List persons = new LinkedList();
            Person p;
            for (int j = 0; j < numInstances; ++j) {
                p = new Person(getFirstName(), getLastName(), getBirthDate());
                persons.add(p);
            }
            long endHeap = Runtime.getRuntime().freeMemory();
            long endTime = System.currentTimeMillis();
            this.begHeapStats.add(begHeap/1024L/1024L);
            this.endHeapStats.add(endHeap/1024L/1024L);
            this.wallTimeStats.add((endTime - begTime)/1000.0);
//          summarize("outside", i, begTime, endTime, maxHeap, begHeap, endHeap, persons);
        }
        System.out.println(this.begHeapStats);
        System.out.println(this.endHeapStats);
        System.out.println(this.wallTimeStats);
    }

    private void instantiateInside(int numInstances, int numRepetitions) {
        this.begHeapStats = new Statistics("beg heap (MB)");
        this.endHeapStats = new Statistics("end heap (MB)");
        this.wallTimeStats = new Statistics("wall time (sec)");
        for (int i = 0; i < numRepetitions; ++i) {
            long begTime = System.currentTimeMillis();
            long begHeap = Runtime.getRuntime().freeMemory();
            List persons = new LinkedList();
            for (int j = 0; j < numInstances; ++j) {
                Person p = new Person(getFirstName(), getLastName(), getBirthDate());
                persons.add(p);
            }
            long endHeap = Runtime.getRuntime().freeMemory();
            long endTime = System.currentTimeMillis();
            this.begHeapStats.add(begHeap/1024L/1024L);
            this.endHeapStats.add(endHeap/1024L/1024L);
            this.wallTimeStats.add((endTime-begTime)/1000.0);
//            summarize("inside", i, begTime, endTime, maxHeap, begHeap, endHeap, persons);
        }
        System.out.println(this.begHeapStats);
        System.out.println(this.endHeapStats);
        System.out.println(this.wallTimeStats);
    }

    private String getFirstName() {
        return FIRST_NAMES[this.random.nextInt(FIRST_NAMES.length)];
    }

    private String getLastName() {
        return LAST_NAMES[this.random.nextInt(LAST_NAMES.length)];
    }

    private Date getBirthDate() {
        return new Date(this.random.nextLong());
    }

    private void summarize(String testName, int repetition, long begTime, long endTime, long maxHeap, long begHeap, long endHeap, List objects) {
        System.out.println(String.format("test name    : %s", testName));
        System.out.println(String.format("repetition # : %d", repetition));
        System.out.println(String.format("list size    : %d", objects.size()));
        System.out.println(String.format("beg heap (MB): %d", begHeap / 1024L / 1024L));
        System.out.println(String.format("end heap (MB): %d", endHeap / 1024L / 1024L));
        System.out.println(String.format("max heap (MB): %d", maxHeap / 1024L / 1024L));
        System.out.println(String.format("wall time (s): %10.3f", (endTime - begTime) / 1000.0));
        for (int i = 0; i < 5; ++i) {
            System.out.println(objects.get(i));
        }
    }
}
I ran this test case on two different machines - one running Windows XP, another using Windows 7 - and both running Sun JVMs for Java 6. The results consistently said that the idiom recommended by the automated code review suite was incorrect. I understand the dangers of benchmarks like these, but it's comforting to attempt to be scientific rather than merely complain.
profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers

Sunday, October 14, 2012

Race Day













I was nervous during the week leading up to the race. My last long run went very well. I did another six mile run the following Saturday, followed by an easy three mile jaunt the next day that included my first taste of gel packs, those doses of instant fuel that could keep me from hitting the wall at the end of the race. I followed the "don't introduce anything new on the day of the race" mantra and tried one. The vanilla flavor was delicious; it went down easily but took time to get out of the package; most importantly, it didn't upset my stomach.

It was a week to taper, according to my plan, but I ended up doing nothing. Yoga was cancelled due to Columbus Day. I didn't run at all, because my wife was in the midst of a cold for the entire week, as were my co-workers. Disease was all around me! I was concerned about waking up on race day with a whopper of a cold that would put me on the sidelines. So I went to bed early every night and hoped that my immune system would fight the good fight.

Race Day dawned bright and cold. Temperatures were below freezing overnight. What to wear? I decided to run in shorts and forego the tights. I had an Under Armor shirt with the half-marathon long-sleeved t-shirt over the top. I wore the same baseball cap that I'd done all my training in; no hat over my ears. I heeded the "no changes on race day" advice and ran in my minimals. I was determined to not give in to sneaker fear. My one concession to the cold was gloves. That turned out to be a very smart thought.

We drove into the city, parked in the convention center garage, and walked over to Bushnell Park. We had time, but there wouldn't be too much standing around. I was in line, right next to the "9 minute mile" sign, with only twenty minutes til race time. The crowd was shoulder to shoulder. Would the start be as viscous as the Manchester Road Race? It only took me two minutes to cross the starting line, and I was running comfortably right away. I hit the start button on my stopwatch and fell into my cadence - ninety right leg strikes per minute. The full and half marathon groups ran in one pack at the beginning. My biggest concern was taking a wrong turn and getting lost. But that turned out fine. There was a big sign at Main Street telling full marathons to turn left and half marathoners to turn right. After that, all I had to do was follow the crowd.

I didn't feel warm for the first mile or two, but everything was calm and relaxed. My breathing was smooth and comfortable. Nothing hurt. There was a Gatorade stop around mile two. I decided to take a cup at each stop to make sure that I didn't dehydrate. I didn't hurry. I'd stop, take the cup, gulp it down, and then head off. Drinking and running don't mix any more than drinking and driving do. I made sure that all my paper cups made it into a bin. The street was covered with crushed cups and spilled Gatorade. The volunteers would have a big cleanup job.

The training plan works. There were no official mile markers, but I got the idea that I was maintaining a nine-minute per mile pace without any strain. When I ran under the halfway arch the clock read 59 minutes. It was the first time I thought that I could actually break two hours. I noticed a woman wearing a yellow shirt that said "2:00 finish" on the back. She was the pace keeper: as long as I could stay with her I'd have a chance of meeting my time goal.

I was ahead of her when I entered Elizabeth Park, but some gentle slopes slowed me down more than I realized. I could see the Travelers tower ahead when I ran down Albany Ave past the eleven mile marker. The pace lady was within sight but ahead of me, surrounded by suffering acolytes who hoped for the reward of a two hour finish time. I had to decide: settle into a relaxed pace, call it a nice effort, and miss out on a two hour finish, or pick up my cadence and catch her. It was uncomfortable, but I decided on the latter course. I heard her say "You wanna bring it home?" as I passed her. I kept going until I couldn't hear her voice anymore. Less than two miles to go? That was all I could manage when I started this journey 16 months ago, but now it was easy. I pumped my arms and legs the whole way, under the Bushnell arch, into the chute, and under the finish arch. The clock read 1:59. My watch said 1:57. My average speed was a shade under nine minutes per mile, which was better than any training run I'd done.

I surprised my wife. I walked along like a veal calf to pick up a medal, a water bottle, and a bag with a banana and some other snacks. She sounded surprised when I called her on my cell phone. "Are you done?" she asked incredulously.

I enjoyed both the journey and the destination. It's so satisfying to say that I managed such a thing at my age. I thoroughly enjoyed the experience, so much so that I'm happily thinking about another run in November.

Most importantly, I've made myself into a runner. What other transformation can I undergo? How else might I remake myself again?

profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers

Wednesday, October 3, 2012

Ready!














Last Saturday I had my last long run before the half marathon on 13-Oct. It was overcast and threatening rain when I hit the Airline Trail at noon, so I had it mostly to myself. There were a few hardy mountain bikers and a dog walker or two, but the traffic was greatly diminished.

The plan called for 12 miles, but I was ahead of schedule for the last two long runs. I wanted to have the confidence of knowing that I could complete the distance. I had a water bottle filled with ice-cold green Gatorade in hand. I planned to stop every 3-4 miles for 30 seconds to get a drink and stay relaxed. I wanted to simulate my plan for the race.

The first mile was a warm-up. I had a sweatshirt on that may have been overkill. By the time I stopped for my first drink I was loose and lathered up. The markers for the fourth mile went by quickly. I was at nine minute miles or better the whole way. I never saw the fifth or sixth mile markers, same as the week before. The trail had a gentle upward slope. I felt anxious as I ran: would there be a marker for the turnaround point at mile 3? I crossed a road that intersected the trail, ran to the next gate - and there it was! I was surprised when I checked my watch: I was still maintaining a nine-minute mile pace! I felt good. I had a long, relaxed drink and started down the slope towards my car.

I have to compliment the folks who put together the plan that I've used to prepare for the race. They know what they're doing. I felt strong on the way back. When I passed the ten mile mark, I had plenty left. I thought back to how I felt at the end of that personal best long run two weeks ago. I was greatly improved. I was slowing down and suffering a bit more when I passed the twelve mile mark. I thought back to how knackered I felt at the end of last Saturday's run. I was beyond that. The last two miles were difficult. But I felt like a running man when I finished in 2:15.

Now I'm tapering down for the next two weeks. The longest run will be five miles. I'll do some swimming, shorter runs, and yoga. I'm looking forward to that day. I just hope I can dodge catching a cold for the next two weeks.



profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers

Monday, September 24, 2012

Uncharted Trails

















Saturday morning meant another long run. My training plan called for ten miles, but I'd already accomplished that. It was time to go above and beyond. I was going back to the Airline Trail for a twelve mile run, longer than anything I've attempted.

Thankfully it was another perfect day. I brought a water bottle filled with Gatorade to carry with me. I resolved to stop for a swig every three miles. I found the starting mile mark at precisely noon and hit the trail.

The first miles were easy. I had no trouble knocking off the first three at a nine minute mile pace. I stopped for my first drink and felt fine.

The next three miles were on a part of the trail that was a little rougher: washed out and slightly uphill. I missed the five mile marker the week before. I had a pretty good idea of where it was this time, so I hit my stop watch as I passed it. It's a good thing I did, because thirteen minutes later I hadn't encountered the six mile marker. I stopped for a drink, turned around, and headed back where I came from.

I was experiencing some discomfort on the way back last week. My feet were sore and tired, and my right inner thigh hurt every time I picked up my foot. Not so this week. I felt strong and determined. My pace was slower, but I was still in the 10-10:30 per mile range. I still had some energy when I passed the ten mile mark. I finished twelve miles in 2 hours 5 minutes. Not bad! Best of all, I felt okay when I walked back to the car during the cool down.

My legs were tired for the rest of the day, but I was far from wrecked. When I woke up the next day I felt stiff, but not too bad. It was another gorgeous day. I could have followed my plan and done 30 minutes of easy running. I had gas in my tank, but I decided to spend it cutting my grass instead.

Next Saturday is the last long run before the race. The plan calls for twelve; I'll go above and beyond again and shoot for 14 miles. If I can do that, I know I'll finish this race in fine style. After that, I start tapering down for the race.

I'm going to hold at this level for a while. I'd like to try a few more half marathons and see how I feel before deciding on my next step. This has been a terrific ten weeks. I doubted whether I'd be able to do this when I started, but those doubts are almost gone.




profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers

Thursday, September 20, 2012

Personal Best









I'm close to the end of week 10 in my training plan. My last entry hinted that my journey to the half marathon might be at a premature end, pending the outcome of a long run.

The results are in: all is well.

My training plan called for an eight mile run. The weather was exquisite last Saturday: sunny and cool as befits a late summer day on the cusp of autumn. I decided that long runs were best attempted on the Airline Trail, a rails-to-trails project that runs from East Hampton through Colchester, Hebron, and beyond. It's a straight shot, interrupted periodically by intersecting streets. I drove to a small parking lot off Route 85 and found the wooden post with the numeral ten on the left side of the trail. Miles are marked by numbered posts along the entire length, so keeping track of my progress was mindless and easy.

The surface is packed, dressed gravel that's easier on the feet than pavment. There's a canopy of leaves overhead. The trees seemed to act like a natural wind tunnel: there was an intermittent, comfortable breeze in my face for most of the run. Lots of other people had the same idea as me: to be out on foot, bike, or horse on a beautiful day. The traffic was still light compared to my usual routes on the road.

The first five miles felt easy. I was comfortable the whole time. I had trouble seeing the post with the five on it, because it was obscured by shrubs. I ran past it for a minute before turning back and correcting my error. The miles added up with each step. Soon I had matched my previous best of seven miles; then eight and nine went by. That last mile was a struggle mentally. My feet were tired. A muscle in my right inner thigh complained a little every time I lifted my foot. I kept waiting for "runner's high" to make an appearance and ease my suffering, but endorphins were in short supply. I only had two thoughts: to finish the run and to stop. I managed to accomplish both, finishing the run in 1:42. It was the longest run of my life. Now I know that I must have been hallucinating when I recalled a run of that distance when I was 21. There's no way I would have had the patience to run for that long a period of time.

I was dog tired for the rest of the day, but recovery was swift. I had a good yoga session at home on Sunday, and my regular ninety minute class on Monday night. I soon felt like myself again.

I've had two more runs since that personal best. I ran four miles inside on the track on Tuesday, because weather threatened to make running impossible after work. Conditions were delightful after work tonight. I ran the three miles that my plan called for. Dare I say it? The run felt easy. I experienced no pain anywhere. I could have done more. I had a lot of gas in the tank when I returned home.

Saturday will mean twelve miles on the Airline Trail. I'll be in uncharted waters again. My confidence is growing. I think I might succeed in this venture.



profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers




Sunday, September 9, 2012

Feet












I had a setback this week in my preparations for the Hartford half marathon. I did a seven-mile run on Sat 1-Sep. The first two miles were run under threatening clouds; the middle three had steady rain the whole time; the last two were sun-lit. My feet were very tired when I was done, but everything else felt fine.

The following Monday was the Labor Day holiday from work. The remnants of Hurricane Issac threatened us with rain for most of the week, so I decided to forego the rest day and do a four-mile run. I felt okay, which was good considering the long mileage I'd logged just two days before. I can't recall now if I stepped on something and felt a twinge in my left foot. But I wasn't concerned when I done.

I woke up the next morning and felt a lot of pain between the balls of my left foot. It was sore enough and in a spot where I feared a stress fracture at first. I put the thought out of my mind, because there wasn't any swelling. But the pain was sufficient to persuade me to take a break and stay off the road.

I consulted with a number of people about it. I have two daughters who are familiar with such things. My oldest is preparing for her third marathon next month. She got ready last Saturday by running a half marathon in 1:35. The two girls ran 18 miles together a couple of weeks ago, so they know something about running and the injuries that go along with it. Both counseled patience; all I needed was rest and exercises. I got some suggestions for exercises that were sobering. I did calf raises with my heel hanging over a stair that showed my left foot to be dramatically weaker than my right. I have to pay more attention to those. I've done yoga for five years now, but I think I'll have to redouble my efforts. Hips, knees, calves, Achilles, feet - all are connected. My left side is markedly stiffer than my right. A daily morning yoga session before work is an order.

A co-worker steered me to Fleet Feet in West Hartford. I knew that my minimal shoes were due to be replaced; I bought them last year on 22-Oct. The soles are worn away. I picked up a beautiful new pair of blue shoes and a green "Rubz" ball to roll out the knots in my feet. Damn, but that thing hurts! My left arch is in agony when I start using it. The pain subsides after a few minutes, but I'm very tender there. I'll have to be diligent about using the ball, too.

Conditions outside are spectacular today, so I decided to hit the road for a three-mile jaunt in my new shoes to see how my feet would hold up. I warmed up with 30 minutes of yoga, rolling out the knots with the ball, and some strengthening exercises. My times weren't impressive - they haven't been all along - but my feet withstood the road without a problem. They're tired, but there's no pain - until I step on that damned ball.

I've lost a week, but I don't think that's the end of the journey. I'm still hoping to run the half in a month. I'll know better after my next long run on Saturday.




profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers



Wednesday, August 29, 2012

Running The Half










One of the things that I try to practice is change. I used to work for a guy who insisted that it was impossible to read a book or learn something new or change at all "at my age."

I think I'm now as old as he was when he made that statement to me.

It's not necessarily about him, because my choices are solely my own, but I believe it's important to not give up the fight to improve physically and mentally just because we're older. I'm not what I was when I was 20 or 30, but I do what I can.

I also believe that the best way to make progress on anything is to pick a well-defined goal and take frequent, small, incremental steps in the chosen direction. New skills are best acquired by accretion of lots of small, targeted efforts.

A year ago last June I wrote about starting to run seriously for the first time since I was in my twenties. I spent a year of doing half mile repeats on the road in front of my house, thinking more about pace, cadence, and form instead of distance. I ran the Manchester Road Race on Thanksgiving Day, as tradition demands, but that was the only long run that I tried. I kept it up faithfully throughout the mild, snowless winter. I was still injury-free and feeling good in June.

Now that I had a good base, I decided that I needed a new goal. At the end of that previous posting I wrote "I'm not trying to run marathons. I don't want to race or be competitive." I don't want to race or be competitive, but I've decided to try completing a half marathon.

I needed a plan, so I fired up Google and found a schedule that I liked. I put the plan into a spreadsheet and set the start date so that race day was Saturday 13-Oct-2012: the ING Hartford half marathon. Plan the work, work the plan. I missed one whole week to attend a family wedding, but I've been faithful with the rest of it. I've completed the first half of the schedule. Now it becomes challenging. I increase the length of the long run by a mile per week until I complete a 12 mile run the week before the race.

This is uncharted territory for me. I have a vague memory of a long run when I was 20 years old. I was living in my parents' house, home from college, when I ran into a friend when I was out for a run after work. I fell in with him for a long time. I asked him how long the route was that we were taking. He told me it was a ten mile run, but now I'm not sure. The six mile run I completed last Saturday might have been the longest I've ever finished.

My times won't impress anyone. I feel comfortable at a 9-10 minute per mile pace. If I can keep that up for 13.1 miles I'll finish somewhere between 2:00 and 2:20. The most important thing will be to finish.

It's not a fait accompli that I'll be able to do it, even thought I've registered and paid my money. My ankles, knees, and hips feel fine so far. I've had no muscle problems at all, not even stiffness or soreness on the day after running. But if something blows out or gives way I'll have to postpone it for another day.

I'm hoping that won't happen. I'd like very much to see this through.

I'm most curious about the mental aspect of running for that long a time. I've never experienced "runner's high." I don't know if I'll be able to handle the discomfort and pain that's an inevitable part of crossing the finish line. How do you stay focused when running for more than two hours? I'll find out.

If I can finish this, who knows? Maybe I'll find a full marathon training plan and just keep going!

I haven't been in a pool in two months. I'm going to go back, but I've learned that daily exposure to chlorine isn't good for my lungs. I'm going to continue to mix swimming in, but it might be just a once per week Masters swim with my friends.

After all these years of pounding out yards, the biggest surprise is that I could stop swimming and actually feel better.

The journey has been a pleasure. I've achieved my real goal: my lungs are feeling much better. I don't wheeze when I run. I recently had to see a doctor. They took my pulse and blood pressure, per usual custom. "Are you a runner?" the nurse asked me. My resting heart rate was 50 bpm; my blood pressure was 98/70. I was startled to see both, because they haven't been that low in a long time.

I smiled and said "Why yes, I am a runner."

What will that next objective be? Where else could I use some change in my life? I've got some ideas.



profile for duffymo at Stack Overflow, Q&A for professional and enthusiast programmers