Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.forth > #59178

Forth-94 vs. Forth-2012 FP data (long)

From anton@mips.complang.tuwien.ac.at (Anton Ertl)
Newsgroups comp.lang.forth
Subject Forth-94 vs. Forth-2012 FP data (long)
Date 2017-05-13 07:34 +0000
Organization Institut fuer Computersprachen, Technische Universitaet Wien
Message-ID <2017May13.093451@mips.complang.tuwien.ac.at> (permalink)

Show all headers | View raw


In the last few days I and others have made some claims about the
performance effects of a separate FP stack vs. a combined stack, and
also on the Forth-94 vs. the Forth-2012 standard, i.e, the benefit (to
programmers) of being guaranteed a separate FP stack (Forth-2012)
vs. having to write code that works for a separate FP stack as well as
with a combined stack with any number of cells per FP item (Forth-94).

I decided to try to check these claims empirically.  To that end I
wrote a Forth-94 version of FAXPY-NOSTRIDE based on Joel Rees' code,
wrote a matrix multiplication routine, and benchmarked multiplying 2
500x500 matrices (once per benchmark run).  Results are from a 4GHz
Core i7-6600K.

Here's the code for the Forth-94 and Forth-2012 versions of
FAXPY-NOSTRIDE:

\ Forth-94
:noname depth >r 0 s>f depth r> - >r fdrop r> ; execute
constant FPCELLWIDTH
fvariable faxpy-ra \ should be a user variable
: faxpy-nostride ( ra f_x f_y ucount -- )
    \ vy=ra*vx+vy
    >r 2>r faxpy-ra f! 2r> r>
    0 ?DO ( f_x1 f_y1 )
        over f@ faxpy-ra f@ f*
        FPCELLWIDTH pick f@ f+ 
        FPCELLWIDTH pick f!
        float+ swap float+ swap
  LOOP
  2drop ;

\ Forth-2012
: faxpy-nostride ( ra f_x f_y ucount -- )
    \ vy=ra*vx+vy
    0 ?do
	fdup over f@ f* dup f@ f+ dup f! 
	float+ swap float+ swap
    loop
    2drop fdrop ;

The benchmark executes faxpy-nostride 250k times, and the inner loop
125M times.  Here's the inner loop of these two words on VFX with
Ndp387.fth, as well as the inner loop of Gforth's FAXPY primitive
(based on straighforward C code; I guess calling the BLAS routine
daxpy would produce better performance; I show this version to show
what a compiler can do and to get a feeling for how close to the limit
the Forth results are):

VFX Forth-2012         VFX Forth-94              gcc (faxpy)
FLD   ST               MOV   EDX, [EBP]          movsd  (%rdi),%xmm1      
MOV   EDX, [EBP]       FLD   TBYTE 0 [EDX]       add    %rsi,%rdi         
FLD   TBYTE 0 [EDX]    FLD   TBYTE [080A3460]    mulsd  %xmm0,%xmm1       
FMULP ST(1), ST        FMULP ST(1), ST           addsd  (%rdx),%xmm1      
FLD   TBYTE 0 [EBX]    FLD   TBYTE 0 [EBX]       movsd  %xmm1,(%rdx)      
FADDP ST(1), ST        FADDP ST(1), ST           add    %rcx,%rdx         
FSTP  TBYTE 0 [EBX]    FSTP  TBYTE 0 [EBX]       sub    $0x1,%r8          
ADD   EBX, 0A          ADD   EBX, 0A             jne    0x4180b8 <faxpy+8>
MOV   EDX, [EBP]       MOV   EDX, [EBP]      
ADD   EDX, 0A          ADD   EDX, 0A         
ADD   [ESP], 01        ADD   [ESP], 01       
ADD   [ESP+04], 01     ADD   [ESP+04], 01    
MOV   [EBP], EDX       MOV   [EBP], EDX      
JNO   080C6D78         JNO   080C6DB8        

So VFX works nicely for optimizing the PICKs, DUPs etc. away here, and
even the variable access does not cause an extra instruction.  One
weakness of the VFX code is that the loop control works through
memory, so each iteration takes at least 5 or 6 cycles; the whole FP
part of each iteration looks independent of the other operations, so
the out-of-order execution engine of this CPU should be able to
exploit a lot of parallelism, if we solve the loop control problem.
So I also wrote an unrolled (factor 4) version of the loop, which
should solve the problem.  The results are:

gforth    VFX/2012      VFX/2012 
C faxpy  no unrolling   unrolled
  1155M     1818M        1259M  instructions
   316M     1432M        1036M  cycles

So unrolling buys some speedup and I will show the unrolled results in
the rest of this posting for simplicity of exposition.  But despite
this speedup, the result is still more than tree times slower than the
C code, even though the number of instructions is similar.  I.e.,
there is still something holding back the out-of-order CPU when
running the VFX code.

One theory I pursued is that the memory accesses for the 10-byte
floats of VFX are at fault, i.e., that there is a false memory
dependency from the fstp of one iteration to the third fld of the next
in many cases, because they access the same word; I successfully
falsified this theory by redefining FLOATS and FLOAT+ for 16-byte
units, and observing that the results did not change.  So there must
be some other reason for the performance difference.

Another theory I pursued is that the 387 instructions are no longer
supported well, and that the slowdown is due to this.  I checked this
by compiling gforth with "gcc -mfpmath=387".  Here are the results:

 gforth    gforth   VFX/2012 
C faxpy   C faxpy   unrolled
    SSE       387        387
  1155M     1157M      1259M  instructions
   316M      561M      1036M  cycles

So, while 387 instructions cause some slowdown (from
2.5cycles/iteration to 4.5), there are still almost 4 cycles per
iteration unexplained in the VFX results.  In the rest of this posting
the gforth results are with SSE instructions (otherwise gcc does not
keep the FTOS in a register).

Anyway, back to the question of Forth-94 vs. Forth-2012 code:

cycles
  2012      94
 1036M   1163M vfxlin Ndp387.fth
 2222M   2949M gforth-fast
12434M  10193M SwiftForth

On VFX the Forth-94 variant is slower by 1 cycle/iteration, on
gforth-fast (which does not optimize stack manipulation much) by 5.8
cycles.  On SwiftForth the Forth-94 variant is faster (by 18
cycles/iteration, and it also executes 3.5 instructions/iteration
less), but even the Forth-94 variant is so slow on SwiftForth (81.5
cycles/iteration) that you won't use SwiftForth for this kind of thing
if performance matters.

Looking at the SwiftForth code, the loop body of the Forth-94 variant
is longer by 4 instructions; the code performs a call for every FP
word, and the code for FDUP executes 6 instructions more than that for
F@, explaining 2 of the 3.5 instructions/iteration difference.  The
implementations of the floating-point words are not written for time
(or space) efficiency: The FTOS is not cached on the FP stack, and
there is a WAIT instruction (2 cycles on Skylake) at the end of every
FP word.  Indeed, FDUP looks as if it was written to perform
pessimally: It loads an FP value from memory to the FPU stack, DUPs it
there, then uses a loop to store these two FP values to memory (i.e.,
including storing to the position where it had just fetched from).

Back to topic: these are all systems/packages that use a separate FP
stack.  How well does a combined stack perform?  VFX provides 5
different FP packages, three with a separate FP stack, two with a
combined stack.

x86/Ndp387.fth uses the 387 stack as FP stack.
x86/Hfp387.fth uses a stack in memory as FP stack, FTOS on 387 stack
x86/HfpGL32.fth uses a combined stack (32-bit floats)
SoftFP32.separate.fth implements FP using integer arithmetic, separate
SoftFP32.combined.fth implements FP using integer arithmetic, combined

Let's see how they perform:

  2012      94
 2222M   2949M gforth-fast (for reference)
 1036M   1163M vfxlin Ndp387.fth
 5156M   7402M vfxlin Hfp387.fth
         2596M vfxlin HfpGL32.fth
18967M  19140M vfxlin SoftFP32.separate.fth
        10307M vfxlin SoftFP32.combined.fth

Ndp387 performs best by far, but that approach is limited to the 387
(and, less importantly, to 8 FP stack items).

Hfp387 benefits a lot from the Forth-2012 variant, but is surprisingly
slow in either case (41 and 59 instructions/iteration).  The source
code looks reasonable (FTOS cached in the FPU, no obvious
inefficiencies), so the slowdown is surprising, especially compared to
gforth-fast, which uses similar techniques.

It storess the F-Stack pointer in a user variable and updates it
there, once for each of the 6 FP operations in the loop, which leads
to dependences through memory, and unlike the loads and stores from f@
and f! in this loop, these are loop-carried dependencies, and thus
their latency determines the performance of this loop; for 6 cycles
latency per update through memory, one iteration takes at least 36
cycles (pretty close to the measures 41 cycles).  I have no
explanation for the additional 18 cycles of the Forth-94 variant,
though.  By contrast, gforth-fast stores the FP stack pointer in a
register.

The moral: Keeping the FP stack pointer in memory is not necessarily
bad, but don't update it once per FP operation; instead, be a little
more analytical, and update it at the end of the basic block.  Or keep
the FP stack pointer in a register, like gforth.

HfpGL32 surprises with its performance, especially once I looked at
the source and object code for F+, which does not look like it is
written for efficiency (it takes 15 instructions compared to 6 for
Hfp387).  Looking at the code generated for the inner loop, it becomes
obvious what happens here: having the FP items on the data stack
brought F@ and F! into the realm of VFX's analytical compiler, and we
get pretty good code for everything in the loop except F+ and F*;
that's apparently good enough to get this relatively good result.

But note that HfpGL32 still is slower than gforth-fast with Forth-2012
code, even though gforth-fast has no analytical compiler.  My
expectation is that on a variant of VFX with an analytical compiler
that encompasses the FP stack, Hfp387 would outperform HfpGL32 by a
good margin, even if the FP stack pointer is kept in memory between
basic blocks.

The SoftFP32 variants are, unsurprisingly, slow, but it's interesting
that they are not that far off, given that they are written in Forth.
SoftFP32.separate is 71 cycles slower on the Forth-94 variant than
SoftFP32.combined. 

My expectation was that the speed difference is small relative to the
overhead of the software FP.  But we already saw for the hardware FP
variants that the analytical compiler of VFX does nicely for the
combined stack, so much of these 10M cycles is probably spent in the
software implementations of F* and F+, with the rest of the loop
condensed nicely by the analytical compiler.

By contrast, SoftFP32.separate contains a lot of >FS and FS>, each
time updating the FP stack pointer in memory; and it's not written for
performance: F+ starts with FS> FS> 2SWAP, i.e., two dependent updates
of the FP stack pointer, and ends with >FS.  At least that could be
handled better without too much effort.

Conclusion: If we want good performance beyond using the 387 stack as
FP stack, we need an analytical compiler; in that case, keeping the FP
stack pointer in memory is probably good enough.  If we don't want to
go there, keeping the FP stack pointer in a register is essential.  If
you don't have a compiler that optimizes PICK (e.g., gforth-fast),
Forth-2012 provides a significant performance benefit (in addition to
the programming convenience benefit) over Forth-94; even for VFX with
Ndp387, there is a small benefit for the Forth-2012 variant.

- anton
-- 
M. Anton Ertl  http://www.complang.tuwien.ac.at/anton/home.html
comp.lang.forth FAQs: http://www.complang.tuwien.ac.at/forth/faq/toc.html
     New standard: http://www.forth200x.org/forth200x.html
   EuroForth 2017: http://www.euroforth.org/ef17/

Back to comp.lang.forth | Previous | Next — Next in thread | Find similar | Unroll thread


Thread

Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-13 07:34 +0000
  Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-13 11:16 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-13 11:49 -0700
      Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-13 13:38 -0700
        Re: Forth-94 vs. Forth-2012 FP data (long) mhx@iae.nl - 2017-05-13 15:08 -0700
          Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-14 00:51 -0700
          Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-14 07:32 +0000
            Re: Forth-94 vs. Forth-2012 FP data (long) mhx@iae.nl - 2017-05-14 02:46 -0700
              Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-14 14:57 +0000
                Re: Forth-94 vs. Forth-2012 FP data (long) krishna.myneni@ccreweb.org - 2017-05-15 19:11 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-14 08:14 +0000
  Re: Forth-94 vs. Forth-2012 FP data (long) Paul Rubin <no.email@nospam.invalid> - 2017-05-13 15:58 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-14 07:01 +0000
  Re: Forth-94 vs. Forth-2012 FP data (long) krishna.myneni@ccreweb.org - 2017-05-14 19:04 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-15 06:10 +0000
      Re: Forth-94 vs. Forth-2012 FP data (long) krishna.myneni@ccreweb.org - 2017-05-15 01:49 -0700
        Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-15 14:33 +0000
  Re: Forth-94 vs. Forth-2012 FP data (long) "HAA" <someone@microsoft.com> - 2017-05-15 13:44 +1000
    Re: Forth-94 vs. Forth-2012 FP data (long) Andrew Haley <andrew29@littlepinkcloud.invalid> - 2017-05-15 04:43 -0500
    Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-15 15:03 +0000
      Re: Forth-94 vs. Forth-2012 FP data (long) "HAA" <someone@microsoft.com> - 2017-05-17 00:10 +1000
  Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-16 09:04 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-17 15:54 +0000
      Re: Forth-94 vs. Forth-2012 FP data (long) mhx@iae.nl - 2017-05-17 10:29 -0700
      Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-17 23:53 -0700
        Re: Forth-94 vs. Forth-2012 FP data (long) pahihu <pahihu@gmail.com> - 2017-05-19 02:34 -0700
  Re: Forth-94 vs. Forth-2012 FP data (long) Joel Rees <joel.rees@gmail.com> - 2017-05-16 22:48 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-17 06:51 +0000
      Re: Forth-94 vs. Forth-2012 FP data (long) Joel Rees <joel.rees@gmail.com> - 2017-05-17 09:29 -0700
    Re: Forth-94 vs. Forth-2012 FP data (long) stephen@mpeforth.com (Stephen Pelc) - 2017-05-17 11:07 +0000
      Re: Forth-94 vs. Forth-2012 FP data (long) anton@mips.complang.tuwien.ac.at (Anton Ertl) - 2017-05-17 15:02 +0000

csiph-web