I do it with 21 (0x15) bytes on the x86. Just to provide a number, for now.
Jon
I do it with 21 (0x15) bytes on the x86. Just to provide a number, for now.
Jon
By the way, 12 instructions on the x86 and this assuming that the values are passed _on a software stack_ and I take the trouble to deal with it. You can cut that to 10, otherwise.
Jon
In case it isn't clear to others, though, both Isaac's VS.NET example and the one I am imagining assume parameters passed on the x86 stack. This isn't always a necessary approach, as compilers may certainly define the case where a certain small number of parameters can be passed in registers and only spilled to memory if addresses need to be taken. However, I am comparing apples to apples with the 21 byte figure here. Both Isaac's and my example assume parameters passed on the stack. My code would be shorter still, otherwise.
Jon
Yup that is correct. By default C compilers on the x86 use the STDCALL convention I believe. In this case the parameters are pushed on the stack in the opposite order they appear on the C func prototype.
There is a FASTCALL convention which I believe uses registers. I might try it out on the compiler and see what it does. I will post again if I get anything different.
-Isaac
; _a$ = ecx ; _b$ = edx ; Line 276 mov eax, ecx ; Line 299 test eax, eax jne SHORT $L1047 test edx, edx jne SHORT $L1054 ; Line 300 mov eax, 1 ; Line 320 ret 0 $L1047: ; Line 303 test edx, edx ; Line 304 je SHORT $L1046 ; Line 309 cmp eax, edx je SHORT $L1054 $L1096: ; Line 311 jae SHORT $L1055 ; Line 312 sub edx, eax ; Line 313 jmp SHORT $L1056 $L1055: ; Line 314 sub eax, edx $L1056: ; Line 309 cmp eax, edx jne SHORT $L1096 $L1054: ; Line 318 mov eax, edx $L1046: ; Line 320 ret 0
I think they could have used a conditional move instruction that later CPU's have. I set the target CPU to the Pentium II/III to see if it will use it but it didnt. I think its safe to say that it is still longer than yours.
-Isaac
Yes, that is pretty much what you'd expect from a decent compiler. It should be able to move the returns up automatically as you did you hand though. Did you also change the final "return b" into "return a", or did GCC do that optimization? Pretty good if it did.
For minimum size you can do better by removing most of the special cases and always using the main loop. The special cases can either be handled at the beginning, or at the end:
unsigned int gcd1 (unsigned int a, unsigned int b) { if (a == 0 && (a = b) == 0) a++; do { if (a > b) a -= b, b += a; b -= a; } while (b != 0); return a; }
unsigned int gcd2 (unsigned int a, unsigned int b) { do { if (a
I'll try and find some time to do it. The topology I've used before may change, depending on what I see and think about. Not sure if it will be today. But I will put some time into it.
Meanwhile, I could post the x86 version to show it there. Actually a number of them.
Jon
Here's GCC's Thumb version, somewhat longer than yours.
Can you produce the GCC version for the x86? Sorry to ask twice, but I'm interested and it would take me less time to compare, for now.
Jon
Yes - I can, but it takes some time. The GCC on a Mac does not produce x86 code, I have to switch computers.
That's a left shift by 1, and some byte shuffling.
To be honest, I'd probably have skipped the eor trick and rather used a few more registers unless I needed particularly optimised code, but I'd definitely make use of the "swap" instruction.
I've had cases when I needed a few extra bytes of code to fix a bug or add a feature, and it's taken ages to find a good way to compact the code to save those few bytes code space or bits of ram. Of course, it would be much easier to change microcontroller (and probably programming language), but sometimes you have to squeeze just that little bit more out of an existing system.
gcc has built-in functions for that. In particular, the function long __builtin_expect(long exp, long c) returns exp, but tells the compiler that "exp" will probably be "c". So you can write something like:
if (__builtin_expect(x > 0, 1)) { // Do this for (x > 0), which is the normal case } else { // The less usual case }
Of course, compilers for targets that support static branch prediction and other complications can figure out common cases automatically (such as in most loops).
I have never tried __builtin_expect in practice - on most small micros, knowing the most frequent path of a conditional is seldom useful, and gcc does not support the PIC.
Hi guys,
I have the GCC code output here. I compiled it using computers at school (Fedora Core 5 w/ GCC 4.1.0):
08048360 : 8048360: 55 push %ebp 8048361: 89 e5 mov %esp,%ebp 8048363: 8b 55 08 mov 0x8(%ebp),%edx 8048366: 8b 45 0c mov 0xc(%ebp),%eax 8048369: 89 d1 mov %edx,%ecx 804836b: 09 c1 or %eax,%ecx 804836d: 74 25 je 8048394 804836f: 85 c0 test %eax,%eax 8048371: 74 15 je 8048388 8048373: 85 d2 test %edx,%edx 8048375: 74 26 je 804839d 8048377: 39 c2 cmp %eax,%edx 8048379: 74 0d je 8048388 804837b: 39 c2 cmp %eax,%edx 804837d: 8d 76 00 lea 0x0(%esi),%esi 8048380: 73 0e jae 8048390 8048382: 29 d0 sub %edx,%eax 8048384: 39 c2 cmp %eax,%edx 8048386: 75 f3 jne 804837b 8048388: 5d pop %ebp 8048389: 89 d0 mov %edx,%eax 804838b: c3 ret 804838c: 8d 74 26 00 lea 0x0(%esi),%esi 8048390: 29 c2 sub %eax,%edx 8048392: eb e3 jmp 8048377 8048394: 5d pop %ebp 8048395: ba 01 00 00 00 mov $0x1,%edx 804839a: 89 d0 mov %edx,%eax 804839c: c3 ret 804839d: 5d pop %ebp 804839e: 89 c2 mov %eax,%edx 80483a0: 89 d0 mov %edx,%eax 80483a2: c3 retKeep in mind that this deviates from the standard Intel syntax ,moves to the 68000 kind of syntax, in that the destination register now the
2nd parameter instead of the 1st. I used the O2 switch for optimization. Hope this helps. Seems like VS .NET did a better job.-Isaac
Here's it. This seems to be somewhat shorter than the competing compilation of Isaac.
----
5 gcd: 6 0000 55 pushl %ebp 7 0001 B9010000 movl $1, %ecx 7 00 8 0006 89E5 movl %esp, %ebp 9 0008 8B5508 movl 8(%ebp), %edx 10 000b 8B450C movl 12(%ebp), %eax 11 000e 85D2 testl %edx, %edx 12 0010 7402 je .L14 13 0012 89D1 movl %edx, %ecx 14 .L14: 15 0014 85C0 testl %eax, %eax 16 0016 7418 je .L1 17 0018 85D2 testl %edx, %edx 18 001a 7412 je .L3 19 001c 39C2 cmpl %eax, %edx 20 001e 740E je .L3 21 .L12: 22 0020 39C2 cmpl %eax, %edx 23 0022 7304 jae .L10 24 0024 29D0 subl %edx, %eax 25 0026 EB02 jmp .L7 26 .L10: 27 0028 29C2 subl %eax, %edx 28 .L7: 29 002a 39C2 cmpl %eax, %edx 30 002c 75F2 jne .L12 31 .L3: 32 002e 89C1 movl %eax, %ecx 33 .L1: 34 0030 5D popl %ebp 35 0031 89C8 movl %ecx, %eax 36 0033 C3 ret----
When reading the code, please note that the GNU assembler shows the command arguments in different order than the original Intel spec: the first argument is source and second argument is destination.
movl $1,%ecx
loads the constant 0x00000001 to register ECX.
Tauno Voipio tauno voipio (at) iki fi
What do you get when optimizing for size (GCC and VS)? Optimizing for speed on x86 can produce rather verbose code.
-a
Did you use -fomit-frame-pointer? It adds 5 redundant instructions, or 20% overhead. It's this kind of thing that gives compilers a bad name...
Wilco
I know Paul Hiesh has a 11 instruction version, but it's possible to get it down to 10. I guess that is the optimum.
If you want to go smaller still you need to change the algorithm - making gcd(0,0) return 0 saves a further 2 instructions.
Wilco
OK - here's without frame pointer and optimized for size (-Os):
----
5 gcd: 6 0000 8B542404 movl 4(%esp), %edx 7 0004 B9010000 movl $1, %ecx 7 00 8 0009 8B442408 movl 8(%esp), %eax 9 000d 85D2 testl %edx, %edx 10 000f 7402 je .L14 11 0011 89D1 movl %edx, %ecx 12 .L14: 13 0013 85C0 testl %eax, %eax 14 0015 7418 je .L1 15 0017 85D2 testl %edx, %edx 16 0019 7412 je .L3 17 001b 39C2 cmpl %eax, %edx 18 001d 740E je .L3 19 .L12: 20 001f 39C2 cmpl %eax, %edx 21 0021 7304 jae .L10 22 0023 29D0 subl %edx, %eax 23 0025 EB02 jmp .L7 24 .L10: 25 0027 29C2 subl %eax, %edx 26 .L7: 27 0029 39C2 cmpl %eax, %edx 28 002b 75F2 jne .L12 29 .L3: 30 002d 89C1 movl %eax, %ecx 31 .L1: 32 002f 89C8 movl %ecx, %eax 33 0031 C3 ret----
The listing was produced with
gcc -c -Os -fomit-frame-pointer -Wa,-ahlms=gcd.lst gcd.c
and editing the directive and empty lines off the listing.
---
Here, an assembler programmer could shorten the code:
- swap %eax and %ecx in the whole module, so the last movl can be left out,
- substitute xchgl %eax,%edx for line 22 and leave lines 23, 24 and 27 out,
- line 30 can use xchgl %eax,%ecx instead, for a saving of 1 byte.
--
Tauno Voipio tauno voipio (at) iki fi
Here is the output of VS .NET optimized for size. I will test for GCC a bit later. Shorter code:
00000 8b 4c 24 04 mov ecx, DWORD PTR _a$[esp-4] 00004 85 c9 test ecx, ecx 00006 8b 44 24 08 mov eax, DWORD PTR _b$[esp-4] 0000a 75 06 jne SHORT $L1429 0000c 85 c0 test eax, eax 0000e 75 15 jne SHORT $L1428 ; Line 607 00010 40 inc eax ; Line 627 00011 c3 ret 0 $L1429: ; Line 610 00012 85 c0 test eax, eax 00014 75 0b jne SHORT $L1538 ; Line 611 00016 8b c1 mov eax, ecx ; Line 627 00018 c3 ret 0 $L1539: ; Line 618 00019 73 04 jae SHORT $L1437 ; Line 619 0001b 2b c1 sub eax, ecx ; Line 620 0001d eb 02 jmp SHORT $L1538 $L1437: ; Line 621 0001f 2b c8 sub ecx, eax $L1538: ; Line 616 00021 3b c8 cmp ecx, eax 00023 75 f4 jne SHORT $L1539 $L1428: ; Line 627 00025 c3 ret 0Again not to confuse anyone, this is Intel syntax (dest, src)
-Isaac
After this discussion of what projects fit into 1K using a HLL, on as Microcontroller, here is the newest devices pitched at
12V battery Monitoring. No, not a tiny13... ( remember when a LIN Slave was a 1K PIC... ? )So, as you can see, it will not matter if your C code is 1.2K, and your ASM code might be 890 bytes....!!
At first glance they do not look cheap, but the mention of HV IO and "The new devices support a maximum power supply of 33 V with all specifications applying over the range of 3.5 V to 18 V " - have me wondering if that means what it says ?
-jg
Have something to add? Share your thoughts — no account required.
Ask the community — no account required