Fonctions inline

Les fonctions inline sont des fonctions qui sont intégrées directement dans le code appelant.

Utilisation de fonctions inline

Lorsque vous utilisez des paramètres de type statique, toutes les fonctions paramétrables par les paramètres de type doivent être inline. Cela garantit que le compilateur peut résoudre ces paramètres de type. Lorsque vous utilisez des paramètres de type générique ordinaires, il n’existe aucune restriction de ce type.

Outre l’activation de l’utilisation des contraintes de membre, les fonctions inline peuvent être utiles pour optimiser le code. Toutefois, la surutilisation des fonctions inline peut rendre votre code moins résistant aux modifications apportées aux optimisations du compilateur et à l’implémentation des fonctions de bibliothèque. Pour cette raison, vous devez éviter d’utiliser des fonctions inline pour l’optimisation, sauf si vous avez essayé toutes les autres techniques d’optimisation. La création d’une fonction ou d’une méthode inline peut parfois améliorer les performances, mais ce n’est pas toujours le cas. Par conséquent, vous devez également utiliser des mesures de performances pour vérifier que la création d’une fonction donnée inline a en fait un effet positif.

Le inline modificateur peut être appliqué aux fonctions au niveau supérieur, au niveau du module ou au niveau de la méthode dans une classe.

L’exemple de code suivant illustre une fonction inline au niveau supérieur, une méthode d’instance inline et une méthode statique inline.

let inline increment x = x + 1
type WrapInt32() =
    member inline this.incrementByOne(x) = x + 1
    static member inline Increment(x) = x + 1

Fonctions inline et inférence de type

La présence d’une inférence de inline type affecte. Cela est dû au fait que les fonctions inline peuvent avoir des paramètres de type résolus statiquement, tandis que les fonctions non inline ne peuvent pas. L’exemple de code suivant montre un cas inline où il est utile, car vous utilisez une fonction qui a un paramètre de type résolu statiquement, l’opérateur float de conversion.

let inline printAsFloatingPoint number =
    printfn "%f" (float number)

Sans le modificateur, l’inférence inline de type force la fonction à prendre un type spécifique, dans ce cas int. Mais avec le inline modificateur, la fonction est également déduite d’avoir un paramètre de type résolu statiquement. Avec le inline modificateur, le type est déduit comme suit :

^a -> unit when ^a : (static member op_Explicit : ^a -> float)

Cela signifie que la fonction accepte tout type qui prend en charge une conversion en float.

InlineIfLambda

Le compilateur F# inclut un optimiseur qui effectue l’incorporation de code. L’attribut InlineIfLambda permet au code d’indiquer éventuellement que, si un argument est déterminé comme une fonction lambda, cet argument doit toujours être inclus dans les sites d’appel. Pour plus d’informations, consultez F# RFC FS-1098.

Par exemple, considérez la fonction suivante iterateTwice pour parcourir un tableau :

let inline iterateTwice ([<InlineIfLambda>] action) (array: 'T[]) =
    for i = 0 to array.Length-1 do
        action array[i]
    for i = 0 to array.Length-1 do
        action array[i]

Si le site d’appel est :

let arr = [| 1.. 100 |]
let mutable sum = 0
arr  |> iterateTwice (fun x ->
    sum <- sum + x)

Ensuite, après l’incorporation et d’autres optimisations, le code devient :

let arr = [| 1..100 |]
let mutable sum = 0
for i = 0 to arr.Length - 1 do
    sum <- sum + arr[i] 
for i = 0 to arr.Length - 1 do
    sum <- sum + arr[i] 

Cette optimisation est appliquée quelle que soit la taille de l’expression lambda impliquée. Cette fonctionnalité peut également être utilisée pour implémenter un déroulement de boucle et des transformations similaires de manière plus fiable.

Un avertissement d’opt-in (/warnon:3517 ou propriété <WarnOn>3517</WarnOn>) peut être activé pour indiquer des emplacements dans votre code où InlineIfLambda les arguments ne sont pas liés aux expressions lambda sur les sites d’appel. Dans des situations normales, cet avertissement ne doit pas être activé. Toutefois, dans certains types de programmation hautes performances, il peut être utile de s’assurer que tout le code est inline et aplatit.

Optimiser les rappels curried

Pour éviter l’adaptation répétée de la arité lorsqu’un rappel ne peut pas être inline, F# 11 prend en charge OptimizeClosureIfNotInlined en même temps InlineIfLambda:

let inline foldItems
    ([<InlineIfLambda; OptimizeClosureIfNotInlined>] folder)
    state
    (items: 'T[]) =
    let mutable total = state
    for item in items do
        total <- folder total item
    total

La fonction doit être inline, les deux attributs doivent être présents, et le rappel doit être courbé avec deux à cinq arguments. Une expression lambda connue peut être insérée ; un rappel opaque peut être adapté une seule fois. Cela nécessite la prise en charge de FSharp.Core 11 et du compilateur, mais ne garantit pas l’allocation zéro.

Déboguer des fonctions inline

Pour effectuer un pas à pas dans les helpers inline, compilez sans optimisation et conservez les informations de débogage. F# 11 préfère les appels normaux dans ces builds :

let inline addFee (fee: decimal) (amount: decimal) =
    let total = amount + fee
    total

let invoiceTotal = addFee 2m 40m
printfn "%M" invoiceTotal
// 42

Dans l’exemple de projet, définissez un point d’arrêt sur let total, passez à addFeepas et inspectez ses arguments.

Les builds optimisées sont toujours inline par défaut. Certaines constructions, y compris le code pouvant être repris et les opérations nécessitant une expansion statique, doivent être incorporées, même sans optimisation. Permet --always-inline de demander l’ancien comportement.

Contraintes d’extension inline entre les bibliothèques

F# 11.2 peut utiliser des membres d’extension publique pour répondre aux exigences SRTP. Consultez les membres de l’extension pour les contraintes SRTP pour les paramètres de langage, l’étendue et les exigences de bibliothèque.

Voir aussi