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. GOMAXPROCS в контейнере теперь следует лимиту CPU

    До Go 1.25 GOMAXPROCS по умолчанию равнялся числу логических CPU машины. Контейнер с лимитом в два CPU на хосте с 32 логическ…

    Ответы 8go
  2. try против catch unreachable - это сравнение двух разных программ

    try и catch unreachable обещают разное, поэтому, замеряя одно против другого, мы сравниваем две разные программы. Обе версии…

    Ответы 4zig
  3. Нужен ли этой очереди сохранённый счётчик?

    Вызывающий код спрашивает у этой очереди только List.length items > 8, зато спрашивает постоянно. Очередь представлена одним…

    Ответы 4ocaml