CompuServe Thread

#C and Assembler

10 messages in this thread
#31970From: Drew LucyJan 24, 1989 10:38 PM
Recently, a time critical loop motivated me to take a look (MC68000 User's Manual in hand) at the assembly source output of the Manx compiler for the first time. This is not intended to be critical of Manx. However, even to a novice, it is pretty easy to see places where the efficiency of the compiler output can be improved. I was able to decrease the execution time of the above mentioned loop by 25% by making just a handful of tweaks to the code. For most of the programs I write, this kind of tweaking probably isn't worth the effort and, I'm certainly not inclined to start writing entire programs in assembler. Once in a while however, it appears that the benifits of altering the compiler output or using in-line assembly code in your C functions can be dramatic. Examining the compiler output is also very educational. C gives us many alterative ways of performing the same task. The code generated by these alternatives can differ significantly in size and efficiency. Finally, looking at the compiler output is a good way, although perhaps not a great way, to begin learning 68000 assembly language. I was surprised that Manx's "optimizing" compiler doesn't detect divisions by powers of two. I'd always been led to believe that other compilers (MicroSoft, for example) did do this. Aztec C alway uses the CPU's divide instructions instead of shifting. Is this a reasonable thing to do? Does the 68000 do power of two divides as fast as it can shift? I'd like to hear some other people's thoughts on and experiences with mixing C and assembler.
#32043From: KEITH YOUNGJan 25, 1989 4:30 AM
Interesting… I just started about a month or so ago looking at the asm output of Manx as well… and optimizing certain time-critical loops, etc. In fact, as I read your message, it was as if I had written it :-). I've learned a lot about asm from it, but as you mentioned, a side-effect of looking at the output can teach you a great deal about how to write better C (for the Manx cmpiler anyway… and in general a lot of times). I don't have an answer on your dived-shift query, but as an aside… Manx's assembler is an optimizing assembler, you might get to know 'db' and/or 'sdb' and take a browse at what the 'final' output is (ie. disassemble it from mem). Btw, I now write some routines, take the output and re-write the loops and/or change register usage (re-writing the whole routine at times) then link it in seporately, but I don't use in-line asm (haven't found a need to yet). – Keith
#32332From: Drew LucyJan 27, 1989 2:32 AM
I believe that the Manx assembler only substitutes bsr for jsr and short branches for long branches where possible and removes unnecessary movem instructions. Frankly, that's not much. I was hoping that the compiler would be alittle smarter. The compiler seems to treat each C statement as if it existed in a vacuum. It never takes advantage of results obtained in previous statements unless you explicitly write the C code to do it. This was the most important lesson I learned. It is well worth storing difficult to calculate intermediate results if they will be used more than once. Even the values of multidimentional array elements fall into this category. It was interesting to see the effects that using register variables had. Here's a question for you, if you're using a register variable as an array index, should it be a UBYTE, a USHORT or a ULONG? Assume, of course, that a UBYTE can represent the range of the index.
#32058From: Fred HewettJan 25, 1989 8:55 AM
Your msg caught my attention because I write 68000 assembly language five days a week at work and only write C on my Amiga. Regarding the last question in your msg: No the 68000 (and 68010) do shifts much faster than divides, even divides by powers of two. (The 68020 has a barrel shifter to speed up shifts even more!) The exact timing of the divide instruction seems hard to predict, but I haven't seen times less about 110 CPU clocks. The timing of the shift instructions is easily computed: 2 clocks per bit plus 6 if byte or word; 2 more clocks if long. For small shifts the difference between shift and divide can be a factor of ten or more. In general, I've noticed that C compilers produce pretty lousy code, at least in comparison with the compilers fr, say, FORTRAN that I've seem on mainframes. In part, this is because C is so low-level: if you want to do a shift, you can use the shift operator. However, I don't see much use of even simple optimizations I learned in college, like strength reduction, moving invariant code out of loops, removing dead code, to say nothing of global flow optimization. All that notwithstanding, it's probably a bad idea to mix assembly language and C unless the CPU speed is critical. You reduce readability and portability. Even if you halve CPU execution time, you probably reduce the wall-clock time by much less, unless you're doing ray-tracing or some such.
#32334From: Drew LucyJan 27, 1989 2:33 AM
Thanks for the info on division vs shifting. As other messages point out, there is, of course, a problem with negative numbers. However, many times you are only dealing with positive numbers. The compiler could "easily" recognize unsigned variables. People alway point to FORTRAN compilers as examples of efficient code generators. They should be. They've been tweaking them for over 20 years. 🙂 I agree that the C philosophy may be to let the programmer select the most efficient method to solve a problem. I just wasn't aware of how seriously they ment it. And as I said, I thought MicroSotf C did do optimizations like replacing multiplication and division by shifting where possible. Although I've been happy with Aztec C for over there years, it's starting to look a little lame compared to Lattice 5.0.
#32102From: Bob RakoskyJan 25, 1989 7:02 PM
Since I don't have/use Manx, I don't have the "luxury" of in-line asm, nor or easily tweaking the generated assembly code before assembling it. However, even if I had this ability, I wouldn't use it. I really think it's a bad habit to develop and sloppy technique. I have no problem with analyzing the way the compiler generates code, and learning to use C code constructs that "help" the compiler to optimize. I also have no problem with mixing C code with assembler code – I do it frequently. But the assembler modules I use _belong_ in a separate file from the C code. This assists the maintainability and portability of the code. Functions within a program should be either ALL C or ALL asm – never mixed. Inline assembler allows you to violate this (I know that you can have the entire function as inline assembler code, but it is still easier to maintain if it is kept in a separate file). As to taking the assembler output from the compiler and "tweaking" the code (hand-optimizing) – that works fine until you have to make a change to the logic in that module. Which version do you change? How do you remember which "tweaks" you made to the previous version? Which are still relevant? Major maintenance hassles! On top of which, if I have determined that there is a performance requirement for a certain module to be coded in assembler, I can almost always do a much better job of optimization if I simply code that function from the start in assembler. There's a price that you pay for using the higher-level language. You're just trying to short-change it, while I'm opting not to pay it. It'll cost you more now (performance-wise), and the time you save now (by letting the compiler do the preliminary leg-work) will be spent later three-fold when you have to modify your program. All code, unless it's a quick-'n'-dirty, throwaway, will need to be changed eventually — that's the nature of software. But I really don't have any opinions on this subject. <grin>
#32166From: Vic WagnerJan 26, 1989 4:09 AM
arithmetic right shifts are NOT divides by powers of two for negative numbers.
#32216From: Richard Rae/SYSOPJan 26, 1989 5:52 PM
"Arithmetic right shifts are NOT divides by powers of two for negative numbers." Am I missing something here? Is there something peculiar to the 68000 that I don't know about? (Probably… I've done VERY little 68000 assembly.) A negative number is nominally 2's complement notation with the MSB (sign bit) lit. An ASR shifts the byte/word right N bits, replicating the MSB as the shift occurs. In any assembly language I've ever used and any reference I've ever seen, this is a divide by 2^N. What do you know that I don't?
#32244From: Vic WagnerJan 26, 1989 7:36 PM
Yup, you're missing something here. Not peculiar to the 68000, peculiar to the way 2's complement numbers work. As this was one of the exercises the C class just did, you may want to drop by some Thursday night, Room 3, 01PM EST (7PM PST) just to find out what we know (and you do to, but seem to have forgotten). See ya soon.
#32336From: Drew LucyJan 27, 1989 2:39 AM
Your question made me realize that I didn't know the answer for certain. I had always just intuitively figured that there was something fishy about shifting negative numbers. So after scratching ones and zeros on a pad for a while, here's the scoop. Shifting negative numbers works fine as long as the number is a multiple of the "divisor". In other words, as long as you only shift out zeros. If the number isn't a multiple, then trucating produces different results. 5 shifted right 1 bit is 2. -5 shifted right one place is -3. I guess you could still call this division by 2. However, it's not associative. (-1 * n) / 2 doesn't equal -1 * (n / 2). Still, sometimes you might not care.