openlang

단순한 루프가 너무 느려요, gfortran, 200000 x 64 배열

작은 보정 도구인데 코드는 아래에 있어요. 출력은 맞는데 너무 느려요. detrend+clip은 몇 초씩 걸리고, rms도 원래보다 느려요. 요소당 곱셈 한 번, 덧셈 한 번뿐인데도요. -O3랑 -march=native를 써봤는데 별로 달라지지 않았어요. Apple silicon에서 sum이랑 real(dp)가 문제인가 의심했어요. 이 느린 두 단계는 같은 문제일까요, 다른 문제일까요? 왜 이렇게 오래 걸리는지 모르겠어요.

Makefile

FC = gfortran
FFLAGS = -O3 -march=native -g

sensor_cal: src/sensor_mod.f90 src/main.f90
	$(FC) $(FFLAGS) -o $@ $^

clean:
	rm -f sensor_cal *.mod

src/sensor_mod.f90

module sensor_mod
  implicit none
  integer, parameter :: dp = kind(1.0d0)
contains

  ! remove the mean of one record (all channels at one time step)
  subroutine detrend_record(v, n)
    integer, intent(in) :: n
    real(dp), intent(inout) :: v(n)
    real(dp) :: m
    m = sum(v) / n
    v = v - m
  end subroutine detrend_record

  subroutine clip_record(v, n, lim)
    integer, intent(in) :: n
    real(dp), intent(inout) :: v(n)
    real(dp), intent(in) :: lim
    integer :: k
    do k = 1, n
      if (v(k) > lim) v(k) = lim
      if (v(k) < -lim) v(k) = -lim
    end do
  end subroutine clip_record

  function channel_rms(a, nrec, nchan) result(r)
    integer, intent(in) :: nrec, nchan
    real(dp), intent(in) :: a(nrec, nchan)
    real(dp) :: r(nchan)
    integer :: i, j
    r = 0.0_dp
    do i = 1, nrec
      do j = 1, nchan
        r(j) = r(j) + a(i, j)**2
      end do
    end do
    r = sqrt(r / nrec)
  end function channel_rms

end module sensor_mod

src/main.f90

program sensor_cal
  use sensor_mod
  implicit none
  integer :: nrec, nchan, i, t0, t1, rate
  real(dp), allocatable :: readings(:,:), rms(:)
  character(len=32) :: arg
  logical :: verbose

  verbose = .false.
  nrec = 200000
  nchan = 64

  if (command_argument_count() >= 1) then
    call get_command_argument(1, arg)
    read(arg, *) nrec
  end if
  if (command_argument_count() >= 2) then
    call get_command_argument(2, arg)
    read(arg, *) nchan
  end if
  if (command_argument_count() >= 3) verbose = .true.

  allocate(readings(nrec, nchan), rms(nchan))
  call random_number(readings)
  readings = 100.0_dp * readings

  call system_clock(t0, rate)
  do i = 1, nrec
    call detrend_record(readings(i, :), nchan)
    call clip_record(readings(i, :), nchan, 40.0_dp)
    if (verbose .and. mod(i, 50000) == 0) print *, 'record', i
  end do
  call system_clock(t1)
  print '(a,f8.3,a)', 'detrend+clip: ', real(t1 - t0) / rate, ' s'

  call system_clock(t0)
  rms = channel_rms(readings, nrec, nchan)
  call system_clock(t1)
  print '(a,f8.3,a)', 'rms:          ', real(t1 - t0) / rate, ' s'

  print '(a,3f10.4)', 'first rms values: ', rms(1:3)

  ! TODO: write rms to calibration file, format not agreed with the lab yet
end program sensor_cal

어셈블리는 안 봤고 경고도 안 켜봤어요. 위 시간은 정확한 값이 아니라 기억나는 대로 적은 거예요.

영어에서 번역원문 보기

이어지는 대화 2

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

    두 개가 아니라 한 가지 문제예요. Fortran은 column-major라서 readings(i,:)는 stride가 있는 접근이고, 호출할 때마다 임시 배열로 복사돼요. rms에서는 루프 순서를 바꿔보세요. 더 좋은 건 배열을 (nchan, nrec)로 선언하는 거예요. -Warray-temporaries로 컴파일하면 복사되는 부분을 볼 수 있어요.

    영어에서 번역원문 보기

같은 질문의 다른 갈래

  1. running_mean이 느려요, 시간이 산술 연산이 아니라 memmove에 쓰여요

    센서 데이터를 평활화하고 있어요. 배열은 readings(nstation, ntime)이고, station이 먼저예요. 데이터 파일이 그렇게 배치돼 있고 다른 도구들도 읽기 때문이에요. station에 대해 루프를 한 번 돌…

    답글 1최근 활동 fortran
  2. 어느 빌드 시간을 말하는 건가

    크레이트 분리 이야기는 늘 누군가 빌드 시간 하나를 올리는 것으로 끝난다. 숫자는 두 개고, 움직이는 방향도 다르다. 클린 빌드는 빈 target에서 시작하는 전체 빌드다. 크레이트를 나누면 이쪽은 오히려 조금 나빠질 수 있…

    답글 2최근 활동 rust
  3. 이 큐에 개수를 캐시하는 필드가 필요할까요?

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

    답글 2최근 활동 ocaml