다음을 통해 공유


Byref

F#에는 하위 수준 프로그래밍 공간을 다루는 두 가지 주요 기능 영역이 있습니다.

  • byref//inrefoutref 관리되는 포인터인 형식입니다. 런타임에 잘못된 프로그램을 컴파일할 수 없도록 사용 제한이 있습니다.
  • byref의미 체계와 컴파일 시간 제한이 byref<'T>동일한 구조체인 -like 구조체입니다. 한 가지 예는 .입니다 Span<T>.

구문

// Byref types as parameters
let f (x: byref<'T>) = ()
let g (x: inref<'T>) = ()
let h (x: outref<'T>) = ()

// Calling a function with a byref parameter
let mutable x = 3
f &x

// Declaring a byref-like struct
open System.Runtime.CompilerServices

[<Struct; IsByRefLike>]
type S(count1: int, count2: int) =
    member x.Count1 = count1
    member x.Count2 = count2

Byref, inref 및 outref

다음과 같은 세 가지 형식이 byref있습니다.

  • inref<'T>- 기본 값을 읽기 위한 관리되는 포인터입니다.
  • outref<'T>- 기본 값에 쓰기 위한 관리되는 포인터입니다.
  • byref<'T>- 기본 값을 읽고 쓰기 위한 관리되는 포인터입니다.

A byref<'T> 는 필요한 위치에 inref<'T> 전달할 수 있습니다. 마찬가지로, byref<'T> 예상되는 위치에 outref<'T> 전달할 수 있습니다.

byrefs 사용

를 사용하려면 다음을 inref<'T>사용하여 포인터 값을 &가져와야 합니다.

open System

let f (dt: inref<DateTime>) =
    printfn $"Now: %O{dt}"

let usage =
    let dt = DateTime.Now
    f &dt // Pass a pointer to 'dt'

또는 byref<'T>을 사용하여 포인터에 쓰려면 포인터mutableoutref<'T> 잡는 값도 만들어야 합니다.

open System

let f (dt: byref<DateTime>) =
    printfn $"Now: %O{dt}"
    dt <- DateTime.Now

// Make 'dt' mutable
let mutable dt = DateTime.Now

// Now you can pass the pointer to 'dt'
f &dt

포인터를 읽는 대신 포인터만 작성하는 경우 대신 사용하는 outref<'T>byref<'T>것이 좋습니다.

Inref 의미 체계

다음 코드를 생각해 봅시다.

let f (x: inref<SomeStruct>) = x.SomeField

의미상 다음을 의미합니다.

  • 포인터의 소유자만 x 값을 읽는 데 사용할 수 있습니다.
  • SomeStruct 첩된 필드에 획득된 struct 모든 포인터에는 형식inref<_>이 지정됩니다.

다음도 마찬가지입니다.

  • 다른 스레드 또는 별칭에 대한 쓰기 권한이 x없다는 의미는 없습니다.
  • 덕에 SomeStruct 변경할 수 없는 xinref의미는 없습니다.

그러나 변경할 수 없는 F# 값 형식 this 경우 포인터가 .로 유추됩니다inref.

이러한 모든 규칙은 포인터 소유자가 가리키는 메모리의 inref 즉각적인 내용을 수정하지 않을 수 있음을 의미합니다.

Outref 의미 체계

목적은 outref<'T> 포인터만 작성해야 함을 나타내는 것입니다. 예기치 않게 이름 outref<'T> 에도 불구하고 기본 값을 읽을 수 있습니다. 이는 호환성을 위한 것입니다.

의미 체계는 outref<'T> 한 가지 차이점을 제외하고 다르지 byref<'T>않습니다. 매개 변수가 있는 메서드는 매개 변수를 사용하여 outref<'T> 메서드를 호출할 때와 마찬가지로 암시적으로 튜플 반환 형식으로 [<Out>] 생성됩니다.

type C =
    static member M1(x, y: _ outref) =
        y <- x
        true

match C.M1 1 with
| true, 1 -> printfn "Expected" // Fine with outref, error with byref
| _ -> printfn "Never matched"

C와의 Interop#

C#은 in refout ref 반환 외에도 ref 키워드(keyword) 지원합니다. 다음 표에서는 F#이 C#이 내보내는 내용을 해석하는 방법을 보여 줍니다.

C# 구문 F# 유추
ref 반환 값 outref<'T>
ref readonly 반환 값 inref<'T>
in ref 매개 변수 inref<'T>
out ref 매개 변수 outref<'T>

다음 표에서는 F#이 내보내는 내용을 보여 줍니다.

F# 구문 내보낸 구문
inref<'T> 인수 [In] 인수의 특성
inref<'T> 반환 modreq 값에 대한 특성
inref<'T> 추상 슬롯 또는 구현 modreq on argument or return
outref<'T> 인수 [Out] 인수의 특성

형식 유추 및 오버로드 규칙

inref<'T> 형식은 다음 경우에 F# 컴파일러에 의해 유추됩니다.

  1. 특성이 있는 .NET 매개 변수 또는 반환 형식입니다 IsReadOnly .
  2. this 변경할 수 있는 필드가 없는 구조체 형식의 포인터입니다.
  3. 다른 inref<_> 포인터에서 파생된 메모리 위치의 주소입니다.

암시적 주소를 inref 사용하는 경우 형식 인수가 있는 오버로드는 형식 SomeType 인수가 있는 오버로드 inref<SomeType>를 사용하는 것이 좋습니다. 예시:

type C() =
    static member M(x: System.DateTime) = x.AddDays(1.0)
    static member M(x: inref<System.DateTime>) = x.AddDays(2.0)
    static member M2(x: System.DateTime, y: int) = x.AddDays(1.0)
    static member M2(x: inref<System.DateTime>, y: int) = x.AddDays(2.0)

let res = System.DateTime.Now
let v =  C.M(res)
let v2 =  C.M2(res, 4)

두 경우 모두 오버로드를 사용하는 System.DateTime 대신 오버로드가 확인됩니다 inref<System.DateTime>.

Byref와 유사한 구조체

트리오 외에도 byref//inrefoutref 같은 의미 체계를 준수할 수 있는 고유한 구조체를 정의할 byref수 있습니다. 이 작업은 특성으로 IsByRefLikeAttribute 수행됩니다.

open System
open System.Runtime.CompilerServices

[<IsByRefLike; Struct>]
type S(count1: Span<int>, count2: Span<int>) =
    member x.Count1 = count1
    member x.Count2 = count2

IsByRefLike 은 암시 Struct하지 않습니다. 둘 다 형식에 있어야 합니다.

F#의 "byref유사" 구조체는 스택 바인딩된 값 형식입니다. 관리되는 힙에는 할당되지 않습니다. byref-like 구조체는 수명 및 비 캡처에 대한 강력한 검사 집합으로 적용되므로 고성능 프로그래밍에 유용합니다. 규칙은 다음과 같습니다.

  • 함수 매개 변수, 메서드 매개 변수, 지역 변수, 메서드 반환으로 사용할 수 있습니다.
  • 클래스 또는 일반 구조체의 정적 또는 인스턴스 멤버일 수 없습니다.
  • 클로저 구문(async 메서드 또는 람다 식)으로 캡처할 수 없습니다.
  • 제네릭 매개 변수로 사용할 수 없습니다.

이 마지막 지점은 입력 형식을 매개 변수화하는 제네릭 함수와 마찬가지로 |> F# 파이프라인 스타일 프로그래밍에 매우 중요합니다. 이 제한은 인라인이며 본문에 인라인되지 않은 제네릭 함수를 호출하지 않으므로 나중에 완화 |> 될 수 있습니다.

이러한 규칙은 사용을 강력하게 제한하지만 안전한 방식으로 고성능 컴퓨팅의 약속을 이행합니다.

Byref 반환

F# 함수 또는 멤버에서 Byref 반환을 생성하고 사용할 수 있습니다. -returning 메서드를 byref사용할 때 값은 암시적으로 역참조됩니다. 예시:

let squareAndPrint (data : byref<int>) =
    let squared = data*data    // data is implicitly dereferenced
    printfn $"%d{squared}"

값 바이레프를 반환하려면 값이 포함된 변수가 현재 범위보다 오래 있어야 합니다. 또한 byref를 반환하려면 값이 현재 범위보다 오래 사는 변수인 경우를 사용합니다 &value .

let mutable sum = 0
let safeSum (bytes: Span<byte>) =
    for i in 0 .. bytes.Length - 1 do
        sum <- sum + int bytes[i]
    &sum  // sum lives longer than the scope of this function.

여러 연결 호출을 통해 참조를 전달하는 것과 같은 암시적 역참조를 방지하려면(값은 어디에) x 사용합니다 &x .

반환 byref에 직접 할당할 수도 있습니다. 다음(매우 명령적) 프로그램을 고려합니다.

type C() =
    let mutable nums = [| 1; 3; 7; 15; 31; 63; 127; 255; 511; 1023 |]

    override _.ToString() = String.Join(' ', nums)

    member _.FindLargestSmallerThan(target: int) =
        let mutable ctr = nums.Length - 1

        while ctr > 0 && nums[ctr] >= target do ctr <- ctr - 1

        if ctr > 0 then &nums[ctr] else &nums[0]

[<EntryPoint>]
let main argv =
    let c = C()
    printfn $"Original sequence: %O{c}"

    let v = &c.FindLargestSmallerThan 16

    v <- v*2 // Directly assign to the byref return

    printfn $"New sequence:      %O{c}"

    0 // return an integer exit code

출력은 다음과 같습니다.

Original sequence: 1 3 7 15 31 63 127 255 511 1023
New sequence:      1 3 7 30 31 63 127 255 511 1023

바이레프 범위 지정

-bound 값은 let해당 참조가 정의된 범위를 초과할 수 없습니다. 예를 들어 다음은 허용되지 않습니다.

let test2 () =
    let x = 12
    &x // Error: 'x' exceeds its defined scope!

let test () =
    let x =
        let y = 1
        &y // Error: `y` exceeds its defined scope!
    ()

이렇게 하면 최적화를 사용하여 컴파일하는지 여부에 따라 다른 결과를 얻을 수 없습니다.