C++Guns – RoboBlog

30.08.2018

FORTRAN: 3x3 matmul intrinsic vs. hand crafted

Filed under: Allgemein — Tags: — Thomas @ 20:08

Verglichen wird der erzeugte Assembler Code der internen Fortran matmul() Routine mit einer von Hand geschriebenen Matrix Multiplikation.
Getestet wird für 3x3 Matrizen mit zur Compilezeit bekannter Größe. Sonst könnte man den vom FORTRAN Compiler erzeugten Assembler Code gar nicht mehr nachvollziehen.

subroutine intrinsicmatmul(a,b,c) 
  implicit none
  real(8), intent(in) :: a(3,3), b(3,3)
  real(8), intent(inout) :: c(3,3)
  
  c = matmul(a,b)
end subroutine

Und hier von Hand

subroutine handmatmul(a,b,c)
  implicit none
  real(8), intent(in) :: a(3,3), b(3,3)
  real(8), intent(inout) :: c(3,3)    
  integer :: i,j,k
  
  c(:,:) = 0
  do i=1, 3
    do j=1, 3
      do k=1, 3
        c(j,i) = c(j,i) + a(j,k) * b(k,i)
      end do
    end do
  end do
end subroutine

Getestet wird mit gfortran 7.3.0 -O1. Die Hand Version hat 13 Instruktionen mehr. Hiervon sind 6 Instruktionen für das Sichern und Wiederherstellen von Register. Der Rest geht wohl auf eine andere Art Adressberechnung drauf. Da die intrinsic matmul Routine selbst bei -O1 geinlint wird, ich sehe kein Funktionsaufruf im Assembler Code, wird sie wohl schneller sein.
Tests mit Praxiscode zeigen aber, dass das nicht so sein muss. Es wurden mehrere Matrizen multipliziert.

Es gilt: messen, messen messen. Bei dem nächsten Compiler/CPU kann es wieder anders sein.

intrinsic

intrinsicmatmul_:
	movq	$0, (%rdx)  
	movq	$0, 8(%rdx) 
	movq	$0, 16(%rdx)
	movq	$0, 24(%rdx)
	movq	$0, 32(%rdx)
	movq	$0, 40(%rdx)
	movq	$0, 48(%rdx)
	movq	$0, 56(%rdx)
	movq	$0, 64(%rdx)
	leaq	24(%rdx), %rcx
	addq	$24, %rsi
	leaq	96(%rdx), %r9
	leaq	96(%rdi), %r10
.L4:
	leaq	24(%rdi), %rdx
	movq	%rsi, %r8
.L3:
	movsd	-24(%r8), %xmm1
	movl	$0, %eax
.L2:
	addq	$1, %rax
	movapd	%xmm1, %xmm0
	mulsd	-32(%rdx,%rax,8), %xmm0
	addsd	-32(%rcx,%rax,8), %xmm0
	movsd	%xmm0, -32(%rcx,%rax,8)
	cmpq	$3, %rax
	jne	.L2
	addq	$8, %r8
	addq	$24, %rdx
	cmpq	%r10, %rdx
	jne	.L3
	addq	$24, %rcx
	addq	$24, %rsi
	cmpq	%r9, %rcx
	jne	.L4
	rep ret
Hand

handmatmul_:
	pushq	%r12               # -
	pushq	%rbp               # |- Register r12, rbp, rbx für Laufvariablen i,j,k freimachen
	pushq	%rbx               # -
	movq	$0, (%rdx)         # -
	movq	$0, 8(%rdx)        # |
	movq	$0, 16(%rdx)       # |
	movq	$0, 24(%rdx)       # |
	movq	$0, 32(%rdx)       # |- c(:,:) = 0
	movq	$0, 40(%rdx)       # |
	movq	$0, 48(%rdx)       # |
	movq	$0, 56(%rdx)       # |
	movq	$0, 64(%rdx)       # -
	leaq	24(%rsi), %r9
	leaq	96(%rsi), %r12
	movq	%rdx, %rcx
	subq	%rsi, %rcx
	leaq	-24(%rcx), %rbp
	movq	%rcx, %rbx
.L4:                               # i Schleife Anfang
    leaq	0(%rbp,%r9), %r8
	movq	%rdi, %r10
	leaq	(%rbx,%r9), %rdx
.L3:                               # j Schleife Anfang
	movq	%r8, %r11 
	movsd	(%r8), %xmm1       # c(i,j) nach xmm1 laden
	movq	%rsi, %rax
	movq	%r10, %rcx
.L2:                               # k Schleife Anfang
	movsd	(%rcx), %xmm0      # -
	mulsd	(%rax), %xmm0      # |- c(j,i) + a(j,k) * b(k,i)
	addsd	%xmm0, %xmm1       # -
	addq	$24, %rcx          # 3x double = 24Byte
	addq	$8, %rax           # 1x double = 8byte. 
	cmpq	%r9, %rax          # rax wird gleichzeitig als Pointer und Laufvariable benutzt.
                               # In r9 steht der Wert den rax erreicht, wenn die Schleife terminiert
	jne	.L2                # k Schleife Ende
	movsd	%xmm1, (%r11)      # c(j,i) = xmm1 
	addq	$8, %r8
	addq	$8, %r10
	cmpq	%rdx, %r8
	jne	.L3                # j Schleife Ende
	addq	$24, %rsi
	addq	$24, %r9
	cmpq	%r12, %r9
	jne	.L4                # i Schleife Ende
	popq	%rbx               # -
	popq	%rbp               # |- Register aus dem Stack wieder herstellen
	popq	%r12               # -
	ret 

Für Optimierung O2 gilt 35 Instruktionen für intrinsic und 43 für Hand.

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Powered by WordPress