8 ms·
Both the RISC-V and Thumb code here are no longer recursive fib; GCC has quite correctly transformed one of the recursions into a normal nonrecursive loop by ex
by kragen 1y ago
Both the RISC-V and Thumb code here are no longer recursive fib; GCC has quite correctly transformed one of the recursions into a normal nonrecursive loop by exploiting the commutative and associative properties of integer addition. I infer that perhaps the same thing may be true of the MSP430 code from the presence of only a single call instruction, but I don't see the loop; perhaps the br to 0 will be relinked to go to .L3 before execution?
I haven't seen MSP430 code before, due to my somewhat embarrassing and unfair distaste for TI as a company, and I agree that it is a very nice encoding indeed!
If you insist that the code contain at least two recursive calls, two subtractions, an addition, and a conditional for the base case, even Thumb2 and RVC no longer look so competitive with stack machines. If not, there is no particular reason to leave one of the recursions in; you may as well optimize that out too, thus reducing an exponential-time algorithm to a linear-time one (or linear to logarithmic, if the parameter is the number of bits of input.) This also makes it smaller, something like (untested):
mov r1, #1
mov r2, #1
1: subs r0, #1
itt lo
movlo r0, r1
bxlo lr
mov r3, r2
adds r2, r1
mov r1, r3
b 1b
I think that's maybe 22 bytes?
But I don't think that tells us much about the relative densities of stack code, register code, accumulator code, and belt code, because it's a very different mix of operations.
- kragen 1y agoThat's 24 bytes because gas is using Thumb2 mov.w instructions to initialize r1 and r2. It took me a long time to figure out that the problem was mov vs. movs; this is the correct 20-byte version: .cpu cortex-m4 .thumb .syntax unified .thumb_func .globl fib fib: movs r1, #1 movs r2, #1 1: subs r0, #1 itt lo movlo r0, r1 bxlo lr mov r3, r2 adds r2, r1 mov r1, r3 b 1b I swear, I keep making the same n00b mistakes, year after year! It might be possible to get it down to 18 bytes, but I don't see how.
- brucehoult 1y ago> Both the RISC-V and Thumb code here are no longer recursive fib If the programmer wrote a doubly-recursive fib() and the compiler transformed one to tail-recursion, then that's a doubly-recursive fib() as far as I'm concerned. If you don't want that to happen then use a benchmark that isn't so easily optimised, such as maybe Ackermann. Note that this optimisation doesn't change the calculation performed or the execution time from being exponential, it merely saves some stack manipulation. Having access to good compilers is part of the attraction of using a real ISA instead of a made-up one. Changing the algorithm to an iterative linear-time form one is not an optimisation I'd expect a compiler to do.
- kragen 1y agoI agree, but what we're trying to look at here is how space-efficient the ISA is at encoding computational operations. If one of the example programs encodes a totally different set of computational operations, it stops being an informative comparison. But, you may reasonably object, this is handwaving! If a subroutine call is a "computational operation", why isn't duplicating a stack item or storing a variable? The stack code is cheating by not storing all its temporaries into variables! And I think the answer is that there's a bit of a slippery slope there, where more extensive program transformations give us less interesting answers. I would argue that transformations like not allocating registers to temporaries (or, say, subroutine arguments) are more broadly applicable than the transformation you're exhibiting, where the compiler has changed a non-tail call to dumbfib, followed by an addition, into an addition followed by a tail-recursive call implemented as a jump. That depends on being able to reassociate the entire chain of additions through the entire call chain, which is something I wouldn't have expected a compiler to be able to do. I agree that it's more interesting to see benchmarks that aren't so easily gamed by optimizers, which is why in https://dernocua.github.io/notes/c-stack-bytecode.html https://dernocua.github.io/notes/c-stack-bytecode.html I selected a bunch of small C and Pascal subroutines quasi-randomly and hand-compiled them into different candidate stack bytecode representations. I'm not sure that Ackermann or Tak would be as interesting.
- brucehoult 1y agoI have my own toy benchmark too :-) https://hoult.org/primes.txt https://hoult.org/primes.txt
- kragen 1y agoThat seems like a significantly better microbenchmark than dumbfib!
- brucehoult 1y agoThe main weakness is -- opposite of dumbfib -- it has no function calls at all! But I find it correlates well with other CPU core & L1 cache/SRAM benchmarks such as Dhrystone and Coremark. The Embench guys added a cut down version -- smaller arrays, shorter execution time [1] to their benchmark suite a couple of year back. [1] baseline ~4s on typical microcontrollers ... I'd started at ~5s on Ivy Bridge, 10-30s on the Arm boards I had back then