Escrevi uma classe de filtro IIR com um estado de filtro interno e uma função elemento a elemento float filter(float x)
. Naturalmente, devido ao estado de filtro interno, ela filter()
só pode ser chamada nas amostras na ordem em que estão organizadas na sequência.
Se eu agora aplicar essa função a um Eigen Array, como
input.unaryExpr(filter);
a ordem de execução será unaryExpr()
sempre estritamente na ordem dos valores no array, ou pode ocorrer execução fora de ordem ou até mesmo paralelização?
Seria mais seguro escrever o loop explicitamente para garantir que a ordem seja sempre a esperada?
No momento, parece estar funcionando corretamente, mas não consigo encontrar nenhuma documentação explícita sobre seu comportamento.
Uma expressão unária, como todas as expressões próprias, é na verdade apenas um functor sofisticado que se comporta como um
Eigen::Matrix
orArray
, que permite alguma introspecção e pode ser solicitado para valores de índices individuais de linha e coluna. A ordem em que esses valores são solicitados é determinada pelo objeto ao qual os valores são atribuídos; essencialmente, o lado esquerdo da atribuição. Podemos demonstrar isso em 2D:A primeira atribuição imprime
0 3 1 4 2 5
, as outras duas imprimem0 1 2 3 4 5
. O motivo é queout1
usa a ordem padrão de coluna principal. Portanto, a ordem mais eficiente para preenchê-la com valores é coluna 0 linha 0, coluna 0 linha 1, coluna 1, linha 0, coluna 1 linha 1, … As outras duas matrizes de saída têm ordem de memória transposta, portanto, usam a ordem de atribuição transposta.Observe também que nada me impede de avaliar duas vezes. É claro que isso não é um uso regular, mas pode aparecer em códigos que de outra forma seriam inofensivos, como este modelo aqui:
Se eu chamar
derivative(in.unaryExpr(…))
, ele imprimirá0 1 3 4 1 2 4 5
, avaliando a maioria das entradas duas vezes.Resumindo: não acho uma boa ideia tornar as expressões com estado como você deseja, pelo menos em geral. Idealmente, as expressões devem ser idempotentes. Mas se for o código mais curto, você sabe o que está fazendo e adiciona alguns avisos para que o próximo programador também entenda as limitações, deve ficar tudo bem.
Em geral, o Eigen não realiza paralelismo automaticamente, exceto para multiplicações de matrizes e operações complexas semelhantes e conhecidas. Todo o resto é considerado pequeno, rápido e, em grande parte, limitado pela largura de banda da memória. Não consigo imaginar essa suposição mudando, exceto em uma nova versão principal, o Eigen4. E mesmo assim, eu não presumiria que esse fosse o comportamento padrão para expressões personalizadas. O custo de iniciar e interromper uma região paralela do OpenMP é alto demais para ser feito sem um bom conhecimento de que isso se justifica. E, por definição, o Eigen não tem ideia da complexidade computacional da sua expressão personalizada.
Em geral, a ordem de avaliação também será de frente para trás na ordem de armazenamento da saída na memória, pelo menos em casos simples. Isso é o que torna o cálculo rápido e a programação simples.
Pessoalmente, eu usaria apenas um método simples
std::transform
ou um loop para tornar a ordem das operações explícita. Iteradores para vetores e matrizes 1D são rápidos, o mesmo comcolwise()
iteradores em 2D.