The C memory aliasing model allows certain kinds of optimizations.
For example, consider the following code that's generated at the end the main loop of the FFT in _num.scm (after pulling it out of ##bignum.*):
___F64VECTORSET(___STK(-5),___R3,___F64V53) ___F64VECTORSET(___STK(-5),___R4,___F64V52) ___F64VECTORSET(___STK(-5),___STK(1),___F64V51) ___F64VECTORSET(___STK(-5),___STK(2),___F64V50) ___F64VECTORSET(___STK(-5),___STK(3),___F64V49) ___F64VECTORSET(___STK(-5),___STK(4),___F64V48) ___F64VECTORSET(___STK(-5),___STK(5),___F64V47) ___F64VECTORSET(___STK(-5),___STK(6),___F64V46) ___SET_R3(___FIXADD(___R3,___FIX(2L))) ___POLL(9) ___DEF_SLBL(9,___L9_direct_2d_fft_2d_recursive_2d_4) ___GOTO(___L22_direct_2d_fft_2d_recursive_2d_4)
Here we're alternating loading elements off the stack and storing floating-point values in a vector.
Under C's memory aliasing model (i.e., with -fstrict-aliasing, the gcc default), STK(-5) can be loaded into a register and reused after each floating-point store, because gcc can assume that the floating-point store does not change the value of STK(-5), since WORDs and doubles are different types.
When we call gcc with -fno-strict-aliasing, then the compiler has to assume that the floating-point store may affect the value of STK(-5), so it has to reload STK(-5) before each floating-point store.
This has the following effect on the time to compute 10,000 forward and 10,000 backward complex FFTs of 1,024 elements.
With -fno-strict-aliasing (used now by Gambit for reasons I'll explain below):
(time (do ((i 0 (fx+ i 1))) ((fx= i 10000)) (direct-fft-recursive-4 a table) (inverse-fft-recursive-4 a table) (do ((i 0 (fx+ i 1))) ((fx= i (fx* two^n 2))) (f64vector-set! a i (fl/ (f64vector-ref a i) inexact-two^n))))) 705 ms real time 704 ms cpu time (704 user, 0 system) 6 collections accounting for 2 ms real time (0 user, 0 system) 1600000 bytes allocated 22 minor faults no major faults
With -fstrict-aliasing:
(time (do ((i 0 (fx+ i 1))) ((fx= i 10000)) (direct-fft-recursive-4 a table) (inverse-fft-recursive-4 a table) (do ((i 0 (fx+ i 1))) ((fx= i (fx* two^n 2))) (f64vector-set! a i (fl/ (f64vector-ref a i) inexact-two^n))))) 590 ms real time 592 ms cpu time (588 user, 4 system) 6 collections accounting for 2 ms real time (4 user, 0 system) 1600000 bytes allocated 22 minor faults no major faults
That's a 19% performance improvement. (And the performance isn't bad, 1.74 GFLOPS on my 2.33GHz Core 2 quad 6600. Later I'll try to get gcc to vectorize the main loop.)
As I understand it, the C memory aliasing model is that once you store something of a certain type into a memory location, then you agree that everything you read from that memory location and everything else you write to that memory location will be of the same type.
So why do we need -fno-strict-aliasing? I know of two reasons: (1) in gambit.h we read and write bignums as adigits, mdigits, or fdigits, and these are chunks of the bignums of different sizes (generally 64/32/8 bits on 64-bit machines, and 64/16/8 bits on 32-bit machines) and (2) I believe we sometimes access double-precision floating-point numbers as integers. Marc, maybe you know of other instances in the code.
Both of these things can be fixed by accessing the internals of a bignum through a pointer type to a union of vectors of integers of the appropriate bit sizes, and doing the same for double-precision floats. (I had a much more difficult plan in mind before, of coming up with a union type of all possible Scheme objects, but that isn't necessary, I see now.)
I've been trying to figure out how to make the changes to gambit.h to do this in a clean way but without luck. Marc, you're the über programmer, do you have time to look at this?
Brad
Afficher les réponses par date
On Thu, 2012-10-25 at 16:19 -0400, Bradley Lucier wrote:
This has the following effect on the time to compute 10,000 forward and 10,000 backward complex FFTs of 1,024 elements.
With -fno-strict-aliasing (used now by Gambit for reasons I'll explain below):
(time (do ((i 0 (fx+ i 1))) ((fx= i 10000)) (direct-fft-recursive-4 a table) (inverse-fft-recursive-4 a table) (do ((i 0 (fx+ i 1))) ((fx= i (fx* two^n 2))) (f64vector-set! a i (fl/ (f64vector-ref a i) inexact-two^n))))) 705 ms real time 704 ms cpu time (704 user, 0 system) 6 collections accounting for 2 ms real time (0 user, 0 system) 1600000 bytes allocated 22 minor faults no major faults
With -fstrict-aliasing:
(time (do ((i 0 (fx+ i 1))) ((fx= i 10000)) (direct-fft-recursive-4 a table) (inverse-fft-recursive-4 a table) (do ((i 0 (fx+ i 1))) ((fx= i (fx* two^n 2))) (f64vector-set! a i (fl/ (f64vector-ref a i) inexact-two^n))))) 590 ms real time 592 ms cpu time (588 user, 4 system) 6 collections accounting for 2 ms real time (4 user, 0 system) 1600000 bytes allocated 22 minor faults no major faults
That's a 19% performance improvement. (And the performance isn't bad, 1.74 GFLOPS on my 2.33GHz Core 2 quad 6600. Later I'll try to get gcc to vectorize the main loop.)
I should have said which OS, gcc, and configuration I was using:
heine:~/programs/gambit> gsc -v v4.6.6 20121013190232 x86_64-unknown-linux-gnu "./configure 'CC=gcc -march=native -fschedule-insns -frename-registers' '--enable-single-host' '--enable-multiple-versions' '--enable-shared'"
heine:~/programs/gambit> gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.7/lto-wrapper Target: x86_64-linux-gnu Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.7.2-2ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.7/README.Bugs --enable-languages=c,c++,go,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.7 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.7 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1)
model name : Intel(R) Core(TM)2 Quad CPU Q8200 @ 2.33GHz
Brad
Le 2012-10-25 à 4:19 PM, Bradley Lucier lucier@math.purdue.edu a écrit :
The C memory aliasing model allows certain kinds of optimizations.
I agree that it would be good to implement those changes to Gambit. Not only would it improve performance, but the code would be more portable (there might be some C compilers without the equivalent of a -fno-strict-aliasing flag).
The problem is that this is a daunting task. gambit.h is written in a style that assumes a simple memory model, so basically every line of gambit.h has to be examined to see whether a change is needed or not. I'm fairly confident that few actual changes are needed (on the order of 100 lines of code is my guess). Would you be interested in doing this with my assistance? This can probably be done incrementally over a few weeks, say 500 lines of gambit.h a day. In a first phase we could focus on those changes we know are absolutely needed (because they cause specific benchmark programs to malfunction). I could add a configure option to enable strict-aliasing so that performance minded adventurous people (like you?) have the option to build Gambit with a strict-aliasing assumption. When we are confident all cases are handled, we can flip the default of that option to "enabled".
Marc