Fortran: динамические массивы против автоматического массива. Избегание выделения памяти
Профилируя один из наших кодов фортрана, есть две подпрограммы, которые занимают большую часть вычислительного времени (22,1% и 17,2%). В каждой подпрограмме ~5% времени тратится на выделение и освобождение памяти. Эти процедуры выглядят как
MODULE foo
CONTAINS
SUBROUTINE bar( ... )
...
IMPLICIT NONE
...
REAL, ALLOCATABLE, DIMENSION(:,:) :: work
...
ALLOCATE (work(size1,size2))
...
DEALLOCATE (work)
END SUBROUTINE bar
...
END MODULE foo
Эти подпрограммы вызываются порядка 4000-5000 раз в моем тесте, поэтому я хотел бы избавиться от ALLOCATE и DEALLOCATE. Изменение этих параметров на автоматические массивы приводит к изменению вывода профилировщика на.
MODULE foo
CONTAINS
SUBROUTINE bar( ... )
...
IMPLICIT NONE
...
REAL, DIMENSION(size1,size2) :: work
...
END SUBROUTINE bar
...
END MODULE foo
Изменяет результирующий профиль на
Running Time Symbol Name
20955.0ms 17.0% __totzsp_mod_MOD_totzsps
7.0ms 0.0% malloc
5.0ms 0.0% free
2.0ms 0.0% user_trap
16192.0ms 13.2% __tomnsp_mod_MOD_tomnsps
20.0ms 0.0% free
3.0ms 0.0% malloc
1.0ms 0.0% szone_size_try_large
Мне кажется, что gfortran размещает их в стеке, а не в куче, но меня беспокоит, когда это произойдет, когда эти массивы станут слишком большими.
Второй подход, который я использую, состоит в том, чтобы выделить и освободить эти массивы один раз.
work_array.f
MODULE work_array
IMPLICIT NONE
REAL(rprec), ALLOCATABLE, DIMENSION(:,:) :: work
END MODULE work_array
Я выделяю их один раз в другой части кода. Теперь моя подпрограмма выглядит так
MODULE foo
CONTAINS
SUBROUTINE bar( ... )
...
USE work_array
IMPLICIT NONE
...
END SUBROUTINE bar
...
END MODULE foo
Однако, когда я запускаю код, профиль ухудшается.
Running Time Symbol Name
30584.0ms 21.6% __totzsp_mod_MOD_totzsps
3494.0ms 2.4% free
3143.0ms 2.2% malloc
27.0ms 0.0% DYLD-STUB$$malloc_zone_malloc
19.0ms 0.0% szone_free_definite_size
6.0ms 0.0% malloc_zone_malloc
24325.0ms 17.1% __tomnsp_mod_MOD_tomnsps
2937.0ms 2.0% free
2456.0ms 1.7% malloc
23.0ms 0.0% DYLD-STUB$$malloc_zone_malloc
3.0ms 0.0% szone_free_definite_size
Откуда эти лишние мальлоки и освобождения? Как я могу настроить это так, чтобы я выделил эти массивы один раз?
2 ответа
Так как work
массив используется только внутри bar
подпрограмму, вы можете добавить save
приписать его и выделить его, когда подпрограмма вызывается в первый раз. Если work1
или же work2
отличается от предыдущих вызовов, в этом случае вы можете просто перераспределить массив.
Это оставляет проблему освобождения, когда подпрограмма больше не нужна. Если вам нужно вызывать его в течение всего времени жизни программы, это не проблема, так как ОС должна освободить память при выходе из программы. С другой стороны, если вам это нужно только во время инициализации, память останется выделенной, даже если она не нужна. Может быть, вы можете добавить в подпрограмму аргумент, который говорит ей освободить work
массив, если использование памяти является проблемой.
Если вы можете обойтись одним выделением при инициализации программы, то нет никаких оснований для определения массива как выделяемого. Поместите это в общее.
Если вам нужен только фиксированный размер, но вы не знаете этот размер до времени выполнения, вам нужно будет использовать окончательный вариант - одно выделение при инициализации. Однако не имеет смысла, что это увеличило удар производительности распределения. Мне нужно увидеть определение и код распределения, чтобы сказать больше.
Поскольку выделяется виртуальная память, "использование памяти" на самом деле не является проблемой, если только массив не настолько велик, что влияет на доступное адресное пространство.