Funzioni inline

Le funzioni inline sono funzioni integrate direttamente nel codice chiamante.

Uso di funzioni inline

Quando si usano parametri di tipo statico, tutte le funzioni con parametri di tipo devono essere inline. Ciò garantisce che il compilatore possa risolvere questi parametri di tipo. Quando si usano parametri di tipo generico ordinari, non esiste alcuna restrizione.

Oltre ad abilitare l'uso di vincoli membro, le funzioni inline possono essere utili per ottimizzare il codice. Tuttavia, l'uso eccessivo delle funzioni inline può causare una minore resistenza al codice alle modifiche apportate alle ottimizzazioni del compilatore e all'implementazione delle funzioni di libreria. Per questo motivo, è consigliabile evitare di usare funzioni inline per l'ottimizzazione, a meno che non si siano provate tutte le altre tecniche di ottimizzazione. La creazione di una funzione o di un metodo inline può talvolta migliorare le prestazioni, ma questo non è sempre il caso. Pertanto, è consigliabile usare anche le misurazioni delle prestazioni per verificare che la creazione di una determinata funzione inline abbia un effetto positivo.

Il inline modificatore può essere applicato alle funzioni al livello superiore, a livello di modulo o a livello di metodo in una classe.

Nell'esempio di codice seguente viene illustrata una funzione inline al livello superiore, un metodo di istanza inline e un metodo statico inline.

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

Funzioni inline e inferenza dei tipi

La presenza di influisce sull'inferenza del inline tipo. Ciò è dovuto al fatto che le funzioni inline possono avere parametri di tipo risolti in modo statico, mentre funzioni non inline non possono. L'esempio di codice seguente illustra un caso in cui inline è utile perché si usa una funzione con un parametro di tipo risolto in modo statico, l'operatore float di conversione.

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

Senza il modificatore, l'inferenza del inline tipo forza la funzione a accettare un tipo specifico, in questo caso int. Tuttavia, con il inline modificatore, la funzione viene dedotta anche per avere un parametro di tipo risolto in modo statico. Con il inline modificatore, il tipo viene dedotto come segue:

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

Ciò significa che la funzione accetta qualsiasi tipo che supporti una conversione in float.

InlineIfLambda

Il compilatore F# include un ottimizzatore che esegue l'inlining del codice. L'attributo InlineIfLambda consente al codice di indicare facoltativamente che, se un argomento è determinato come una funzione lambda, tale argomento deve essere sempre inlined nei siti di chiamata. Per altre informazioni, vedere F# RFC FS-1098.

Si consideri ad esempio la funzione seguente iterateTwice per attraversare una matrice:

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]

Se il sito di chiamata è:

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

Dopo l'inlining e altre ottimizzazioni, il codice diventa:

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] 

Questa ottimizzazione viene applicata indipendentemente dalle dimensioni dell'espressione lambda coinvolta. Questa funzionalità può essere usata anche per implementare l'annullamento della registrazione del ciclo e trasformazioni simili in modo più affidabile.

È possibile attivare un avviso di consenso esplicito (/warnon:3517 o proprietà <WarnOn>3517</WarnOn>) per indicare le posizioni nel codice in cui InlineIfLambda gli argomenti non sono associati alle espressioni lambda nei siti di chiamata. In situazioni normali, questo avviso non deve essere abilitato. Tuttavia, in alcuni tipi di programmazione ad alte prestazioni, può essere utile assicurarsi che tutto il codice sia inlined e appiattito.

Ottimizzare i callback curried

Per evitare l'adattamento ripetuto dell'arità quando un callback non può essere inline, F# 11 supporta OptimizeClosureIfNotInlined insieme InlineIfLambdaa :

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

La funzione deve essere inline, entrambi gli attributi devono essere presenti e il callback deve essere curried con due o cinque argomenti. Un'espressione lambda nota può essere inline; un callback opaco può essere adattato una sola volta. Ciò richiede il supporto FSharp.Core 11 e del compilatore, ma non garantisce allocazioni pari a zero.

Eseguire il debug di funzioni inline

Per esaminare gli helper inline, compilare senza ottimizzazione e conservare le informazioni di debug. F# 11 preferisce le normali chiamate nelle compilazioni seguenti:

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

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

Nel progetto di esempio impostare un punto di interruzione in let total, eseguire un'istruzione in addFeee controllarne gli argomenti.

Per impostazione predefinita, le compilazioni ottimizzate sono ancora inline. Alcuni costrutti, inclusi codice ripristinabile e operazioni che richiedono l'espansione statica, devono essere inline anche senza ottimizzazione. Usare --always-inline per richiedere il comportamento precedente.

Vincoli di estensione inline tra librerie

F# 11.2 può usare membri di estensione pubblica per soddisfare i requisiti SRTP. Vedere Membri dell'estensione per i vincoli SRTP per i requisiti di lingua, ambito e libreria.

Vedere anche