Save Water: Have Smaller Baths

Containers are getting smaller. Our programming needs to shift to compensate.

We used to be able to write code that was going to run on big computers with tons of memory and a hefty CPU. Then cloud computing came along and computers were of a decent size, but not huge.

Then containerization came along and we started running our software on a quarter of a CPU with a scant few megabytes of system RAM.

Enterprise software developers must master some of the same challenges as developers of embedded or gaming software.

We Must Perfect the Art of Resource Usage

As I have written about before in terms of CI, the efficiency of your compute tasks has a green impact. Green in terms of server farms heating up the planet and burning energy, and green in terms of your dollar (or whatever) not being wasted because you have to run your environment over more nodes.

On top of that, you probably want your apps to run at their highest speed.

A computer program uses CPU cycles to calculate things and consumes system memory while it’s doing that. When the task is over, the system memory must be reclaimed, which causes more CPU usage. Similarly, when tasks use a lot of system memory, there’s effort and risk involved. Effort in allocation, risk in terms of something slowing down or stopping when the system runs low/out of memory.

Know Your Enemy

Temporary heap objects are not the enemy. Unnecessary ones are.

Consider this algorithm:

private static final String PAIR_PATTERN = "(?<=\\G.{2})";

public static String hexToString(String hex) {
    if (StringUtils.isBlank(hex) || 
          hex.length() % 2 != 0) {
        throw new RuntimeException("Invalid input");
    }

    List<Byte> list = Arrays.stream(hex.split(PAIR_PATTERN))
            .map(convertStringToByte())
            .collect(Collectors.toList());

    return new String(ArrayUtils.toPrimitive(
            list.toArray(new Byte[list.size()])),
            StandardCharsets.ISO_8859_1);
}

private static Function<String, Byte> convertStringToByte() {
    return s -> Integer.valueOf(Integer.parseInt(s, 16))
        .byteValue();
}

It’s a hex-to-string converter. Perhaps one from a library would be more efficient. Perhaps this one is a good idea as it uses the encoding we want and so on.

The question about the above, is how frugal is this!?

Not Very…

The above is a shopping list of slow or memory wasteful techniques. We’re lured into these by the way the APIs and Java type system let us use them. It’s worth using this innocent example to learn some lessons on this, though. Here’s the code annotated with all the disproportionate resource usage in it:

// splitting a string with a regex and when you just want
// pairs is probably overkill, compared with repeated 
// use of substring
private static final String PAIR_PATTERN = "(?<=\\G.{2})";

public static String hexToString(String hex) {
    if (StringUtils.isBlank(hex) || 
          hex.length() % 2 != 0) {
        throw new RuntimeException("Invalid input");
    }

    // we're using streaming and a variable-sized list
    // when we know the target size - variable sized 
    // structures come with overheads
    List<Byte> list = Arrays.stream(hex.split(PAIR_PATTERN))
            // we're mapping using a function that 
            // creates a function not a huge overhead,
            // but a bit of a surprise
            .map(convertStringToByte())
            .collect(Collectors.toList());

    // this line is the shocker
    // the new Byte[list.size()] is a misuse of the API
    // which only needs ANY byte array, as it will create
    // its own to return - so there's a whole wasted array
    // the array itself being of the wrong type - Byte
    // rather than byte, so it's immediately replaced by
    // another array with the unboxed values
    return new String(ArrayUtils.toPrimitive(
            list.toArray(new Byte[list.size()])),
            StandardCharsets.ISO_8859_1);
}

private static Function<String, Byte> convertStringToByte() {
    // the boxing of the integer only to 
    // convert it to a Byte is an
    // extra object and operation
    return s -> Integer.valueOf(Integer.parseInt(s, 16))
        .byteValue();
}

An Alternative

Seriously… use a library. Though:

public static String hexToString(String hex) {
     if (StringUtils.isBlank(hex) || 
              hex.length() % 2 != 0) {
         throw new RuntimeException("Invalid input");
     }

     byte[] hexToBytes = new byte[hex / 2];

     int writeIndex = 0;
     for (int i = 0; i < hex.length(); i += 2) {
         hexToBytes[writeIndex++] = 
             stringToByte(hex.subString(i, i + 2));
     }

     return new String(hexToBytes, 
         StandardCharsets.ISO_8859_1);
}

private static byte stringToByte(String hex) {
    return (byte)(Integer.parseInt(hex, 16));
}

Would also work. Let’s assume that the overhead of calling stringToByte is somehow automagically dealt with by some smart runtime inlining.

Where this excels over the alternatives is that it allocates a fixed size buffer and reuses it. The way we used to in C++!

Conclusion

We need to use the smallest amount of resource we can. We can probably still rely on computers are so fast it’s cheaper not to over-optimise as a general rule, preferring readable code to many hours of painstaking optimisation.

But, if you develop a solid understanding and feel for wasteful resource usage, you can find clear algorithms that do things with less, and are better for your overall performance/cost.

Nod To The Developers

To anyone who has written code like the above, with the best of intentions, especially the original authors of this specific code, don’t look at this as a criticism of what is working code. Look instead, at the opportunity to understand the cost of innocuous techniques, and the availability of more frugal alternatives.

Those computers aren’t getting any bigger!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s