Функция Fortran для перегрузки умножения между производными типами с выделяемыми компонентами

Предисловие

Для хранения полосатых матриц, полные аналоги которых могут иметь как строки, так и столбцы, проиндексированные из индексов, отличных от 1, я определил производный тип данных как

TYPE CDS
  REAL, DIMENSION(:,:), ALLOCATABLE :: matrix
  INTEGER, DIMENSION(2) :: lb, ub
  INTEGER :: ld, ud
END TYPE CDS

где CDS означает сжатое диагональное хранилище. Учитывая объявление TYPE(CDS) :: A,

  1. Компонент ранга 2 matrix должен содержать в качестве столбцов диагонали фактической полной матрицы (например, здесь, за исключением того, что я сохраняю диагонали как столбцы, а не как строки).
  2. Предполагается, что компоненты ld и ud содержат количество нижних и верхних диагоналей соответственно, то есть -lbound(A%matrix,2) и +ubound(A%matrix,2).
  3. Предполагается, что двухэлементные компоненты lb и ub содержат нижние и верхние границы фактической полной матрицы по двум измерениям. В частности, lb(1) и ub(1) должны совпадать с lbound(A%matrix,1) и lbound(A%matrix,2).

Как вы можете видеть в пунктах 2. и 3., производный тип содержит некоторую избыточную информацию, но меня это не волнует, так как это всего лишь 3 пары целых чисел. Кроме того, в коде, который я пишу, информация о границах и диапазоне фактической полной матрицы известна до заполнения матрицы. Поэтому я сначала присвоил значения компонентам ld, ud, lb и ub, а затем использовал эти компоненты для ALLOCATE компонента matrix (тогда я смогу правильно его заполнить).

Проблема

Мне нужно выполнить умножение матриц между такими разреженными матрицами, поэтому я написал FUNCTION для выполнения такого произведения и использовал его для перегрузки оператора *.

На данный момент функция выглядит следующим образом:

FUNCTION CDS_mat_x_CDS_mat(A, B)
IMPLICIT NONE
TYPE(CDS), INTENT(IN) :: A, B
TYPE(CDS) :: cds_mat_x_cds_mat

! determine the lower and upper bounds and band of the result based on those of the operands
CDS_mat_x_CDS_mat%lb(1) = A%lb(1)
CDS_mat_x_CDS_mat%ub(1) = A%ub(1)
CDS_mat_x_CDS_mat%lb(2) = B%lb(2)
CDS_mat_x_CDS_mat%ub(2) = B%ub(2)
CDS_mat_x_CDS_mat%ld = A%ld + B%ld
CDS_mat_x_CDS_mat%ud = A%ud + B%ud

! allocate the matrix component
ALLOCATE(CDS_mat_x_CDS_mat%matrix(CDS_mat_x_CDS_mat%lb(1):CDS_mat_x_CDS_mat%ub(1),&
                               & -CDS_mat_x_CDS_mat%ld:+CDS_mat_x_CDS_mat%ud))

! perform the product
:
:

END FUNCTION

Это означает, что если мне нужно выполнить продукт несколько раз, распределение будет выполнено несколько раз внутри функции. Я думаю, что это не хорошо с точки зрения производительности.

Я прошу предложений о том, как выполнить задачу ленточной разреженной матрицы, умноженной на лентовидную разреженную матрицу. Я хотел бы использовать тип, который я определил, потому что мне нужно, чтобы он был таким же общим, с точки зрения границ, как на данный момент. Но я мог бы изменить процедуру выполнения продукта (с FUNCTION на SUBROUTINE, если нужно).

Идеи

Я мог бы переписать процедуру как SUBROUTINE, чтобы объявить CDS_mat_x_CDS_mat с помощью INTENT(INOUT), выполнить назначение компонентов, отличных от matrix, а также распределение вне SUBROUTINE. Недостатком было бы то, что я не мог перегрузить оператор *.

Я заметил, что встроенная функция MATMUL может работать с любыми операндами ранга 2, какими бы ни были верхняя и нижняя границы двух измерений. Это означает, что выделение выполняется внутри функции. Я полагаю, что это эффективно (поскольку это встроенная функция). Отличие моей функции в том, что она принимает массивы ранга 2 любой формы, тогда как моя принимает объекты производных типов данных с компонентом массива ранга 2 любой формы.


comment
Вы профилировали, чтобы определить (относительную) стоимость этого распределения (что, кажется, вам нужно сделать ровно один раз, будь то в функции или перед входом в подпрограмму)? Но тогда, возможно, я не понимаю: вы предлагаете повторно использовать выделение при умножении более одного раза. Но почему бы просто не кэшировать результат и сделать?   -  person francescalus    schedule 06.07.2016


Ответы (1)


Встроенная функция MATMUL эквивалентна автоматическому (F2008 5.2.2) результату — форма результата выражается таким образом, что он становится характеристикой функции (F2008 12.3.3) — форма результата функции определяется в части спецификации функции, и (с точки зрения реализации) компилятор поэтому знает, как вычислить форму результата функции до фактического выполнения самой функции.

Как следствие, нет переменных ALLOCATABLE языка Фортран, связанных с эквивалентом результата встроенной функции MATMUL. Это не то же самое, что «отсутствие выделения памяти» - компилятору все еще может потребоваться выделить память за кулисами для своих собственных целей - для таких вещей, как временные выражения и тому подобное.

(Я сказал «эквивалент» выше, потому что встроенные процедуры по своей сути особенные, но представим на мгновение, что MATMUL был просто пользовательской функцией.)

Эквивалент такого автоматического результата для вашего случая может быть достигнут с помощью параметров типа длины. Это функция Fortran 2003 — тот же стандарт базового языка, в котором были представлены выделяемые компоненты, — но это еще не то, что было реализовано всеми активно поддерживаемыми компиляторами.

MODULE xyz
  IMPLICIT NONE

  TYPE CDS(lb1, ub1, ld, ud)
    INTEGER, LEN :: lb1, ub1, ld, ud
    REAL :: matrix(lb1:ub1, ld:ud)
    INTEGER :: lb2, ub2
  END TYPE CDS

  INTERFACE OPERATOR(*)
    MODULE PROCEDURE CDS_mat_x_CDS_mat
  END INTERFACE OPERATOR(*)
CONTAINS
  FUNCTION CDS_mat_x_CDS_mat(A, B) RESULT(C)
    TYPE(CDS(*,*,*,*)), INTENT(IN) :: A, B
    TYPE(CDS(A%lb1, A%ub1, A%ld+B%ld, A%ud+B%ud)) :: C

    C%lb2 = B%lb2
    C%ub2 = B%ub2

    ! perform the product.
    ! :

  END FUNCTION CDS_mat_x_CDS_mat
END MODULE xyz

Теоретически это дает компилятору больше возможностей для оптимизации, потому что перед вызовом функции у него есть больше возможностей для хранения, необходимого для результата функции. Приведет ли это на самом деле к лучшей производительности в реальном мире, зависит от реализации компилятора и характера ссылок на функции.

person IanH    schedule 06.07.2016
comment
Кажется, это именно то, что я искал. Всего два вопроса по этому поводу: (1) я прочитал классический пример параметризованных производных типов данных на Фортране Чепмена 95/2003, но я вижу не LEN после объявления параметров. Есть ли разница? (2) Могу ли я объявить 2-элементы ранга 1 lb и ub и использовать lb(1)use ub(1) в объявлении компонента matrix? - person Enlico; 07.07.2016
comment
Параметры типа для определяемых пользователем производных типов — это либо параметры типа KIND, которые должны быть указаны константами времени компиляции (и которые затем могут использоваться в ситуациях, требующих константы времени компиляции), либо параметры LEN, которые могут быть указаны во время выполнения. Параметры типа всегда скалярны. - person IanH; 07.07.2016
comment
Это должны были быть параметры типа для определения пользователем... - person IanH; 07.07.2016
comment
Если я понимаю о чем вы спрашиваете - то нет - нельзя. Спецификация массива для нераспределяемого массива компонентов без указателя не может напрямую зависеть от значения переменной, поэтому в примере в ответе используется параметр типа длины. Параметры типа всегда являются скалярными — они никогда не являются массивами. - person IanH; 07.07.2016
comment
Ах, да, я ошибочно прочитал ваши последние слова. Тип [я прочитал Kind] параметры всегда скалярны. В порядке! Я попробую с вашим ответом. - person Enlico; 07.07.2016