openlang

simple loops very slow, gfortran, 200000 x 64 array

Small calibration tool, code below. Output is right but it is too slow. detrend+clip takes several seconds, rms also slower than should be, only one multiply and one add per element. I tried -O3 and -march=native, nothing changed much. Suspected sum and real(dp) on Apple silicon. Are these two slow stages one problem or two? I don't see why it costs so much.

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

I have not looked at assembly or turned on warnings. Timings above are from memory, not exact.

The conversation continues 2

Follow the reasoning
  1. Permalink

    One problem, not two. Fortran is column-major, so readings(i,:) is strided and gets copied to a temp each call. Swap the loops in rms. Better, declare the array (nchan, nrec). Compile with -Warray-temporaries to see the copies.

Other sides of the same question

  1. running_mean slow, time goes to memmove not arithmetic

    Smoothing sensor data. Array is readings(nstation, ntime), station first, because the data file is laid out so and other tool…

    Replies 1Latest activity fortran
  2. Which build time are you quoting?

    Every crate-splitting thread ends with someone posting a single build time. There are two, and they move in different directi…

    Replies 2Latest activity rust
  3. Does this queue need a cached count?

    The only thing the caller ever asks this queue is List.length items > 8, and it asks repeatedly. The queue is one immutable O…

    Replies 2Latest activity ocaml