swap() on x86 platforms.

Iain McClatchie iainmcc@ix.netcom.com
Mon Jan 5 19:22:00 GMT 1998


Pal-Kristian> Testing on a Pentium revealed that using xchgl for
Pal-Kristian> swapping numbers is not the best way!
Pal-Kristian> Results: (Don't count on them!)

Pal-Kristian> Normal   = ~5.5 secs (+/- 0.1)
Pal-Kristian> XOR      = ~8.5 secs (+/- 0.1)

Here I've included some timings on a 150MHz PPro, egcs-2.91.02 971216
vs gcc-2.7.2.  I translated the code into C for comparison as well.

gcc-2.7.2:
in C++
	-O	-O2	-O3	-O6
swap1:  4.22	4.93	4.93	4.92
swap2:	8.67	7.11	7.10	7.11
swap3:	5.39	5.36	5.38	5.40
swap4:	7.06	7.07	7.04	7.06

in C
swap1:	4.93	4.22	4.22	4.24
swap2:	9.63	7.76	7.74	7.77

egcs-2.91.02 971216:
in C++:
swap1:	4.95	4.23	4.24	4.23
swap2:	9.73	7.76	7.74	7.79
swap3:	5.53	4.97	4.99	4.93
swap4:	7.10	7.03	7.06	7.05

in C
swap1:	3.59	4.23	4.23	4.23
swap2:	9.87	6.40	6.34	6.34

Obviously, swap1 is the way to go, which is good; it's also the way
everybody does it right now in existing code.  -O3 and -O6 don't do
anything to this kernel.

C is faster than C++.  It's a little interesting to note the
performance loss in C going from -O to -O2.  In an effort to discover
the problem, I extracted the inner loops from the C and C++ versions
of swap1 at -O and -O2:

C++:
	-O: 7 cycles/loop			-O2: 6 cycles/loop
.L60:					.L60:
        cmpl $10238,%esi		        cmpl $10238,%esi
        jg .L58				        jg .L58
        leal -40960(%ebp,%esi,4),%eax	        leal
-40956(%ebp,%esi,4),%eax
        leal -40956(%ebp,%esi,4),%ecx	        movl (%ebx),%ecx
        movl (%eax),%ebx		        movl (%eax),%edx
        movl (%ecx),%edx		        movl %edx,(%ebx)
        movl %edx,(%eax)		        movl %ecx,(%eax)
        movl %ebx,(%ecx)		        addl $4,%ebx
        incl %esi			        incl %esi
        jmp .L60			        jmp .L60

C:
	-O: 5 cycles/loop			-O2: 6 cycles/loop
.L39:					.L39:
        leal -40960(%ebp,%esi,4),%eax	        movl (%ecx),%edx
        leal -40956(%ebp,%esi,4),%edx	        movl (%ebx),%eax
        movl (%eax),%ebx		        movl %eax,(%ecx)
        movl (%edx),%ecx		        movl %edx,(%ebx)
        movl %ecx,(%eax)		        addl $4,%ebx
        movl %ebx,(%edx)		        addl $4,%ecx
        incl %esi			        incl %esi
        cmpl $10238,%esi		        cmpl $10238,%esi
        jle .L39			        jle .L39

You can see that C++ fails to combine the loop exit branch with the
loop closure branch, although since both branches are almost perfectly
predicted, and we have spare operational units, there is no loss.

I suspect that without higher level optimization, 5 cycles/loop is
optimal on the PPro.  Unrolling the loop does not help.  The machine
can do one memory operation per cycle, and is probably spending a
cycle bypassing the result of one store to a successive load.  You can
imagine a compiler that notices that one iteration's stored ebx will
be the next iteration's loaded ebx, and thereby eliminates a load and
potentially even a store.  On some numeric code this might be an
interesting optimization, but on this example it is somewhat
contrived.

Eliminating that load is worth one cycle per iteration, and since the
machine is no longer bypassing stores to loads, we can unroll the loop
to amortize what appears to be a one cycle per taken branch cost over
several iterations.  Eliminating that store is worth another cycle per
iteration.  The resulting loop averages 2.34 cycles per iteration of
the source (Why not 2.5 cycles?  I don't know).  I've included the
loop setup and exit code.

        leal -40960(%ebp),%ecx
        leal -8(%ebp),%esi
        movl (%ecx),%edx
        .align 16
.L39:
        movl 4(%ecx),%eax
        movl %eax,(%ecx)
        movl 8(%ecx),%eax
        movl %eax,4(%ecx)
        addl $8,%ecx
        cmpl %esi,%ecx
        jle .L39
        movl %edx,(%ecx)


I cannot imagine why the "C -O2" loop runs a cycle slower than the "C
-O" loop.

Here's GCC-2.7.2.  (I formatted the table differently.)  You can see
that the older GCC could actually do induction variable elimination in
C at -O2.  The newer egcs-121697 cannot do it -- perhaps someone will
dig around to find out why.

at -O, inner loop:
	C++:6 cycles/loop			C:7 cycles/loop
.L60:					.L44:
        cmpl $10238,%esi			
        jg .L58				        leal 0(,%esi,4),%eax
        leal 0(,%esi,4),%eax		        leal -40960(%ebp,%eax),%ebx
        leal -40960(%ebp,%eax),%ebx	        leal -40956(%ebp,%eax),%ecx
        leal -40956(%eax,%ebp),%ecx	        movl (%ebx),%edx
        movl (%ebx),%edx			movl (%ecx),%eax
        movl (%ecx),%eax		        movl %eax,(%ebx)
        movl %eax,(%ebx)		        movl %edx,(%ecx)
        movl %edx,(%ecx)		        incl %esi
        incl %esi			        cmpl $10238,%esi
        jmp .L60			        jle .L44

at -O2, inner loop:
	C++:7 cycles/loop			C:6 cycles/loop
.L60:					.L44:
        cmpl $10238,%edi		        leal -40960(%ebp,%esi),%ebx
        jg .L58				        leal -40956(%ebp,%esi),%ecx
        leal -40960(%ebp,%esi),%ebx	        movl (%ebx),%edx
        leal -40956(%esi,%ebp),%ecx	        movl (%ecx),%eax
        movl (%ebx),%edx		        movl %eax,(%ebx)
        movl (%ecx),%eax		        movl %edx,(%ecx)
        movl %eax,(%ebx)		        addl $4,%esi
        movl %edx,(%ecx)		        cmpl $40952,%esi
        addl $4,%esi			        jle .L44
        incl %edi
        jmp .L60

-Iain



More information about the Gcc mailing list