Gostaria de saber como "%" funciona em uma matriz de tipo derivado.
Eu escrevi o seguinte programa onde o operador "+" foi sobrecarregado:
module my_mod
implicit none
type :: my_type
real :: r1 , r2
integer :: i1, i2
end type my_type
interface operator(+)
module procedure add_my_type_vec, add_my_type
end interface
contains
function add_my_type_vec(a,b) result(res)
type(my_type), dimension(:),intent(in) :: a, b
type(my_type), dimension(size(a)) :: res
res%r1 = a%r1 + b%r1
res%r2 = a%r2 + b%r2
res%i1 = a%i1 + b%i1
res%i2 = a%i2 + b%i2
end function add_my_type_vec
function add_my_type(a,b) result(res)
type(my_type), intent(in) :: a, b
type(my_type) :: res
res%r1 = a%r1 + b%r1
res%r2 = a%r2 + b%r2
res%i1 = a%i1 + b%i1
res%i2 = a%i2 + b%i2
end function add_my_type
end module my_mod
program my_pgm
use my_mod
implicit none
type(my_type),allocatable, dimension(:) :: my_vec1, my_vec2, my_vec3
write(*,*) "Sum on type : ", my_type(1.0, 2.0, 1, 2) + my_type(1.0, 2.0, 1, 2)
allocate(my_vec1(1000), my_vec2(1000), my_vec3(1000))
my_vec1 = my_type(1.0, 2.0, 1, 2)
my_vec2 = my_type(1.0, 2.0, 1, 2)
my_vec3 = my_vec1 + my_vec2
write(*,*) "Sum on vec of type : ", my_vec3(123)
end program my_pgm
Na add_my_type_vec
função, como %
funciona? Por exemplo, na instrução res%r1 = a%r1 + b%r1
, há a criação (ou seja, cópia de memória) de dois arrays de reais que contêm apenas a%r1
e b%r1
, a soma é feita nesses arrays e, em seguida, a atribuição é feita em res%r1
? Acho que é mais complicado.
Os compiladores geralmente são livres para criar matrizes temporárias sempre que quiserem. 1 Há momentos, como você verá em muitos posts aqui, em que cópias temporárias são efetivamente necessárias ou altamente desejáveis. Esta outra resposta , por exemplo, aborda alguns casos.
Como exemplo prático, este é um caso em que o pensamento pode ser elaborado, em vez de uma resposta estrita ser dada.
Em todo o código aqui, há muitas oportunidades para que um array temporário seja criado em torno de
add_my_type_vec
, como:a
eb
podem ser copiadosa%r1
,b%r1
, etc.) poderiam ser copiadasa%r1+b%r1
, etc.) poderia ir para um local temporárioNenhuma cópia de qualquer classe é necessária aqui, nem é particularmente benéfica.
Os argumentos fictícios
a
eb
assumem a forma, e o compilador sabe que eles são (simplesmente) contíguos.A matriz
a%r1
(etc.) não é contígua, mas tem um passo constante: um compilador pode facilmente lidar com o passo a passo entre os elementos.O lado esquerdo de
res%r1 = a%r1 + b%r1
não aparece de forma alguma (especialmente de forma complicada) no lado direito: a avaliação do lado direito não afeta o lado esquerdo, portanto, nenhuma cópia é necessária para manter a avaliação antes da atribuição.Da mesma forma, com a atribuição do resultado da função, não há interações entre nenhuma das variáveis envolvidas.
Não há necessidade de contornar as demandas de finalização em lugar nenhum.
Basicamente, tudo o que o compilador precisa fazer é iterar sobre os elementos de
my_vec3
e atribuir diretamente a soma em pares dos elementos demy_vec1
emy_vec2
, percorrendo essas matrizes de maneiras bem chatas.Um compilador poderia decidir fazer qualquer uma das cópias se achasse sensato ou se quisesse jogar com mais segurança. Normalmente, você pode pedir a um compilador para lhe informar (em tempo de compilação ou execução) quando estiver fazendo uma cópia ou se estiver implementando o mecanismo necessário para permitir uma decisão de cópia.
O único motivo para se preocupar com os componentes (uso de
%
) é que os arrays referenciados não são contíguos. Fortran é muito capaz de lidar com arrays não contíguos sem exigir cópias. Restrições a arrays "complicados" e usos "complicados" de componentes existem para tornar o tratamento desses casos comuns realmente muito chato (para compiladores).1 Um compilador é necessário para garantir que os efeitos em um temporário sejam efeitos em uma entidade associada a esse temporário. Por exemplo, se houver uma cópia temporária de um argumento fictício, o argumento real deve refletir quaisquer alterações (apropriadas). As regras do Fortran sobre aliasing são restrições em programas que facilitam muito a tarefa do compilador de determinar quando os efeitos precisam ser visíveis em técnicas como copy-in/copy-out.
Em operações de array
res(:)%r1 = a(:)%r1 + b(:)%r1
como as deadd_my_type_vec()
, espero que qualquer compilador otimizador decente NÃO crie arrays temporários.Ainda assim, essa abordagem provavelmente não é a ideal em termos de acesso à memória:
a(:)%r1
é um array não contíguo com um stride de 4 (porque você tem 4 unidades de armazenamento numérico em seu tipo derivado), e instruções vetoriais, pelo que sei, são menos eficientes em arrays com strides. Além disso, ao executar sequencialmente as 4 operações de array, apenas 25% do conteúdo de uma linha de cache é usado antes que a linha de cache seja apagada e recarregada posteriormente.Por outro lado, somando elemento por elemento (do tipo derivado array), os caches são melhor aproveitados, mas não há como aproveitar as instruções do vetor.
No final, somente um benchmark pode dizer qual é a abordagem mais eficiente... para a combinação de máquina/compilador que você está usando.
Observe que você não precisa escrever duas rotinas separadas aqui. Você pode escrever uma única
elemental
rotina que lidará com as adições escalares e de array (o que o compilador fará nos bastidores não é óbvio, no entanto).