Strategy Optimization ..
Size_Optimization and Speed_Optimization should be rejected from the Asm32 programming area. They are ridiculous, of no use in most cases, and hardly kill Readability, which is the most important thing to be preserved, in programming.
The superiority of Assembly over any HLL does not rely on Code Size and speed optimization, but on Strategy optimization and on a Specific Programming style (as opposed to a Modular Programming style). Yes, Strategy is particularly reserved to Assembly programming, for the very simple reason that HLL programmers cannot know what they are doing. Its as simple as that.
If giving many examples of Strategy Optimization is difficult, while giving counter examples is easy. You can see them everyday when running C Applications. Just take a look at the time Acrobat-Reader takes to find occurrences of one searched word, and when you will have finished laughing and drying your tears, ask yourself ''How is this possible???''. Well, the answer is that this requires sophisticated programming methods, which base is Modular Programming.
Modular Programming is the ensured way for producing fat and slow Code, because, to turn a Routine reusable, and flexible enough, you have to first make it able to hold all expected (and unexpected) possibilities. There are few reasons why an Assembly written Application should be faster than a C draft, if you use Modular Programing in Asm. Better to use Visual Basic. The worst aspect of Modular programming, is that, once a Module is available, you will continously be tempted to make a simple call to it, in any circumstance, no matter whether this way is accurate or not: It does the expected action. Period.
Good Asm Programing is re-inventing the wheel, or, at least, always reviewing how the wheel turns. This is good and desirable. Each time you re-invent a wheel, the wheel will be better. If you do not want to work again and again upon one given computing routine, just Copy/Paste from a previous work (with RosAsm, use the Clip feature, which is a friendly Source Level Librarian). Doing it this way will always give you full control of what you are really doing, when reusing and adapting some Template.
A frequently encountered example of Code reuse by Modular Programing is some Procedure for outputting a flow of Bytes under the form of Text Hexa. Inside RosAsm Source, I have to implement many those outputs. Each time I have to do it, I re-write, because there is no such a thing in the real world as a general purpose Hexa outputting: In one case, you may want leading zeros, in another one, you may want either Bytes or bigger formating. In one other case, the source format is known, whereas in another one it would not be... Is the outputted Text under Raw-aligned format, or not?... and so on... and so on... If such a basic and utterly simple concept as having a simple Module for outputting the simple Hexa Text form of an Number is 100% stupid and ineffective, what about more complex Modules ???...
Some examples of Strategy Optimization
Length of Text
Computing the length of short and middle length Texts does not require any optimization. Processors are fast enough. Applied upon really huge Data, this computation may slow down processes, from the final users point of view. In such cases, the answer is quite simple: No optimization trick-and-tip will ever make any difference for the human perception of time . The only way is to not run this computation: Example, when you load a text File in Memory, the Reading Function provides you with the Length of the File. Simply store this Value in a Variable, and increase / decrease it at each Data modification. This takes no time.
Sorting Tables
One example of a problem encountered was when developing the RosAsm Disassembler: When disassembling, each time an Instruction includes an evocation of some Code 'Symbol' (any Code Address, in fact), we have to store this pointer somewhere, in order to later write the targeted Label in the final Source output. In the very first version of the Disassembler, I simply stored those Pointers in a Table, and then sorted them in ascendant order between the two disassembling Passes. Result: Most of the time was then spent at sorting!!!
Solution: 'Fast Sort Algo???' > 'Ridiculous!!!'. After this first release, I declared a Check Table of the same length, in Bytes, as the code itself. Then each time a Code evocation was encountered, I have simply set to 1 the byte at the same displacement in the Check Table. This indice Table takes no time, neither at disassembling, nor at source building. Just a simple inside adjustment, and it is done. The total amount of time for a complete disassembly was then divided by 3.
Avoiding Tables
Generally speaking, Tables, used for wide ranges of possible events, makes programming easier, but also slower. Searching one given value inside a set of Data that can not be ordered is a heavy time cost. Long flow of Cases Selections (If and friends, or others...), are much more killing to write, but much faster to run, because, in most cases, if not all, the selection(s) Cases tend to organize in some form of Selections Trees by themselves. This is the way I wrote the RosAsm Encoder and Disassembler: Zero Tables for the Encoder, one Table for the Disassembler. Results: When some Assemblers take 10 seconds, RosAsm takes 1 second; when other Disassemblers spend 1 minute, RosAsm spends 1 second.
Exchanging Pointers
One regular example given for Strategy is the pointers exchanging. Let us suppose you have two tables (same sizes), and you want to move all the Data from Table1 into Table2. (Of course, both Tables are dynamically attributed, and so, we have two Pointers-Variables to them). Instead of moving all the data, you just have to:
push D§Table1Pointer
push D§Table2pointer
pop D§Table1Pointer
pop D§Table2Pointer
and it's done. Or better with a Macro:
[Exchange | push #1 | push #2 | pop #1 | pop #2]
Exchange D§Table1Pointer D§Table2pointer
Splitting Tables
Once the Data, that an Application has to parse, grows in size, searching for a particular Item inside the huge Table(s) may dramatically slow down the overall Application processes. This is why you will find here and there so many various fast search Algos. Another very simple way is to organize your Tables Data in some kind of 'Sub-Tables', which are to be built by applying to the Data one or several Check and split criterium.
Let us take an example with a huge Table, which is a flow of zero ended Names followed by a numeric Value. Your Application Parser has to return the Value according to a given Name.
Sorting the Table Names in alphabetic order will allow the Parser to exit the search as soon as a 'DestinationName' will be found 'bigger' than the 'SourceName'. This is to say that the overall searches will be made twice as fast, on average. But this trick only applies in particular cases, when the sort time is shorter than the usage time, and when the Search Exit does not have to run into an error case and stop the user actions.
A much better way is to implement one other Pointers Table to each new first Char into the Name Table. I mean another Table of Pointers (Check-Table), the first one pointing to the first 'a-leading' Name, the second one pointing to the first 'b-leading' Name, and so on. Then, your search Algo speed will be multiplied by 26 and, instead of having to first entirely sort all of your strings in alphabetic order (which may take some time...), you will just have to sort your Strings Table on the first Character of each Name (which is much faster), if half of the Table user does not consume more time than the sort does.
I used this method for several RosAsm Assembler Parsers, working upon Source symbols sorted by sizes, (alphabetically sorted or not in the 'Sub-Table' -depending on the comparison of time required for alphabetically parsing the Data and the overall usage time-).
CheckSums Search
When having to search, for example, for a Value corresponding to some KeyWord, the search of the KeyWords references, in a Table, may severely slow down an Application, if the List of KeyWords is huge. Instead of storing the KeyWords List in a Table, you may, first, build a CheckSums Table: A Table of the CheckSums of all the KeyWords, and build a corresponding Table for the Values. Then, you sort the CheckSums Table in ascending order. When sorting, at the same time you modify the position of a CheckSum, in the CheckSum Table, you modify also the corresponding Value position, in the Value Table (associated Sorts).
Once done, pointing out a Value corresponding to a Name becomes quite simple, and blazing fast: You compute the CheckSum of the Name, and, when you have this Checksum, you search for it, in the CheckSum Table... with a 2n Search. When the position of the CheckSum is pointed out, in the CheckSum Table, the Value is at the same displacement, in the Value Table. Really killing fast.
What is a 2n Search:
esi > End of Table // ecx = Size of Table // eax = Searched CheckSum
Value at esi < eax >>> shl ecx 1 | sub esi ecx
Value at esi > eax >>> shl ecx 1 | add esi ecx
Loop upper two operations until found or ecx = 0
Note: If the Table size can not be a power of two, when ecx = 0, you also have to add a linear search upward or downward until smaller, or bigger, or out of Boundaries. Also, if the Table size may be power of two, you have to pad the empty part with 0-1, of course.
What is a CheckSum:
Any Algo computing a Number from a flow of Bytes. For the CheckSum Searches, you have to carefully choose an Algo providing uniformly distributed CheckSums (avoiding, as far as possible, producing the same CheckSums for two different Names -the Computing must have a verification security for this - ).
End
As a general rule, for the Assembly programmer, the answer to speed problems is quite simple: One task is not fast enough??? Simple: Do not do it!!! There is zero relationship between Ticks saved at Code level, on one hand, and the final user perception of time, on the other hand. The guys who save Ticks by Code level optimization are putting a sheet of paper on the floor, then they put their two feet on the paper and say: 'Hey! Look! I am closer to the moon'. Usually, saving 20 or 25% of run time by Code Level Optimization is considered a great and exceptional performance achievement. At the final user point of view, the cases when 20 or 25% of usage time may make some real difference are very exceptional, and, usually, if 10 Seconds are too long... 8 Seconds will also be too long!
Nevertheless, there are some valuable Code Level optimizations tricks that do not kill readability, that are cheap and that may seriously increase the overall speed:
All Data should be aligned on their own Boundary. This means that Words should be aligned on Words, dWords on dWords, qWords on qWords. This is no cost. When bad alignements are encountered, the Processors requires two Memory accesses instead of one.
Shift Instructions should be preferred for all Multiplication / Division of integers by powers of 2. This is not more difficult to read, requires zero extra Registers (as opposed to mul / div), and the execution is really much faster. See Multiplying Without MUL and IMUL in Machine_and_Arithmetic_Idioms.
LEA is very interesting for computing at once several Integer Math operations. See INTEGER MULTIPLY Paragraph in Speed_Optimization Chapter.
~~~~~~~