2012-05-04 42 views
7

Mã dụ:F # "cho vòng lặp" tối ưu hóa

let foo1 (arr : int[]) = 
    for i = 0 to arr.Length-1 do 
     arr.[i] <- i 

let foo2 (arr : int[]) = 
    for i in [0..arr.Length-1] do 
     arr.[i] <- i 

Tôi nghĩ rằng chức năng này cần phải tương ứng với nhau (về mặt hiệu suất). Nhưng nếu chúng ta nhìn vào danh sách IL, chúng ta sẽ thấy:

chức năng đầu tiên, 15 dòng, không phân bổ năng động, không try điều hành, không gọi điện thoại ảo:

IL_0000: nop 
IL_0001: ldc.i4.0 
IL_0002: stloc.0 
IL_0003: br.s IL_0011 
// loop start (head: IL_0011) 
    IL_0005: ldarg.0 
    IL_0006: ldloc.0 
    IL_0007: ldloc.0 
    IL_0008: stelem.any [mscorlib]System.Int32 
    IL_000d: ldloc.0 
    IL_000e: ldc.i4.1 
    IL_000f: add 
    IL_0010: stloc.0 

    IL_0011: ldloc.0 
    IL_0012: ldarg.0 
    IL_0013: ldlen 
    IL_0014: conv.i4 
    IL_0015: blt.s IL_0005 
// end loop 

IL_0017: ret 

và thứ hai - gần 100 dòng, nhiều phân bổ/deallocations, kêu gọi các chức năng ảo, rất nhiều try/Dispose:

IL_0000: nop 
IL_0001: ldc.i4.0 
IL_0002: ldc.i4.1 
IL_0003: ldarg.0 
IL_0004: ldlen 
IL_0005: conv.i4 
IL_0006: ldc.i4.1 
IL_0007: sub 
IL_0008: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [FSharp.Core]Microsoft.FSharp.Core.Operators/OperatorIntrinsics::RangeInt32(int32, int32, int32) 
IL_000d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [FSharp.Core]Microsoft.FSharp.Core.Operators::CreateSequence<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
IL_0012: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!0> [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>) 
IL_0017: stloc.0 
IL_0018: ldloc.0 
IL_0019: unbox.any class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> 
IL_001e: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator() 
IL_0023: stloc.1 
.try 
{ 
    // loop start (head: IL_0024) 
     IL_0024: ldloc.1 
     IL_0025: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() 
     IL_002a: brfalse.s IL_003e 

     IL_002c: ldloc.1 
     IL_002d: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current() 
     IL_0032: stloc.3 
     IL_0033: ldarg.0 
     IL_0034: ldloc.3 
     IL_0035: ldloc.3 
     IL_0036: stelem.any [mscorlib]System.Int32 
     IL_003b: nop 
     IL_003c: br.s IL_0024 
    // end loop 

    IL_003e: ldnull 
    IL_003f: stloc.2 
    IL_0040: leave.s IL_005b 
} // end .try 
finally 
{ 
    IL_0042: ldloc.1 
    IL_0043: isinst [mscorlib]System.IDisposable 
    IL_0048: stloc.s 4 
    IL_004a: ldloc.s 4 
    IL_004c: brfalse.s IL_0058 

    IL_004e: ldloc.s 4 
    IL_0050: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    IL_0055: ldnull 
    IL_0056: pop 
    IL_0057: endfinally 

    IL_0058: ldnull 
    IL_0059: pop 
    IL_005a: endfinally 
} // end handler 

IL_005b: ldloc.2 
IL_005c: pop 
IL_005d: ret 

câu hỏi của tôi là tại sao F # biên dịch sử dụng mã quá phức tạp cho foo2? Tại sao nó sử dụng một IEnumerable để thực hiện vòng lặp tầm thường như vậy?

Trả lời

12

Trong ví dụ thứ 2 nếu bạn sử dụng biểu hiện phạm vi, nó sẽ được chuyển đổi thành for vòng lặp thông thường:

let foo2 (arr : int[]) = 
    for i in 0..arr.Length-1 do 
     arr.[i] <- i 

và trở thành tương đương với foo1.

tôi trích dẫn Section 6.3.12 Range Expressions in F# language specs:

Một chuỗi lặp biểu hiện của hình thức cho var trong expr1 .. expr2 làm expr3 thực hiện đôi khi được xây dựng như một đơn giản cho vòng lặp thể hiện (§6.5.7).

Tuy nhiên, ví dụ thứ 2 của bạn là giống như:

let foo2 (arr : int[]) = 
    let xs = [0..arr.Length-1] (* A new list is created *) 
    for i in xs do 
     arr.[i] <- i 

nơi bạn đã tạo ra một danh sách mới một cách rõ ràng.

+0

Tôi không biết về biểu thức 'for i in 0..arr.Length-1 do' này. Cảm ơn rất nhiều. – qehgt

7

Điều bạn thấy là sự khác biệt tiêu chuẩn giữa việc sử dụng bảng liệt kê dựa trên chỉ mục và đếm số IEnumerable (hoặc trong điều khoản C# forforeach).

Trong mẫu thứ hai, biểu thức [0..arr.Length-1] đang tạo bộ sưu tập và F # đang sử dụng IEnumerable<T> để liệt kê các giá trị. Một phần của kiểu liệt kê này liên quan đến việc sử dụng IEnumerator<T> thực hiện IDisposable. Khối try/finally mà bạn đang thấy được tạo ra để đảm bảo rằng phương thức IDisposable::Dispose được gọi vào cuối điều tra ngay cả khi đối mặt với ngoại lệ.

Có thể F # tối ưu hóa ví dụ thứ hai vào ví dụ đầu tiên và tránh tất cả các chi phí phụ không? Có thể họ có thể thực hiện tối ưu hóa này. Về cơ bản nhìn qua biểu thức phạm vi, không phải chỉ là một phạm vi số đơn giản và do đó tạo mã tương đương for.

Nếu F # tối ưu hóa ví dụ thứ hai. Phiếu bầu của tôi sẽ không. Các tính năng như thế này thường có vẻ tầm thường từ bên ngoài nhưng thực sự triển khai chúng, và quan trọng hơn là duy trì chúng, có thể khá đắt tiền. Một người dùng sành điệu luôn có thể chuyển đổi mã của họ trở lại phiên bản for tiêu chuẩn và tránh chi phí trên IEnumerable<T> (nếu trình tiết lộ tiết lộ đó là vấn đề).Không triển khai tối ưu hóa giải phóng đội F # để triển khai các tính năng tuyệt vời khác.

Các vấn đề liên quan