Introduction: to write assembler or not to write assembler?

Many programmers still associate assembler with a difficult, low-level kind of programming. Most of them also think it is incomprehensible and impossible to master. In reality, things are not that bad. It is possible to learn how to write good assembly code without being a genius. On the other hand, don't think a few lessons in assembly will leave you producing faster code than the average Delphi/Pascal equivalent. The reason is that when you write Delphi code, you can in fact rely on an efficient and clever assembler programmer: the Delphi compiler. Overall, the code produced by it is efficient and fast enough for the average application.

The first thing to consider when you are experiencing bottlenecks in your programmes, is to review your Pascal code, rather than immediately taking recourse to assembler. Writing it in assembler is not going to turn a bad algorithm or a bad approach into a good one! Robert Lee had a whole series of articles on the topic of optimising Delphi code, but unfortunately the site has gone offline.

Most programmers believe that writing code in assembler by definition means it's fast and in fact assume that code directly written in assembler is by definition faster than compiled Delphi/Pascal code. That is certainly not the case. Badly written assembler routines will perform on an inferior level and can even cause strange bugs and problems in your application. Unless you are at least as good an assembly coder as the Delphi compiler, you'll find that Pascal code will more often than not beat you to it.

There is however a good case for writing assembler code. Delphi's (Pascal) language is a general programming language and certain specialised tasks could be done better in assembler. Also, the Delphi compiler is starting to show its age these days. It will not take advantage of the new features of modern CPUs and still generate code that basically is optimised for the early generations of Intel processors based on the P6 core. As such, there are sometimes good reasons to write parts of your Delphi applications in assembler.

It is not the goal of these articles to teach you the basics of assembler programming. There are already information resources out there on the general topic of assembler programming. See "Appendix A: Further reading" to find some pointers to relevant material.

If you believe you need assembler to improve the execution speed of your application, you should first use a profiler to determine where the bottlenecks are. Once you have identified these, you need to carefully examine your code. Take a step back and look at the general structure and algorithms. Often, you can get much better performance by revising the algorithm, rather than just throwing in some assembler. On the other hand, for some particular cases of bit twiddling, assembler might be the better and even simpler choice.

Once you have decided that assembler is actually needed, you should take enough time to draw up a plan for your code and design the algorithm. Only when you have a clear idea of what you want to do and how you will be implementing it, you can start coding. If you don't think about these issues, you'll end up with spaghetti code, convoluted statements and an unmaintainable program. Not to mention the possibility of introducing nasty bugs!

During the implementation phase, there are several general guidelines you should follow. The first important rule is: keep your routines as short as possible. Assembler should be used for some real core activity in which performance and simplicity are important. So, in most cases, you can manage with fairly short and specialised routines. If you see that you have plenty of long pieces of assembler in your project, you are probably over-enthusiastic about it.

Secondly: keep the routines readable by commenting them in a meaningful way. Because of the basic linear nature of assembly code, the individual statements are quite easy to read by themselves. Comments are needed to clarify how you want to implement the algorithm, so that a third party will immediately understand what is going on. Don't add comments just for the sake of having commentary. Your comments need to add valuable information for the readers of your code. In other words, do not do anything like this:

inc edx {increase edx}

These kinds of comments are totally useless, since the instruction itself is making it blatantly clear that you are incrementing the edx register. Comments should indicate the inner workings of the algorithm, not repeat what the mnemonics already tell you. So, the above could for instance be replaced by something like this:

inc edx {go to the next element in our product table}

Thirdly: avoid the use of slow instructions. In general, the simple instructions are favourable over the complex opcodes, since the latter are implemented in microcode. An extensive manual on this topic is written by Agner Fog and you will find a link to it in Appendix A. Try to read through it multiple times, paragraph by paragraph, so that you fully understand the general idea. Which does not mean you'll be able to apply all of it at once. But it will give you a good overall understanding of the process of writing optimal code.

The last general advice is predictable: test your assembler routines thoroughly. For routines written in assembler, the compiler will produce substantially less warnings and error messages, especially with regard to the logical construction of your algorithm. Unused variables and wrong use of pointers will not be detected as easily as in Pascal code. So, prepare for intensive testing and debugging of your code. It will take more time and effort when compared to debugging regular Pascal code! And that is yet another reason to keep these routines as short and readable as possible.

If after this introduction, you have decided you still want to go ahead, reading this article is probably the best first step. It is written to be as generic as possible, meaning that most of the information herein covers basm (which simply stands for built-in assembler) for all current 32-bit versions of Delphi starting with version 3. Most of it will equally be true for Delphi 2, but that version is now really outdated. Also, there are some memory leak problems with Delphi 2 and in general I would advise to use at least version 3.02 or above. If you have Delphi 3, 4, 5, 6 or 7, all information in this series will apply. I am in the process of checking everything against Delphi 2006. If there are version-specific differences, I will try to specifically indicate them. I do not intend to verify this article against Delphi 8 or Delphi 2005, the least said about those abominations, the better...

More material will be added to this article over time. In the mean time, I hope you will find the article both enjoyable and helpful.

Next part: Chapter 1: General Context of Assembler Code
Table of Contents


This page was last updated 2 January 2007.