openlang

호출마다 배열 단면을 복사하던 gfortran 루프

행렬의 행을 도는 루프에서 반복마다 행 a(i, :)를 넘겨 서브루틴을 호출한다. 답은 맞고 속도는 민망하다. 시간은 산술이 아니라 복사에 들어간다.

subroutine scale(v, n)
  integer, intent(in) :: n
  real, intent(inout) :: v(n)
  v = 2.0 * v
end subroutine scale

! caller, a is m by n
do i = 1, m
  call scale(a(i, :), n)
end do

v(n) 같은 명시적 형상 가인수는 연속된 메모리 한 덩어리를 전제한다. Fortran에서 행은 연속이 아니고, 이웃한 원소 사이가 열 하나만큼 떨어져 있다. 그래서 gfortran은 호출마다 임시 배열을 만들어 행을 복사해 넣고, 호출이 끝나면 다시 복사해 낸다. 반복마다 복사가 두 번이고, 기본 설정으로는 아무 말도 없다.

-Warray-temporaries는 컴파일할 때 그 자리를 짚어 주고, -fcheck=array-temps는 실행 중에 임시 배열을 알려 준다. 둘 다 이제 디버그 빌드에 들어 있다. 이런 서브루틴을 전부 v(:)로 고칠지, 루프 순서를 바꿀지는 아직 못 정했다.

영어에서 번역원문 보기

이어지는 대화 2

서로의 근거를 따라
  1. 고유 링크

    가인수를 가정 형상 v(:)로 바꾼다. 그러면 호출하는 쪽이 스트라이드가 담긴 디스크립터를 넘기고 복사는 없다.

    module ops
    contains
      subroutine scale(v)
        real, intent(inout) :: v(:)
        v = 2.0 * v
      end subroutine scale
    end module ops

    use ops 뒤에 호출은 call scale(a(i, :))가 된다. 명시적 인터페이스가 필요하고 모듈이 그것을 준다. 없으면 그 호출은 틀린 것이고, 컴파일러가 알아챌 의무도 없다.

    복사는 사라진다. 스트라이드는 남는다. 서브루틴은 여전히 열 하나씩 건너뛰며 메모리를 읽는다. 루프 순서를 바꿔도 된다면 a(:, j)로 열을 돌아라.

    영어에서 번역원문 보기
  2. 고유 링크

    재밌는 부작용이 있어요. 그 v(:) 가인수에 contiguous를 붙이면 복사가 돌아와요. 이 속성은 가인수의 원소가 붙어 있다고 컴파일러에게 약속하는 거라서, a(i, :)처럼 스트라이드가 있는 실인수는 v(n)일 때와 똑같이 호출 전에 임시 배열로 묶어야 해요. contiguous는 벡터화에 도움이 될 수 있어서 곧잘 붙이는데, 여기서는 하필 이 일을 시작하게 한 그 호출들에서 pgr의 해결책을 조용히 무르게 돼요. 루프를 고쳤다고 하기 전에 소스에서 grep으로 한번 찾아보겠어요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. CPU 한도를 따라가는 컨테이너 안의 GOMAXPROCS

    Go 1.25 전에는 GOMAXPROCS 기본값이 머신의 논리 CPU 개수였어요. 논리 CPU가 32개인 호스트에서 CPU 두 개로 제한된 컨테이너도 32를 받았어요. P 32개가 고루틴을 돌리는데 쿼터는 주기마다 CPU 두…

    답글 8go
  2. try와 catch unreachable 비교는 서로 다른 두 프로그램 비교다

    try와 catch unreachable은 같은 약속을 하지 않아요. 그래서 둘을 맞대어 시간을 재면 서로 다른 프로그램 두 개를 재는 셈이에요. 두 버전 모두 오류 유니언을 반환하는 함수를 불러요. 한쪽은 try로 실패를…

    답글 4zig
  3. 이 큐에 개수를 캐시하는 필드가 필요할까요?

    호출자가 이 큐에 묻는 건 List.length items > 8 하나뿐이고, 그걸 계속 반복해서 물어요. 큐는 불변 OCaml 리스트 하나이고, 갱신할 때마다 새 큐를 돌려줘요. 선택지는 두 가지예요. 정확한 개수를 들고…