Updated libsmm using OpenMP rather than multiprocess make for more

realistic affinity control (Alfio Lazzaro, Cray)


svn-origin-rev: 14083
This commit is contained in:
Iain Bethune 2014-06-16 14:01:55 +00:00
parent 8a529013ef
commit 4c3b374e4f
16 changed files with 283 additions and 295 deletions

View file

@ -19,7 +19,7 @@ Below you can find the detailed instructions for some examples.
====================================================================================================================
a) How to generate the library running several jobs in a cluster, where each
computer allows for both execution and compilation.
For this example we will use a CRAY system with GNU compiler and PBS.
For this example we will use a CRAY system with GNU compiler and SLURM.
Run "./generate -h" to see the meaning of the options.
1) Run: ./generate -c config/cray.gnu -j 100 -t 16 -w slurm tiny1
@ -52,7 +52,7 @@ a) How to generate the library running several jobs in a cluster, where each
====================================================================================================================
b) How to generate the library for the Intel Xeon Phi in batch mode
For this example we will use a cluster with PBS, where each node has a MIC card.
For this example we will use a cluster with SLURM, where each node has a MIC card.
Run "./generate -h" to see the meaning of the options.
We use the config file mic.intel (inside the directory config).
Check if all options are OK for your case, in particular:

View file

@ -9,7 +9,7 @@
# and allow some flexibility in reordering floating point expressions (-ffast-math).
# Higher level optimisation (in particular loop nest optimization) should not be used.
#
target_compile="ftn -O2 -hfp3 -Oipa0 -hnodwarf -Onopattern "
target_compile="ftn -O2 -hfp3 -hnodwarf -Onopattern -hvector1 "
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)
@ -24,9 +24,14 @@ target_compile="ftn -O2 -hfp3 -Oipa0 -hnodwarf -Onopattern "
# the library. The library itself is not compiled this way.
# This compiler needs to be able to deal with some Fortran2003 constructs.
#
host_compile="gfortran -O2"
host_compile="gfortran-4.6 -O2"
#
# Show affinity mask
#
export CRAY_OMP_CHECK_AFFINITY=TRUE
#
# Set the aprun command and its options for batch submission
#
aprun_cmd="aprun -B -cc none"
aprun_cmd="aprun -n 1 -N 1 -d ${ntasks} -r 1"

View file

@ -9,7 +9,7 @@
# and allow some flexibility in reordering floating point expressions (-ffast-math).
# Higher level optimisation (in particular loop nest optimization) should not be used.
#
target_compile="ftn -O2 -funroll-loops -ffast-math -ftree-vectorize -fno-inline-functions -march=native"
target_compile="ftn -O2 -funroll-loops -ffast-math -ftree-vectorize -finline-functions -fopenmp -march=native"
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)
@ -29,4 +29,4 @@ host_compile="gfortran -O2"
#
# Set the aprun command and its options for batch submission
#
aprun_cmd="aprun -B -cc none"
aprun_cmd="aprun -n 1 -N 1 -d ${ntasks} -r 1"

View file

@ -9,7 +9,7 @@
# and allow some flexibility in reordering floating point expressions (-ffast-math).
# Higher level optimisation (in particular loop nest optimization) should not be used.
#
target_compile="ftn -O2 -funroll-loops -vec-report2 -warn -xHost -fno-inline-functions -nogen-interfaces -openmp -nolib-inline"
target_compile="ftn -O2 -funroll-loops -warn -finline-functions -nogen-interfaces -openmp -nolib-inline"
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)
@ -25,7 +25,12 @@ target_compile="ftn -O2 -funroll-loops -vec-report2 -warn -xHost -fno-inline-fun
#
host_compile="ifort -O2"
#
# Set KMP affinity
#
export KMP_AFFINITY=verbose,granularity=fine,scatter
#
# Set the aprun command and its options for batch submission
#
aprun_cmd="aprun -B -cc none"
aprun_cmd="aprun -n 1 -N 1 -d ${ntasks} -r 1 -cc none"

View file

@ -9,7 +9,7 @@
# and allow some flexibility in reordering floating point expressions (-ffast-math).
# Higher level optimisation (in particular loop nest optimization) should not be used.
#
target_compile="ifort -O2 -funroll-loops -vec-report2 -warn -xHost -fno-inline-functions -nogen-interfaces -openmp -nolib-inline"
target_compile="ifort -O2 -funroll-loops -warn -xHost -finline-functions -nogen-interfaces -openmp -nolib-inline"
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)
@ -25,7 +25,12 @@ blas_linking="-static-intel -mkl=sequential"
#
host_compile="ifort -O2"
#
# Set KMP affinity
#
export KMP_AFFINITY=verbose,granularity=fine,scatter
#
# Set the aprun command and its options for batch submission
#
aprun_cmd="aprun -B -cc none"
aprun_cmd="aprun -n 1 -N 1 -d ${ntasks} -r 1 -cc none"

View file

@ -6,7 +6,7 @@
# and allow some flexibility in reordering floating point expressions (-ffast-math).
# Higher level optimisation (in particular loop nest optimization) should not be used.
#
target_compile="gfortran -O2 -funroll-loops -ffast-math -ftree-vectorize -march=native -fno-inline-functions"
target_compile="gfortran -O2 -funroll-loops -ffast-math -ftree-vectorize -march=native -finline-functions -fopenmp"
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)

View file

@ -7,7 +7,7 @@
# Higher level optimisation (in particular loop nest optimization) should not be used.
# Note: -fp-model fast=1 is default for the Intel compiler
#
target_compile="ifort -O2 -funroll-loops -vec-report2 -warn -xHost -fno-inline-functions -nogen-interfaces -openmp -nolib-inline"
target_compile="ifort -O2 -funroll-loops -vec-report2 -warn -xHost -finline-functions -nogen-interfaces -openmp -nolib-inline"
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)

View file

@ -7,7 +7,7 @@
# Higher level optimisation (in particular loop nest optimization) should not be used.
# Note: -fp-model fast=1 is default for the Intel compiler
#
target_compile="ifort -O2 -funroll-loops -vec-report2 -warn -mmic -fno-inline-functions -nogen-interfaces -openmp"
target_compile="ifort -O2 -funroll-loops -vec-report2 -warn -mmic -finline-functions -nogen-interfaces -openmp"
#
# target dgemm link options... these are the options needed to link blas (e.g. -lblas)

View file

@ -2,5 +2,5 @@
batch_cmd() {
echo "${aprun_cmd} $@" | \
qsub -N ${test_name} -j oe -V -d ${PWD} -lmppwidth=1 -lmppnppn=1 -lwalltime=${wtime};
qsub -N ${test_name} -j oe -V -d ${PWD} -lnodes=1 -lwalltime=${wtime};
}

View file

@ -1,7 +1,7 @@
# Author: Alfio Lazzaro, alazzaro@cray.com (2013)
batch_cmd() {
sbatch -J ${test_name} -o "${test_name}-%j.out" -D ${PWD} --ntasks=1 --ntasks-per-node=1 -t ${wtime} <<EOF
sbatch -J ${test_name} -o "${test_name}-%j.out" -D ${PWD} --ntasks=1 --ntasks-per-node=1 --cpus-per-task=${ntasks} -t ${wtime} <<EOF
#!/bin/bash -e
${aprun_cmd} $@
EOF

View file

@ -21,7 +21,7 @@ source generate.bash
def_config_dir="config"
def_config_file="${def_config_dir}/cray.gnu"
def_SIMD="avx"
def_ntasks="16"
def_ntasks="1"
def_base_out_dir="output"
def_target="all"
def_njobs="1"
@ -43,8 +43,14 @@ show_help() {
echo " Default value is ${def_ntasks}."
echo " -w <wlm type> : workload manager for batch submission. The value must correspond to one of the files ${def_config_dir}/*.wlm."
echo " Default value is \"${def_wlm}\"."
echo " -m : time limit for batch execution."
echo " -m <time> : time limit for batch execution."
echo " Default value is \"${def_wtime}\"."
echo " -a <target> : Target for make compilation. Values are: "
echo " all: generate, compile and run the kernels."
echo " source: only generate the source file kernels."
echo " compile: generate and compile the kernels."
echo " bench: same as all."
echo " Default value is \"${def_target}\"."
echo
echo "COMMAND is one of the followings:"
echo " tiny1 : it runs the tiny phase. Batch execution if requested."
@ -87,7 +93,7 @@ check_number()
}
while getopts "c:hj:s:t:w:m:" OPTION; do
while getopts "c:hj:s:t:w:m:a:" OPTION; do
case $OPTION in
c)
config_file=$OPTARG
@ -112,6 +118,17 @@ while getopts "c:hj:s:t:w:m:" OPTION; do
m)
wtime=$OPTARG
;;
a)
case $OPTARG in
all|source|compile|bench)
target=$OPTARG
;;
*)
echo "Warning: target \"$OPTARG\" unknown. Run ./generate -h for help."
exit
;;
esac
;;
?)
exit
;;
@ -123,15 +140,15 @@ shift $(( OPTIND - 1))
# Use default OPTIONS values if they were not declared
#
if [ -z "${config_file}" ]; then
config_file=$def_config_file
config_file=${def_config_file}
fi
if [ -z "${SIMD}" ]; then
SIMD=$def_SIMD
SIMD=${def_SIMD}
fi
if [ -z "${ntasks}" ]; then
ntasks=$def_ntasks
ntasks=${def_ntasks}
fi
if [ -z "${jobs}" ]; then
@ -146,8 +163,11 @@ if [ -z "${wtime}" ]; then
wtime=${def_wtime}
fi
if [ -z "${target}" ]; then
target=${def_target}
fi
base_out_dir=$def_base_out_dir
target=$def_target
#
# Check COMMAND value
@ -167,6 +187,7 @@ case $cmd in
rm -fR run_tiny_*/
rm -fR run_small_*/
rm -fR run_lib_*/
rm -fR run_check_*/
rm -f *.mod Makefile.* *.x *.o
rm -f *~
if [ "$cmd" == "cleanall" ]; then
@ -243,6 +264,7 @@ echo "WLM : ${wlm}"
echo "SIMD register type : ${SIMD}"
echo "Limit time batch : ${wtime}"
echo "COMMAND : \"${cmd}\""
echo "Make target : \"${target}\""
echo
#
@ -250,7 +272,6 @@ echo
#
config_file_name=`basename ${config_file}`
out_dir="${base_out_dir}_${config_file_name}"
OMP_NUM_THREADS=1
if [ ${jobs} -gt 0 ]; then
source ${def_config_dir}/${wlm}.wlm
@ -266,6 +287,12 @@ if [ ${jobs} -gt 0 ]; then
else
# Require 1 job
jobs=1
case $cmd in
tiny2|small2|check2)
run_cmd=true
;;
esac
fi
type_label="_"
@ -333,9 +360,13 @@ case $cmd in
run_dir+="_small"
make_file+="small"
;;
lib|check1|check2)
lib)
run_dir+="_lib"
make_file+="lib"
;;
check1|check2)
run_dir+="_check"
;;
esac
run_dir+="${type_label}"
@ -343,7 +374,7 @@ make_file+="${type_label}_${config_file_name}"
tiny_file="tiny_gen_optimal${type_label}_${config_file_name}.out"
small_file="small_gen_optimal${type_label}_${config_file_name}.out"
test_file="test_smm${type_label}_${config_file_name}"
test_file="test_smm${type_label}"
library="smm${type_label}_${config_file_name}"
archive="../lib/lib${library}.a"

View file

@ -36,12 +36,39 @@ write_makefile_header() {
#
# list of executables
#
printf "EXEFILES=\$(patsubst %%,\$(OUTDIR)/${prefix_file}_find_%%.x,\$(INDICES)) \n"
printf "OBJFILES=\$(patsubst %%,\$(OUTDIR)/${prefix_file}_find_%%.o,\$(INDICES)) \n"
#
# list of output files
#
printf "OUTFILES=\$(patsubst %%,\$(OUTDIR)/${prefix_file}_find_%%.out,\$(INDICES)) \n\n"
printf "OUTFILES=\$(OBJFILES:.o=.out) \n\n"
#
# name of the executable
#
printf "EXE=${prefix_file}_find_\$(firstword \$(INDICES))__\$(lastword \$(INDICES)).x \n\n"
#
# main target
#
printf ".PHONY: bench \$(EXE:.x=.f90) \n"
printf "all: bench \n\n"
#
# include makefile for source master code generation
#
printf "DATATYPE=${strdat}\n"
printf "include ../make.gen\n\n"
#
# write general targets
#
printf "bench: \$(OUTDIR)/\$(EXE) \n"
printf "\t rm -f \$(OUTFILES) \n"
printf "\t export OMP_NUM_THREADS=${ntasks} ; ./\$< \n\n"
printf "\$(OUTDIR)/\$(EXE): \$(OBJFILES) \$(EXE:.x=.f90) \n"
printf "\t ${target_compile} \$^ -o \$@ ${blas_linking} \n\n"
}
@ -98,7 +125,7 @@ collect_results() {
suffix=$1 ; shift
#
# analyse results finding optimal tiny mults
# analyse results finding optimal tiny or small mults
#
(
for m in $@ ; do
@ -134,26 +161,16 @@ do_generate_tiny() {
write_makefile_header tiny "${dims_tiny}"
#
# Write targets
# Write specific targets
#
printf "all: bench \n\n"
printf "compile: \$(OBJFILES) \n"
printf "\$(OUTDIR)/%%.o: %%.f90 \n"
printf "\t ${target_compile} -c \$< -o \$@ \n\n"
printf "source: \$(SRCFILES) \n\n"
printf "source: \$(SRCFILES) \n"
printf "%%.f90: \n"
printf '\t .././tiny_gen.x `echo $* | awk -F_ '\''{ print $$3" "$$4" "$$5 }'\''`'
printf " ${transpose_flavor} ${data_type} > \$@\n\n"
printf "compile: \$(EXEFILES) \n\n"
printf "\$(OUTDIR)/%%.x: %%.f90 \n"
printf "\t ${target_compile} \$< -o \$@ \n\n"
printf "bench: \$(OUTFILES) \n\n"
printf "\$(OUTDIR)/%%.out: \$(OUTDIR)/%%.x \n"
printf "\t ${mic_cmd} ./\$<"
if [[ ( -n "${mic_cmd}" ) && ( -n "${OMP_NUM_THREADS}" ) ]]; then
printf " -e \"OMP_NUM_THREADS=${OMP_NUM_THREADS}\""
fi
printf " > \$@ \n\n"
printf " ${transpose_flavor} ${data_type} > \$@ \n\n"
) > ${make_file}
run_make ${dims_tiny}
@ -186,13 +203,6 @@ do_generate_small() {
${host_compile} -c multrec_gen.f90
${host_compile} mults.o multrec_gen.o small_gen.f90 -o small_gen.x
#
# Make directory for MIC execution
#
if [[ ( -n "${MICFS}" ) && ( -n "${ssh_mic_cmd}" ) ]]; then
mkdir -p ${MICFS}
fi
#
# for easy parallelism go via a Makefile
#
@ -200,38 +210,17 @@ do_generate_small() {
(
write_makefile_header small "${dims_small}"
if [[ ( -n "${MICFS}" ) && ( -n "${ssh_mic_cmd}" ) ]]; then
printf "MICFS=${MICFS}\n\n"
fi
#
# Write targets
# Write specific targets
#
printf "all: bench \n\n"
printf "compile: \$(OBJFILES) \n"
printf "\$(OUTDIR)/%%.o: \$(OUTDIR)/%%.f90 \n"
printf "\t ${target_compile} -c \$< -o \$@ \n\n"
printf "source: \$(SRCFILES) \n\n"
printf "%%.f90: \n"
printf "source: \$(addprefix \$(OUTDIR)/,\$(SRCFILES)) \n"
printf "\$(OUTDIR)/%%.f90: ../${tiny_file} \n"
printf '\t .././small_gen.x `echo $* | awk -F_ '\''{ print $$3" "$$4" "$$5 }'\''`'
printf " ${transpose_flavor} ${data_type} ${SIMD_size} ../${tiny_file} > \$@\n\n"
printf "compile: \$(EXEFILES) \n\n"
printf "\$(OUTDIR)/%%.x: %%.f90 \n"
printf "\t ${target_compile} \$< -o \$@ ${blas_linking} \n\n"
printf "bench: \$(OUTFILES) \n\n"
printf "\$(OUTDIR)/%%.out: \$(OUTDIR)/%%.x \n"
if [[ ( -n "${MICFS}" ) && ( -n "${ssh_mic_cmd}" ) ]]; then
printf "\t cp \$< \$(MICFS) \n"
printf "\t ${ssh_mic_cmd} \"ulimit -s 256000; export OMP_NUM_THREADS=1; "
if [ -n "${MIC_LD_LIBRARY_PATH}" ]; then
printf "export LD_LIBRARY_PATH=${MIC_LD_LIBRARY_PATH}; "
fi
printf "\$(MICFS)/./\$(<F)\" > \$@ \n"
printf "\t rm \$(MICFS)/\$(<F)"
else
printf "\t ./\$< > \$@"
fi
printf "\n\n"
) > ${make_file}
run_make ${dims_small}
@ -408,30 +397,30 @@ do_generate_lib() {
#
# list of source files
#
printf "SRCFILES=\$(patsubst %%,smm${type_label}_%%.f90,\$(INDICES)) \n"
printf "SRCFILES=\$(patsubst %%,\$(OUTDIR)/smm${type_label}_%%.f90,\$(INDICES)) \n"
#
# list of obj files
#
printf "OBJFILES=\$(patsubst %%,\$(OUTDIR)/smm${type_label}_%%.o,\$(INDICES)) \n\n"
printf ".PHONY: \$(OUTDIR)/\$(DRIVER) \n\n"
printf ".PHONY: \$(OUTDIR)/\$(DRIVER) archive \n\n"
printf "all: archive \n\n"
#
# generation source rule
#
printf "source: \$(SRCFILES) \n\n"
printf "%%.f90: \n"
printf "source: \$(SRCFILES) \n"
printf "\$(OUTDIR)/%%.f90: ../${small_file} ../${tiny_file} \n"
printf '\t .././lib_gen.x `echo $* | awk -F_ '\''{ print $$3" "$$4" "$$5 }'\''`'
printf " ${transpose_flavor} ${data_type} ${SIMD_size} ../${small_file} ../${tiny_file} > \$@\n\n"
#
# compile rule
#
printf "compile: \$(OBJFILES) \n\n"
printf "\$(OUTDIR)/%%.o: %%.f90 \n"
printf "compile: \$(OBJFILES) \n"
printf "\$(OUTDIR)/%%.o: \$(OUTDIR)/%%.f90 \n"
printf "\t ${target_compile} -c \$< -o \$@ \n\n"
printf "\$(OUTDIR)/\$(DRIVER): \n"
@ -456,7 +445,7 @@ do_generate_lib() {
# execute makefile compiling all variants and executing them
#
test_name="${run_dir}_job${jobs}"
${run_cmd} make -B -j ${ntasks} -f ../${make_file} ${target}
${run_cmd} make -j ${ntasks} -f ../${make_file} ${target}
}
@ -487,6 +476,11 @@ do_check() {
element_end=0
ijob=0
#
# make the output dir
#
mkdir -p ${out_dir}
#
# Make directory for MIC execution
#
@ -509,7 +503,7 @@ do_check() {
echo "Preparing test program for job #$ijob..."
filename=${test_file}_job$ijob
cat << EOF > ${filename}.f90
cat << EOF > ${out_dir}/${filename}.f90
MODULE WTF_job$ijob
INTERFACE MYRAND
MODULE PROCEDURE SMYRAND, DMYRAND, CMYRAND, ZMYRAND
@ -573,6 +567,7 @@ SUBROUTINE testit(M,N,K)
IF (MAXVAL(ABS(C2-C1))>100*EPSILON(REAL(1.0,KIND=KIND(A(1,1))))) THEN
write(6,*) "Matrix size",M,N,K
write(6,*) "Max diff=",MAXVAL(ABS(C2-C1))
write(6,*) "A=",A
write(6,*) "B=",B
write(6,*) "C1=",C1
@ -605,33 +600,35 @@ END SUBROUTINE
PROGRAM tester
IMPLICIT NONE
!\$omp parallel
EOF
fi
echo " CALL testit(${m},${n},${k})" >> ${filename}.f90
echo " CALL testit(${m},${n},${k})" >> ${out_dir}/${filename}.f90
# last entry for a job
if [ $element -eq $element_end ]; then
# last job
if [ $ijob -eq $jobs ]; then
cat << EOF >> ${filename}.f90
cat << EOF >> ${out_dir}/${filename}.f90
! checking 'peak' performance (and a size likely outside of the library)
CALL testit(1000,1000,1000)
EOF
fi
cat << EOF >> ${filename}.f90
cat << EOF >> ${out_dir}/${filename}.f90
!\$omp end parallel
END PROGRAM
EOF
echo "Launching job #$ijob"
test_name="${run_dir}_check_job${ijob}"
test_name="${run_dir}_job${ijob}"
exe=${filename}.x
rm -f ${filename}.sh
rm -f ${out_dir}/${filename}.sh
#
# Prepare the script for compile the benchmarking
# and testing program for the smm library
@ -639,21 +636,13 @@ EOF
(
printf "#!/bin/bash -e \n\n"
printf "set -o pipefail\n\n"
printf "${target_compile} ${filename}.f90 -o ${exe} -L../lib -l${library} ${blas_linking}\n"
if [[ ( -n "${MICFS}" ) && ( -n "${ssh_mic_cmd}" ) ]]; then
printf "cp ${exe} ${MICFS}\n"
printf "${ssh_mic_cmd} \"ulimit -s 256000; export OMP_NUM_THREADS=1; "
if [ -n "${MIC_LD_LIBRARY_PATH}" ]; then
printf "export LD_LIBRARY_PATH=${MIC_LD_LIBRARY_PATH}; "
fi
printf "${MICFS}/./${exe}\" | tee ${filename}.out\n"
else
printf "./${exe} | tee ${filename}.out"
fi
) > ${filename}.sh
chmod +x ${filename}.sh
printf "cd ${out_dir}\n"
printf "${target_compile} ${filename}.f90 -o ${exe} -L../../lib -l${library} ${blas_linking}\n"
printf "export OMP_NUM_THREADS=1 ; ./${exe} | tee ${filename}.out"
) > ${out_dir}/${filename}.sh
chmod +x ${out_dir}/${filename}.sh
${run_cmd} ./${filename}.sh
${run_cmd} ./${out_dir}/${filename}.sh
fi
element=$(( element + 1 ))
@ -674,28 +663,30 @@ EOF
#
# Merge the output files in the case of $run_cmd" = "true"
#
rm -f ${test_file}.out
rm -f ${test_file}_${config_file_name}.out
for (( ijob=1 ; ijob<=jobs; ijob++ )); do
if [ ! -s ${run_dir}/${test_file}"_job$ijob".out ]; then
echo "ERROR: Empty check file \"${run_dir}/${test_file}_job$ijob.out\""
if [ ! -s ${run_dir}/${out_dir}/${test_file}"_job$ijob".out ]; then
echo "ERROR: Empty check file \"${run_dir}/${out_dir}/${test_file}_job$ijob.out\""
exit
fi
cat ${run_dir}/${test_file}"_job$ijob".out >> ${test_file}.out
cat ${run_dir}/${out_dir}/${test_file}"_job$ijob".out >> ${test_file}_${config_file_name}.out
done
#
# We're done... protect the user from bad compilers
#
set +e
grep "BLAS and smm yield different results" ${test_file}.out >& /dev/null
grep "BLAS and smm yield different results" ${test_file}_${config_file_name}.out >& /dev/null
if [ "$?" == "0" ]; then
echo "Library is miscompiled... removing lib/lib${library}.a"
echo
rm -f ${archive}
else
pathhere=`pwd -P`
echo "Done... check performance looking at ${test_file}.out"
echo "Done... check performance looking at ${test_file}_${config_file_name}.out"
echo "Final library can be linked as -L${pathhere}/lib -l${library}"
echo
fi
}

View file

@ -76,24 +76,28 @@ CONTAINS
CHARACTER(LEN=*), OPTIONAL :: stack_size_label
INTEGER, OPTIONAL :: Cbuffer_row, Cbuffer_col
IF (PRESENT(stack_size_label).AND.stack_size_label/="") THEN
write(6,'(A,I0,A,I0,A,I0,A)') " SUBROUTINE smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,"_stack"//TRIM(label)//"("//TRIM(trparam(stack_size_label))//")"
CALL write_stack_params(data_type,stack_size_label)
write(6,'(A)') " INTEGER :: sp"
IF (PRESENT(Cbuffer_row).AND.PRESENT(Cbuffer_col)) THEN
write(6,'(A,I0,A,I0,A)') " "//trdat(data_type)//":: Cbuffer(",Cbuffer_row,",",Cbuffer_col,")"
IF (PRESENT(stack_size_label)) THEN
IF (stack_size_label/="") THEN
write(6,'(A,I0,A,I0,A,I0,A)') " SUBROUTINE smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,"_stack"//TRIM(label)//"("//TRIM(trparam(stack_size_label))//")"
CALL write_stack_params(data_type,stack_size_label)
write(6,'(A)') " INTEGER :: sp"
IF (PRESENT(Cbuffer_row).AND.PRESENT(Cbuffer_col)) THEN
write(6,'(A,I0,A,I0,A)') " "//trdat(data_type)//":: Cbuffer(",Cbuffer_row,",",Cbuffer_col,")"
ENDIF
write(6,'(A)') " DO sp = 1, "//TRIM(stack_size_label)
IF (PRESENT(Cbuffer_row).AND.PRESENT(Cbuffer_col)) THEN
write(6,'(A,I0,A,I0,A,I0,A)') " CALL smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,&
TRIM(label)//"_buffer(A(params(p_a_first,sp)),B(params(p_b_first,sp)),C(params(p_c_first,sp)),Cbuffer)"
ELSE
write(6,'(A,I0,A,I0,A,I0,A)') " CALL smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,&
TRIM(label)//"(A(params(p_a_first,sp)),B(params(p_b_first,sp)),C(params(p_c_first,sp)))"
ENDIF
write(6,'(A)') " ENDDO"
write(6,'(A)') " END SUBROUTINE"
ENDIF
write(6,'(A)') " DO sp = 1, "//TRIM(stack_size_label)
IF (PRESENT(Cbuffer_row).AND.PRESENT(Cbuffer_col)) THEN
write(6,'(A,I0,A,I0,A,I0,A)') " CALL smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,TRIM(label)//"_buffer(A(params(p_a_first,sp)),B(params(p_b_first,sp)),C(params(p_c_first,sp)),Cbuffer)"
ELSE
write(6,'(A,I0,A,I0,A,I0,A)') " CALL smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,TRIM(label)//"(A(params(p_a_first,sp)),B(params(p_b_first,sp)),C(params(p_c_first,sp)))"
ENDIF
write(6,'(A)') " ENDDO"
write(6,'(A)') " END SUBROUTINE"
ENDIF
END SUBROUTINE write_subroutine_stack
@ -103,12 +107,17 @@ CONTAINS
INTEGER :: multElements,modElements
CHARACTER(LEN=*) :: label
CHARACTER(LEN=*), OPTIONAL :: stack_size_label
LOGICAL :: do_stack
multElements=(M/nSIMD)*nSIMD
modElements=MOD(M,nSIMD)
IF (modElements>0) THEN
if (PRESENT(stack_size_label).AND.stack_size_label/="") THEN
do_stack=.FALSE.
IF (PRESENT(stack_size_label)) THEN
IF (stack_size_label/="") do_stack=.TRUE.
ENDIF
IF (do_stack) THEN
write(6,'(A,I0,A,I0,A,I0,A)') " PURE SUBROUTINE smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,TRIM(label)//"_buffer(A,B,C,Cbuffer)"
write(6,'(A,I0,A,I0,A)') " "//trdat(data_type,"INOUT")//" :: Cbuffer(",nSIMD,",",MIN(stride,N),")"
@ -322,8 +331,10 @@ CONTAINS
! generation of the vector version
IF (nSIMD>0) THEN
CALL MULTVECTOR(label,M,N,K,transpose_flavor,data_type,nSIMD,stride,stack_size_label)
IF (PRESENT(write_buffer_interface).AND.write_buffer_interface) THEN
CALL MULTVECTOR(label,M,N,K,transpose_flavor,data_type,nSIMD,stride)
IF (PRESENT(write_buffer_interface)) THEN
IF (write_buffer_interface) THEN
CALL MULTVECTOR(label,M,N,K,transpose_flavor,data_type,nSIMD,stride)
ENDIF
ENDIF
ENDIF

View file

@ -8,11 +8,12 @@ CONTAINS
CHARACTER(LEN=*), OPTIONAL :: in_intent_label
CHARACTER(LEN=50) :: intent_label
CHARACTER(LEN=50) :: trdat
IF (PRESENT(in_intent_label).AND.(in_intent_label.ne."")) THEN
intent_label=", INTENT("//TRIM(in_intent_label)//")"
ELSE
intent_label=""
intent_label=""
IF (PRESENT(in_intent_label)) THEN
IF (in_intent_label/="") THEN
intent_label=", INTENT("//TRIM(in_intent_label)//")"
ENDIF
ENDIF
SELECT CASE(data_type)
CASE(1)
@ -25,6 +26,7 @@ CONTAINS
trdat="COMPLEX(KIND=KIND(0.0))"//TRIM(intent_label)
END SELECT
END FUNCTION
FUNCTION trgemm(data_type)
INTEGER :: data_type
CHARACTER(LEN=5) :: trgemm
@ -39,6 +41,7 @@ CONTAINS
trgemm="CGEMM"
END SELECT
END FUNCTION
FUNCTION trstr(transpose_flavor,data_type)
INTEGER :: transpose_flavor, data_type
CHARACTER(LEN=3) :: trstr
@ -64,6 +67,7 @@ CONTAINS
trstr=dstr//"tt"
END SELECT
END FUNCTION trstr
FUNCTION trparam(stack_size_label)
CHARACTER(LEN=*), OPTIONAL :: stack_size_label
CHARACTER(LEN=128) :: trparam
@ -73,6 +77,7 @@ CONTAINS
trparam = "A,B,C"
ENDIF
END FUNCTION trparam
SUBROUTINE write_stack_params(data_type,stack_size_label)
INTEGER :: data_type
CHARACTER(LEN=*), OPTIONAL :: stack_size_label
@ -83,6 +88,7 @@ CONTAINS
write(6,'(A)') " INTEGER, INTENT(IN) :: p_a_first, p_b_first, p_c_first"
ENDIF
END SUBROUTINE write_stack_params
SUBROUTINE write_matrix_defs(M,N,K,transpose_flavor,data_type,write_intent,stack_size_label,padding)
INTEGER, OPTIONAL :: M,N,K,transpose_flavor
INTEGER :: data_type
@ -90,6 +96,7 @@ CONTAINS
CHARACTER(LEN=*), OPTIONAL :: stack_size_label
LOGICAL, OPTIONAL :: padding
CHARACTER(LEN=50) :: intent_label
LOGICAL :: do_padding
IF (PRESENT(M).AND.PRESENT(N).AND.PRESENT(K).AND.PRESENT(transpose_flavor)) THEN
IF (PRESENT(stack_size_label)) THEN
@ -105,7 +112,11 @@ CONTAINS
" "//trdat(data_type,"INOUT")//" :: C(",M,",",N,")"
intent_label="IN"
ELSE
IF (PRESENT(padding).AND.padding) THEN
do_padding=.FALSE.
IF (PRESENT(padding)) THEN
IF (padding) do_padding=.TRUE.
ENDIF
IF (do_padding) THEN
write(6,'(A)') &
" "//trdat(data_type)//" :: C(M*N+8)"
ELSE
@ -130,43 +141,16 @@ CONTAINS
END SELECT
ENDIF
ELSE
write(6,'(A)') " "//trdat(data_type,"INOUT")//" :: C(*)"
write(6,'(A)') " "//trdat(data_type,"IN")//" :: B(*), A(*)"
IF (write_intent) THEN
write(6,'(A)') " "//trdat(data_type,"INOUT")//" :: C(*)"
write(6,'(A)') " "//trdat(data_type,"IN")//" :: B(*), A(*)"
ELSE
write(6,'(A)') " "//trdat(data_type)//" :: C(*)"
write(6,'(A)') " "//trdat(data_type)//" :: B(*), A(*)"
ENDIF
ENDIF
END SUBROUTINE write_matrix_defs
SUBROUTINE write_test_fun(M,N,K,transpose_flavor,data_type,stack_size_label)
INTEGER :: M,N,K,transpose_flavor,data_type
CHARACTER(LEN=*), OPTIONAL :: stack_size_label
write(6,'(A)') "FUNCTION TEST(X,"//TRIM(trparam(stack_size_label))//",Niter) RESULT(res)"
IF (PRESENT(stack_size_label)) THEN
CALL write_stack_params(data_type,stack_size_label)
ELSE
CALL write_matrix_defs(M,N,K,transpose_flavor,data_type,.FALSE.)
ENDIF
write(6,'(A)') " INTEGER :: Niter"
write(6,'(A)') " REAL :: t2,t1,res"
write(6,'(A)') " INTERFACE"
write(6,'(A)') " SUBROUTINE X("//TRIM(trparam(stack_size_label))//")"
IF (PRESENT(stack_size_label)) THEN
CALL write_stack_params(data_type,stack_size_label)
ELSE
CALL write_matrix_defs(M,N,K,transpose_flavor,data_type,.FALSE.)
ENDIF
write(6,'(A)') " END SUBROUTINE"
write(6,'(A)') " END INTERFACE"
write(6,'(A)') " INTEGER :: i"
write(6,'(A)') " CALL CPU_TIME(t1)"
write(6,'(A)') " DO i=1,Niter"
write(6,'(A)') " CALL X("//TRIM(trparam(stack_size_label))//")"
write(6,'(A)') " ENDDO"
write(6,'(A)') " CALL CPU_TIME(t2)"
write(6,'(A)') " res=REAL(t2-t1,KIND=KIND(res))"
write(6,'(A)') "END FUNCTION"
END SUBROUTINE
SUBROUTINE smm_inner(mi,mf,ni,nf,ki,kf,iloop,mu,nu,ku,transpose_flavor,data_type)
INTEGER :: mi,mf,ni,nf,ki,kf,iloop,mu,nu,ku,transpose_flavor,data_type
INTEGER :: im,in,ik,ido

View file

@ -15,11 +15,10 @@ PROGRAM small_gen
USE multrec_gen
IMPLICIT NONE
INTEGER :: M,N,K,nline,iline,max_dim, best_square(4),i,isquare, opts(4), transpose_flavor, data_type, SIMD_size
INTEGER, DIMENSION(:,:), ALLOCATABLE :: tiny_opts
REAL, DIMENSION(:), ALLOCATABLE :: tiny_perf,square_perf
CHARACTER(LEN=1024) :: arg,filename,line,label
INTEGER :: ibest_square=3
CHARACTER(LEN=1024) :: arg,filename,label
INTEGER :: M,N,K,transpose_flavor,data_type,SIMD_size
INTEGER :: ibest_square=3, best_square=4
INTEGER :: isquare
CALL GET_COMMAND_ARGUMENT(1,arg)
READ(arg,*) M
@ -48,73 +47,49 @@ PROGRAM small_gen
CALL mult_versions(M,N,K,3,label,transpose_flavor,data_type,SIMD_size,filename)
! generation of the multrec versions (4)
DO isquare=1,SIZE(best_square)
write(label,'(A,I0)') "_",ibest_square+isquare
CALL mult_versions(M,N,K,ibest_square+isquare,label,transpose_flavor,data_type,SIMD_size,filename)
DO isquare=ibest_square+1,ibest_square+best_square
write(label,'(A,I0)') "_",isquare
CALL mult_versions(M,N,K,isquare,label,transpose_flavor,data_type,SIMD_size,filename)
ENDDO
! generation of the vector version,
! only in the case of SIMD_size=32(i.e. AVX) and SIMD_size=64(i.e. MIC)
IF ((SIMD_size==32 .OR. SIMD_size==64) .AND. transpose_flavor==1 .AND. data_type<=2) THEN
ibest_square=ibest_square+1
write(label,'(A,I0)') "_",ibest_square+SIZE(best_square)
CALL mult_versions(M,N,K,ibest_square+SIZE(best_square),label,&
write(label,'(A,I0)') "_",ibest_square+best_square
CALL mult_versions(M,N,K,ibest_square+best_square,label,&
transpose_flavor,data_type,SIMD_size,filename,stack_size_label="")
ENDIF
! test function
CALL write_test_fun(M,N,K,transpose_flavor,data_type)
! test program
write(6,'(A)') " PROGRAM small_find"
write(6,'(A)') " IMPLICIT NONE"
write(6,'(A,I0,A,I0,A,I0,A,I0)') " INTEGER, PARAMETER :: M=",&
M,",N=",N,",K=",K,",Nmin=5,versions=",ibest_square+SIZE(best_square)
write(6,'(A,I0,A,I0,A,I0,A)') "SUBROUTINE small_find_",M,"_",N,"_",K,"(unit)"
write(6,'(A)') " IMPLICIT NONE"
write(6,'(A)') " INTEGER :: unit ! Output unit"
write(6,'(A,I0,A,I0,A,I0,A,I0)') " INTEGER, PARAMETER :: M=",M,",N=",N,",K=",K
write(6,'(A)') " CHARACTER(len=64) :: filename"
CALL write_matrix_defs(M,N,K,transpose_flavor,data_type,.FALSE.,padding=.TRUE.)
write(6,'(A)') " REAL :: timing(versions), best_time, test"
write(6,'(A)') " REAL(KIND=KIND(0.D0)) :: flops,gflop"
write(6,'(A)') " INTEGER :: imin,Niter,iloop,best_loop"
write(6,'(A)') " INTERFACE"
write(6,'(A)') " SUBROUTINE X("//TRIM(trparam())//")"
write(6,'(A)') " INTERFACE"
write(6,'(A)') " SUBROUTINE X("//TRIM(trparam())//")"
CALL write_matrix_defs(data_type=data_type,write_intent=.TRUE.)
write(6,'(A)') " END SUBROUTINE"
write(6,'(A)') " END INTERFACE"
DO i=1,ibest_square+SIZE(best_square)
write(6,'(A)') " END SUBROUTINE"
write(6,'(A)') " END INTERFACE"
DO isquare=1,ibest_square+best_square
write(6,'(A,I0,A,I0,A,I0,A,I0)') "PROCEDURE(X) :: smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,"_",i
M,"_",N,"_",K,"_",isquare
ENDDO
write(6,'(A)') ""
write(6,'(A)') " flops=2*REAL(M,KIND=KIND(0.D0))*N*K"
write(6,'(A)') " gflop=1000.0D0*1000.0D0*1000.0D0"
write(6,'(A)') " ! assume we would like to do 1 Gflop for testing a subroutine"
write(6,'(A)') " Niter=MAX(1,CEILING(MIN(100000000.0D0,1*gflop/flops)))"
write(6,'(A)') ""
write(6,'(A)') " best_time=HUGE(best_time)"
write(6,'(A)') " timing=best_time"
write(6,'(A)') " C=0 ; A=0 ; B=0 "
write(6,'(A)') ""
write(6,'(A)') " DO imin=1,Nmin"
DO i=1,ibest_square+SIZE(best_square)
write(6,*) " timing(",i,")= &"
write(6,*) " MIN(timing(",i,"), &"
write(6,'(A,I0,A,I0,A,I0,A,I0,A)') " TEST(smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,"_",i,",A,B,C,Niter))"
write(6,*) 'write(6,''(1I4,F12.6,F12.3)'') ',i,',&'
write(6,*) "timing(",i,"),&"
write(6,*) "flops*Niter/gflop/timing(",i,")"
write(6,'(A,I0,A,I0)') " INTEGER, PARAMETER :: Nmin=5,Nk=1,Nloop=",ibest_square+best_square
write(6,'(A)') " TYPE t_kernels"
write(6,'(A)') " PROCEDURE(X), POINTER, NOPASS :: ptr"
write(6,'(A)') " END TYPE t_kernels"
write(6,'(A)') " TYPE(t_kernels) :: kernels(Nk,Nloop)"
write(6,'(A)') " INTEGER :: mnk(3,Nk) ! mu, nu, ku"
DO isquare=1,ibest_square+best_square
write(6,'(A,I0,A,I0,A,I0,A,I0,A,I0)') " kernels(Nk,",isquare,")%ptr => smm_"//trstr(transpose_flavor,data_type)//"_",&
M,"_",N,"_",K,"_",isquare
ENDDO
write(6,'(A)') " ENDDO"
write(6,'(A)') ""
write(6,'(A)') " DO iloop=1,versions"
write(6,'(A)') " IF (timing(iloop)< best_time) THEN"
write(6,'(A)') " best_time=timing(iloop)"
write(6,'(A)') " best_loop=iloop"
write(6,'(A)') " ENDIF"
write(6,'(A)') " ENDDO"
write(6,'(A84)') ' write(6,''(1I4,F12.6,F12.3)'') '//&
'best_loop, best_time, (flops*Niter/best_time)/gflop'
write(6,'(A)') "END PROGRAM small_find"
write(6,'(A,I0,A,I0,A,I0,A)') " filename='small_find_",M,"_",N,"_",K,".out'"
write(6,'(A)') " C = 0 ; A = 0 ; B = 0"
write(6,'(A)') " mnk=0"
write(6,'(A)') " CALL run_kernels(filename,unit,M,N,K,A,B,C,Nmin,Nk,Nloop,kernels,mnk)"
write(6,'(A,I0,A,I0,A,I0)') "END SUBROUTINE small_find_",M,"_",N,"_",K
END PROGRAM small_gen

View file

@ -2,9 +2,10 @@ PROGRAM tiny_gen
USE mults
IMPLICIT NONE
INTEGER :: M,N,K
INTEGER :: M,N,K,transpose_flavor,data_type
INTEGER, PARAMETER :: Nloop=6
CHARACTER(LEN=80) :: arg
INTEGER :: mi,mf,ni,nf,ki,kf,iloop,ku,nu,mu,transpose_flavor, data_type
INTEGER :: Nk,mi,mf,ni,nf,ki,kf,iloop,ku,nu,mu
CALL GET_COMMAND_ARGUMENT(1,arg)
READ(arg,*) M
@ -16,56 +17,31 @@ PROGRAM tiny_gen
READ(arg,*) transpose_flavor
CALL GET_COMMAND_ARGUMENT(5,arg)
READ(arg,*) data_type
CALL loop_variants(1)
CALL write_test_fun(M,N,K,transpose_flavor,data_type)
write(6,'(A)') " PROGRAM tiny_find"
write(6,'(A)') " IMPLICIT NONE"
write(6,'(A,I0,A,I0,A,I0,A)') " INTEGER, PARAMETER :: M=",M,",N=",N,",K=",K,",Nmin=2"
write(6,'(A,I0,A,I0,A,I0,A)') "SUBROUTINE tiny_find_",M,"_",N,"_",K,"(unit)"
write(6,'(A)') " IMPLICIT NONE"
write(6,'(A)') " INTEGER :: unit ! Output unit"
write(6,'(A,I0,A,I0,A,I0)') " INTEGER, PARAMETER :: M=",M,",N=",N,",K=",K
write(6,'(A)') " CHARACTER(len=64) :: filename"
CALL write_matrix_defs(M,N,K,transpose_flavor,data_type,.FALSE.)
write(6,'(A)') " REAL :: timing(6,M,N,K), best_time, test"
write(6,'(A)') " REAL(KIND=KIND(0.D0)) :: flops,gflop"
write(6,'(A)') " INTEGER :: imin,Niter,iloop,i,j,l, best_loop,best_mu, best_nu, best_ku"
write(6,'(A)') " INTERFACE"
write(6,'(A)') " SUBROUTINE X(A,B,C)"
CALL write_matrix_defs(M,N,K,transpose_flavor,data_type,.FALSE.)
write(6,'(A)') " END SUBROUTINE"
write(6,'(A)') " END INTERFACE"
write(6,'(A)') " INTERFACE"
write(6,'(A)') " SUBROUTINE X(A,B,C)"
CALL write_matrix_defs(data_type=data_type,write_intent=.FALSE.)
write(6,'(A)') " END SUBROUTINE"
write(6,'(A)') " END INTERFACE"
CALL loop_variants(2)
write(6,'(A)') ""
write(6,'(A)') " flops=2*REAL(M,KIND=KIND(0.D0))*N*K"
write(6,'(A)') " gflop=1000.0D0*1000.0D0*1000.0D0"
write(6,'(A)') " ! assume we would like to do 1 Gflop for testing a subroutine"
write(6,'(A)') " Niter=MAX(1,CEILING(MIN(100000000.0D0,1*gflop/flops)))"
write(6,'(A)') ""
write(6,'(A)') " best_time=HUGE(best_time)"
write(6,'(A)') " timing=best_time"
write(6,'(A)') " C=0 ; A=0 ; B=0 "
write(6,'(A)') ""
write(6,'(A)') " DO imin=1,Nmin"
write(6,'(A,I0,A,I0)') " INTEGER, PARAMETER :: Nmin=2, Nk=",Nk,", Nloop=",Nloop
write(6,'(A)') " TYPE t_kernels"
write(6,'(A)') " PROCEDURE(X), POINTER, NOPASS :: ptr"
write(6,'(A)') " END TYPE t_kernels"
write(6,'(A)') " TYPE(t_kernels) :: kernels(Nk,Nloop)"
write(6,'(A)') " INTEGER :: mnk(3,Nk) ! mu, nu, ku"
CALL loop_variants(3)
write(6,'(A)') " ENDDO"
write(6,'(A)') ""
write(6,'(A)') " DO iloop=1,6"
write(6,'(A)') " DO i=1,M"
write(6,'(A)') " DO j=1,N"
write(6,'(A)') " DO l=1,K"
write(6,'(A)') " IF (timing(iloop,i,j,l)< best_time) THEN"
write(6,'(A)') " best_time=timing(iloop,i,j,l)"
write(6,'(A)') " best_loop=iloop"
write(6,'(A)') " best_mu=i"
write(6,'(A)') " best_nu=j"
write(6,'(A)') " best_ku=l"
write(6,'(A)') " ENDIF"
write(6,'(A)') " ENDDO"
write(6,'(A)') " ENDDO"
write(6,'(A)') " ENDDO"
write(6,'(A)') " ENDDO"
write(6,'(A112)') ' write(6,''(4I4,F12.6,F12.3)'') '//&
'best_loop,best_mu, best_nu, best_ku, best_time, (flops*Niter/best_time)/gflop'
write(6,'(A)') "END PROGRAM tiny_find"
write(6,'(A,I0,A,I0,A,I0,A)') " filename='tiny_find_",M,"_",N,"_",K,".out'"
write(6,'(A)') " C = 0 ; A = 0 ; B = 0"
write(6,'(A)') " CALL run_kernels(filename,unit,M,N,K,A,B,C,Nmin,Nk,Nloop,kernels,mnk)"
write(6,'(A,I0,A,I0,A,I0)') "END SUBROUTINE tiny_find_",M,"_",N,"_",K
CONTAINS
@ -74,8 +50,11 @@ CONTAINS
INTEGER :: itype
mi=1 ; mf=M ; ni=1 ; nf=N ; ki=1 ; kf=K
DO iloop=1,6
DO iloop=1,Nloop
IF (itype.ne.1) Nk=0
DO mu=1,M
DO nu=1,N
DO ku=1,K
@ -83,9 +62,10 @@ CONTAINS
IF (MOD(M,mu).NE.0) CYCLE
IF (MOD(N,nu).NE.0) CYCLE
IF (MOD(K,ku).NE.0) CYCLE
! do not unroll with more than 32 mults in the inner loop (some more unrolling can be faster, but we reduce size and speedup lib generation)
! do not unroll with more than 32 mults in the inner loop
! (some more unrolling can be faster, but we reduce size and speedup lib generation)
IF (mu*nu*ku>32) CYCLE
SELECT CASE(itype)
CASE(1)
write(6,'(A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A)') &
@ -96,25 +76,26 @@ CONTAINS
CALL smm_inner(mi,mf,ni,nf,ki,kf,iloop,mu,nu,ku,transpose_flavor,data_type)
write(6,'(A)') " END SUBROUTINE"
CASE(2)
Nk=Nk+1
write(6,'(A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0)') &
" PROCEDURE(X) :: smm_"//trstr(transpose_flavor,data_type)//"_",M,"_",N,"_",K,"_",iloop, &
" PROCEDURE(X) :: smm_"//trstr(transpose_flavor,data_type)//"_",M,"_",N,"_",K,"_",iloop, &
"_",mu,"_",nu,"_",ku
CASE(3)
write(6,'(A,I0,A,I0,A,I0,A,I0,A)') " timing(",iloop,",",mu,",",nu,",",ku,")= &"
write(6,'(A,I0,A,I0,A,I0,A,I0,A)') " MIN(timing(",iloop,",",mu,",",nu,",",ku,"), &"
write(6,'(A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A)') &
" TEST(smm_"//trstr(transpose_flavor,data_type)//"_",M,"_",N,"_",K,"_", &
iloop,"_",mu,"_",nu,"_",ku,",A,B,C,Niter))"
write(6,'(A,I0,A,I0,A,I0,A,I0,A)') 'write(6,''(4I4,F12.6,F12.3)'') ',iloop,",",mu,",",nu,",",ku,",& "
write(6,'(A,I0,A,I0,A,I0,A,I0,A)') "timing(",iloop,",",mu,",",nu,",",ku,"),&"
write(6,'(A,I0,A,I0,A,I0,A,I0,A)') "flops*Niter/gflop/timing(",iloop,",",mu,",",nu,",",ku,")"
Nk=Nk+1
write(6,'(A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0,A,I0)') " kernels(",Nk,",",iloop, &
")%ptr => smm_"//trstr(transpose_flavor,data_type)//"_",M,"_",N,"_",K,"_", &
iloop,"_",mu,"_",nu,"_",ku
IF (iloop.eq.1) THEN
write(6,'(A,I0,A,I0,A,I0,A,I0,A,I0,A,I0)') " mnk(1,",Nk,")=",mu, &
" ; mnk(2,",Nk,")=",nu, &
" ; mnk(3,",Nk,")=",ku
END IF
END SELECT
ENDDO
ENDDO
ENDDO
ENDDO
END SUBROUTINE loop_variants
END PROGRAM tiny_gen