Mostly when I write code these days I write in assembly language on 8-bit systems. And I like it. That’s how sick I am.
I was working on a project and needed a small function written. I knew what it needed to do, but I wasn’t sure how to do it. My colleague did know how to do it, so I asked him to write it for me, figuring it would be easier to wrap my head around something I could see, and it would get done more quickly this way.
This (now former) colleague used to write gaming software and firmware. We’re not talking PS3 or XBox here, we’re talking the old stuff, a couple K (if that) of ROM and a handful of bytes of RAM with no performance to speak of animating games on video. These guys used to know not only every instruction of the CPU but the number of bytes and number of cycles every instruction took, what flags were affected for every operation, and so on. They practically lived inside the machines. I don’t know if he himself was that intense but he was at least very close, and worked with the guys who were.
So I got the function back and started studying it. The first thing I asked was, “What is ‘cs’?” cs was the name of a two-byte checksum used in virtually all our code (most projects were similar, so derived from each other). He was using cs and cs+1 in several places. “It’s a counter,” he said, “obviously” he didn’t quite say. “cs is a checksum. Why use that instead of creating a new variable?” “Well, it saved two bytes.”
I was incredulous. “Colleague,” said I, “this part has 256 bytes of RAM and we’re using about 120, less than half. Why on Earth did you need to save two bytes?”
He didn’t really have an answer. He’d never bothered to check; saving two bytes was as automatic as breathing to him. When I write code I ask myself, ‘Will this work, is it maintainable, what comments should I add, how should I name the labels, how does it fit in with the rest of the code, is it efficient’, stuff like that. When he writes code the only question he asks is ‘Can I save two bytes?’
Re-using variables that are out-of-scope is a time-honored tradition, necessary when your resources are extremely scarce, and fraught with danger. It is far too easy to think a variable is being used in two disjoint scopes when in fact the scopes overlap in unexpected ways. Doing it when it’s required is a necessary evil. Doing it when you have mostly-finished code that’s using less than half the RAM is inexcusable.
But that wasn’t his only crime. In assembler it’s very easy to attach multiple labels to the same address. Yes, part of the problem was re-using variables when it wasn’t necessary. But another part was using variable names that made no sense. To him, maintainable code is code he wrote. He knows how it works, therefore it’s obvious how it works, therefore it’s maintainable. But when I started to study the code I saw “cs” and thought “checksum” because that’s what that variable was. Even when I knew it was a counter it was hard for me to shift paradigms. What was worse was that he was using both bytes of cs as two different counters in that code, and they both had the same label with a different offset. Even given his passion for saving two bytes he should have added new label names to those bytes and used the related labels. (And added a comment that he was re-using cs.)
I created two appropriately-named variables and altered the code accordingly. Suddenly the code was quite simple to understand, given that I already knew what it was supposed to do. It went from opaque to clear that easily.
Books like Code Complete will point out that optimizing first is counter-productive. And they have a point.
- When you’re writing the code, you don’t really know where the bottlnecks are. Oh, you can make an educated guess, but it’s amazing how often those guesses are completely off the mark.
- It’s more important to get the code working first. Optimizing bad code just means it will be wrong with fewer resources, except the time you spent optimizing it. Now you have to fix the code, and chances are it will break the optimization you’ve performed. It may even get thrown out. And it’s even possible that you broke it by optimizing it!
- Any programmer with experience will know that a lot of their code will be radically changed or even thrown out before the project is finished. Even if it works perfectly as written, what’s needed may change.
So the lesson is: get it working first. Optimize later.
Which is great advice. But what if you’re working on a tiny processor with few resources? If the code doesn’t fit, it doesn’t matter if it’s right. It can’t work!
This is one of the places where general programming advice doesn’t quite fit in the embedded world. Mind you, it’s not wrong. It’s just not quite the black-and-white area it is when you have a PC with virtually unlimited resources.
So when you’re writing code for small processors you get into certain habits. Despite my outrage at my colleague’s mindless need to save resources we quite positively had more than enough of, I do understand it. It’s just that it should not have been mindless. He should have stopped to consider: I can save two bytes here. But do I need to? Should I? The answer being a resounding NO this time. …And had we been tight on RAM, then it might have been worth doing.
It’s a constant background hum, thinking about where to save a byte or two, what is the fastest way to do this, can I re-use the common part of this code by jumping into the middle, and so on. And sometimes I run out of something and have to optimize or find another way to do it.
There’s a funny psychological twist to that, too. I’ll be going along, resisting temptation to optimize as I write, and then run out of ROM. Then I’ll have to spend time optimizing so it will fit — and I’ll resent that time spent, feeling bitter about not having done the optimization in the first place, and if I had, see? I wouldn’t have to do this — completely forgetting the fact that I’m now only using time I’d previously saved by not optimizing prematurely.
Do I have any specific advice? I’m not really sure I do. The balance depends on a lot of factors, not the least of which is how much room do you have vs. how big is the project? Mostly I would be inclined to repeat what I’ve already said, some of which is really just channeling folks with more experience than I. Don’t automatically optimize from the start. Try to keep the code elegant. Think about it as you go, keep it in the back of your mind. Don’t be afraid to throw code away. Don’t be afraid to go back and tighten up or re-write code. Accept that if you can’t optimize the code to fit, maybe there just isn’t room for the functionality. Or maybe there is using the old techniques, but is it worth making code that’s impossible to maintain?
Generally the highest cost in software development is maintenance. Spend a bit of time up front to make the code clean and elegant and you’ll save time down the road.
Don’t save two bytes at the cost of clarity and robustness just because you can.
Hello from Russia!
Can I quote a post in your blog with the link to you?
Hello, Russia! You certainly may. Thank you for asking.
My name is Piter Jankovich. oOnly want to tell, that your blog is really cool
And want to ask you: is this blog your hobby?
P.S. Sorry for my bad english
It’s partly a hobby, partly a self-imposed obligation — I think it’s important for those with experience to share what we’ve learned with people trying to learn — partly an ego thing, which I think is true of all writing, and partly a way to get the attention of prospective employers. In a positive way, I should hope.