5 votes

Passer de manière optimale les dimensions d'un tableau de taille fixe dans julia

Je veux écrire une fonction qui prend une matrice en entrée. Il s'agit d'un appel de bas niveau fréquent dans un projet complexe, donc rendre cette fonction aussi rapide que possible a des implications potentiellement sérieuses en termes de performances. Parce que la vitesse est si importante pour moi, j'utilise les types de FixedSizeArrays car je sais que cela permettra d'économiser de la mémoire. Mais je connais souvent certaines propriétés de la matrice d'entrée, et je ne suis pas certain d'en faire une utilisation optimale.

Voici un exemple simple. Imaginons que la fonction Je veuille faire ce qui suit le plus rapidement possible :

using FixedSizeArrays

function foo( input::Mat )
# NB: Mat is the FixedSizeArrays matrix type
  return 2 * input
end

Il s'agit évidemment d'un exemple trivial, mais là n'est pas la question. L'important est que je sache quelque chose sur les dimensions de la matrice input Elle ne comporte toujours que deux colonnes et je peux toujours spécifier le nombre de lignes au moment de l'exécution. Cela semble être une information qui pourrait être transmise au compilateur pour rendre mon code plus rapide. Pourrais-je la passer comme un argument qui définit la taille de input d'une manière ou d'une autre ? Voici un exemple qui ne fonctionne pas, mais qui devrait vous donner une idée de ce que j'essaie de faire.

function bar( int::N, thismat::Mat{N,2,Float64} )
  return 2 * thismat
end

Y a-t-il quelque chose que je puisse faire ? Cela fonctionnerait-il même si je le pouvais ? Peut-être que FixedSizeArrays fait déjà tout ce qui peut être fait. Merci pour vos réflexions !

8voto

Fengyang Wang Points 8869

Les tableaux de taille fixe sont déjà spécialisés en fonction de la taille. Ces tableaux ne sont pas appropriés lorsque le nombre de lignes est variable, N dans votre cas, peut varier. Les problèmes de performance que vous constatez sont probablement dus à surspécialisation .

Permettez-moi d'être un peu plus précis.

Le compilateur Julia est capable de réaliser une abstraction à coût nul grâce à une spécialisation agressive sur les types d'arguments. Ainsi, en général (c'est-à-dire dans tous les cas à l'exception de quelques-uns où la spécialisation serait trop coûteuse ou est explicitement désactivée), si une fonction est appelée avec deux signatures de type différentes, deux versions de cette fonction seront compilées.

Étant donné que la taille d'un Mat fait partie de son type, ce qui signifie qu'une version sera compilée pour chaque taille possible de l'élément Mat . La spécialisation que vous recherchez est donc déjà réalisée.

La spécialisation n'est toutefois pas gratuite. Elle entraîne deux coûts :

  • La première fois qu'une fonction est appelée sur une signature particulière, de la mémoire est allouée et le compilateur doit s'exécuter.
  • Lorsqu'un paramètre dont le type ne peut être déduit est transmis à une fonction, il y a "instabilité de type" et une répartition dynamique est nécessaire. La répartition dynamique implique des recherches au moment de l'exécution.

Ainsi, si vos matrices sont de la taille (2, N) donde N varie et n'est pas connu au moment de la compilation, le coût de la performance de la répartition dynamique seront encourus. Ce coût de performance peut être limité en utilisant la technique de la barrière de fonction : nous n'encourons ce coût qu'une seule fois pour chaque appel de type instable, de sorte que la limitation du nombre de ces appels améliore la performance.

Mais ce qui augmenterait encore plus les performances serait d'éviter complètement cette répartition dynamique. Il est possible de construire un type de tableau qui n'encode que le nombre de colonnes dans le type, et qui a le nombre de lignes comme champ au moment de l'exécution. En d'autres termes, il est possible que votre problème de performance soit dû à une surspécialisation et que vous deviez créer vos types de manière à réduire le degré de spécialisation.

Trouver le bon équilibre est essentiel pour tirer le maximum de performances d'une application. La spécialisation sur la taille d'un tableau est en fait rarement utile - même les codes C et C++, par exemple, ont tendance à passer les tailles de tableau en tant que paramètres d'exécution, au lieu de se spécialiser sur une taille de tableau particulière. Cela n'est pas très coûteux. Dans la plupart des cas, FixedSizeArrays.jl n'améliorera pas les performances, mais les nuira au contraire. Il y a certainement des situations où cela peut aider, mais la vôtre n'est peut-être pas l'une d'entre elles.


Dans votre cas, pour une performance maximale, je pense qu'un type comme celui-ci serait le plus rapide :

immutable TwoColumnMatrix{T, BaseType} <: AbstractArray{T, 2}
    height::Int
    base::BaseType
end

function TwoColumnMatrix(A::Matrix)
    size(A, 2) == 2 || throw(ArgumentError("must be two columns"))
    TwoColumnMatrix{eltype(A), typeof(A)}(size(A, 1), A)
end

Base.@propagate_inbounds function getindex(M::TwoColumnMatrix, n::Int)
    M.base[n]
end

size(M::TwoColumnMatrix) = (M.height, 2)

Il se peut que vous deviez définir des méthodes supplémentaires pour obtenir des performances maximales et, comme toujours, procédez à des analyses comparatives. Il est possible que le surcoût du wrapper ne vaille pas la peine que le compilateur connaisse les dimensions.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X