2010-07-27 21 views
11

Imagine mã đơn giản sau đây:Làm thế nào để đúc một giá trị của loại chung T để tăng gấp đôi mà không có quyền anh?

public void F<T>(IList<T> values) where T : struct 
{ 
    foreach (T value in values) 
    { 
    double result; 
    if (TryConvertToDouble((object)value, out result)) 
    { 
     ConsumeValue(result); 
    } 
    } 
} 

public void ConsumeValue(double value) 
{ 
} 

Vấn đề với đoạn code trên được đúc để phản đối, mà kết quả trong boxing trong vòng lặp.

Có cách nào để đạt được cùng chức năng, ví dụ: cho ăn ConsumeValue với tất cả các giá trị mà không cần đến boxing trong vòng lặp foreach không? Lưu ý rằng F phải là một phương pháp chung.

Tôi có thể sống với mã chuẩn bị tốn kém miễn là mã được thực hiện ngoài vòng lặp một lần. Ví dụ, nếu một phương pháp năng động ưa thích cần được phát ra, thì sẽ tốt nếu được thực hiện một lần.

EDIT

T là đảm bảo được một số loại số hoặc bool.

Động lực. Hãy tưởng tượng ứng dụng hướng dữ liệu meta, trong đó một tác nhân báo cáo luồng dữ liệu, nơi loại mục dữ liệu được tự động phát ra dựa trên dữ liệu meta luồng dữ liệu. Hãy tưởng tượng cũng có, đó là động cơ bình thường, mà biết để bình thường hóa các luồng dữ liệu số theo một số thuật toán. Loại luồng dữ liệu số đến chỉ được biết đến trong thời gian chạy và có thể được chuyển hướng đến một phương thức chung của kiểu dữ liệu đó. Bình thường, tuy nhiên, dự kiến ​​tăng gấp đôi và sản xuất tăng gấp đôi. Đây là một mô tả mức độ rất cao, xin vui lòng không đi sâu vào nó.

EDIT2

Về các diễn viên sẽ tăng gấp đôi. Trên thực tế chúng ta có một phương pháp để chuyển đổi để tăng gấp đôi với chữ ký sau đây:

bool TryConvertToDouble(object value, out double result); 

tôi nên đã sử dụng nó trong ví dụ ở nơi đầu tiên, nhưng tôi muốn tiết kiệm không gian và viết cái gì đó không được đi làm. Sửa lỗi ngay bây giờ. Cảm ơn vì đã chú ý.

EDIT3

Guys, việc thực hiện hiện tại không hộp các giá trị. Và ngay cả khi tôi không có phán quyết của profiler về hình phạt hiệu suất của nó (nếu có), tôi vẫn còn thú vị để biết liệu có một giải pháp mà không có quyền anh (và không chuyển đổi thành chuỗi). Hãy để tôi gọi nó là mối quan tâm hoàn toàn về mặt học thuật. Điều này thực sự quan tâm đến tôi, bởi vì những thứ như thế là tầm thường trong C++ với các khuôn mẫu, nhưng, tất nhiên, tôi không bắt đầu một đối số ngu ngốc và vô nghĩa nào về những gì tốt hơn .NET Generics hoặc C++ templates. Xin vui lòng, bỏ qua câu cuối cùng này.

EDIT4

Nhờ https://stackoverflow.com/users/267/lasse-v-karlsen đã cung cấp câu trả lời. Thực ra, tôi đã sử dụng mẫu mã của mình để viết một lớp đơn giản như thế này:

public static class Utils<T> 
{ 
    private static class ToDoubleConverterHolder 
    { 
    internal static Func<T, double> Value = EmitConverter(); 

    private static Func<T, double> EmitConverter() 
    { 
     ThrowIfNotConvertableToDouble(typeof(T)); 

     var method = new DynamicMethod(string.Empty, typeof(double), TypeArray<T>.Value); 
     var il = method.GetILGenerator(); 

     il.Emit(OpCodes.Ldarg_0); 
     if (typeof(T) != typeof(double)) 
     { 
     il.Emit(OpCodes.Conv_R8); 
     } 
     il.Emit(OpCodes.Ret); 

     return (Func<T, double>)method.CreateDelegate(typeof(Func<T, double>)); 
    } 
    } 

    public static double ConvertToDouble(T value) 
    { 
    return ToDoubleConverterHolder.Value(value); 
    } 
} 

đâu:

  • ThrowIfNotConvertableToDouble (Type) là một phương pháp đơn giản mà làm cho chắc chắn rằng loại nhất định có thể được chuyển đổi sang gấp đôi , tức là một số kiểu số hoặc bool.
  • TypeArray là một lớp helper để sản xuất new[]{ typeof(T) }

Phương pháp Utils.ConvertToDouble chuyển đổi bất kỳ giá trị số tăng gấp đôi trong cách hiệu quả nhất, thể hiện bởi câu trả lời cho câu hỏi này.

Nó hoạt động như một người quyến rũ - cảm ơn người đàn ông.

+7

Vấn đề với trên cũng là nó không có ý nghĩa nhiều. Tại sao sử dụng một phương pháp chung, với một ràng buộc, và sau đó cứng đúc nó đến một đôi? Bạn có thể giải thích rõ hơn những gì bạn đang cố gắng đạt được không? –

+0

Điều này thật lạ lùng với tôi. Tại sao bạn đúc một cấu trúc chung cho một đối tượng và sau đó đến một đôi? Có vấn đề gì với ví dụ này không? Chúng ta có cần thêm ngữ cảnh không? Mã này có vẻ như vậy, tôi không biết cách trả lời ... –

+0

Tôi đã cập nhật câu hỏi. – mark

Trả lời

7

LƯU Ý: Đã xảy ra lỗi trong mã ban đầu của tôi để tạo mã dựa trên cá thể. Vui lòng kiểm tra lại mã bên dưới. Phần thay đổi là thứ tự của các giá trị tải lên ngăn xếp (ví dụ: các dòng .Emit). Cả hai mã trong câu trả lời và kho lưu trữ đã được cố định.

Nếu bạn muốn đi theo con đường của thế hệ mã, như bạn gợi ý để trong câu hỏi của bạn, đây là mẫu mã:

Nó thực ConsumeValue (mà không có gì trong ví dụ của tôi) 10 triệu lần, trên một mảng của ints và một mảng các boolean, định thời gian thực hiện (nó chạy tất cả các mã một lần, để loại bỏ JIT overhead từ skewing thời gian.)

Kết quả: chưa

F1 ints = 445ms   <-- uses Convert.ToDouble 
F1 bools = 351ms 
F2 ints = 159ms   <-- generates code on each call 
F2 bools = 167ms 
F3 ints = 158ms   <-- caches generated code between calls 
F3 bools = 163ms 

Khoảng 65% chi phí với hệ mã.

Mã có sẵn từ kho Mercurial của tôi tại đây: http://hg.vkarlsen.no/hgweb.cgi/StackOverflow, duyệt qua bằng cách tìm số câu hỏi SO của bạn.

Mã:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Reflection; 
using System.Reflection.Emit; 

namespace ConsoleApplication15 
{ 
    class Program 
    { 
     public static void F1<T>(IList<T> values) where T : struct 
     { 
      foreach (T value in values) 
       ConsumeValue(Convert.ToDouble(value)); 
     } 

     public static Action<T> GenerateAction<T>() 
     { 
      DynamicMethod method = new DynamicMethod(
       "action", MethodAttributes.Public | MethodAttributes.Static, 
       CallingConventions.Standard, 
       typeof(void), new Type[] { typeof(T) }, typeof(Program).Module, 
       false); 
      ILGenerator il = method.GetILGenerator(); 

      il.Emit(OpCodes.Ldarg_0); // get value passed to action 
      il.Emit(OpCodes.Conv_R8); 
      il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); 
      il.Emit(OpCodes.Ret); 

      return (Action<T>)method.CreateDelegate(typeof(Action<T>)); 
     } 

     public static void F2<T>(IList<T> values) where T : struct 
     { 
      Action<T> action = GenerateAction<T>(); 
      foreach (T value in values) 
       action(value); 
     } 

     private static Dictionary<Type, object> _Actions = 
      new Dictionary<Type, object>(); 
     public static void F3<T>(IList<T> values) where T : struct 
     { 
      Object actionObject; 
      if (!_Actions.TryGetValue(typeof(T), out actionObject)) 
      { 
       actionObject = GenerateAction<T>(); 
       _Actions[typeof (T)] = actionObject; 
      } 
      Action<T> action = (Action<T>)actionObject; 
      foreach (T value in values) 
       action(value); 
     } 

     public static void ConsumeValue(double value) 
     { 
     } 

     static void Main(string[] args) 
     { 
      Stopwatch sw = new Stopwatch(); 

      int[] ints = Enumerable.Range(1, 10000000).ToArray(); 
      bool[] bools = ints.Select(i => i % 2 == 0).ToArray(); 

      for (int pass = 1; pass <= 2; pass++) 
      { 
       sw.Reset(); 
       sw.Start(); 
       F1(ints); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F1 ints = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       F1(bools); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F1 bools = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       F2(ints); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F2 ints = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       F2(bools); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F2 bools = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       F3(ints); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F3 ints = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       F3(bools); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F3 bools = " 
         + sw.ElapsedMilliseconds + "ms"); 
      } 
     } 
    } 
} 

Lưu ý rằng nếu bạn thực hiện GenerationAction, F2/3 và ConsumeValue không tĩnh, bạn phải thay đổi mã hơi:

  1. Tất cả Action<T> tờ khai trở thành Action<Program, T>
  2. Thay đổi tạo DynamicMethod để bao gồm thông số "this":

    DynamicMethod method = new DynamicMethod(
        "action", MethodAttributes.Public | MethodAttributes.Static, 
        CallingConventions.Standard, 
        typeof(void), new Type[] { typeof(Program), typeof(T) }, 
        typeof(Program).Module, 
        false); 
    
  3. Thay đổi các hướng dẫn để tải các giá trị đúng vào đúng thời điểm:

    il.Emit(OpCodes.Ldarg_0); // get "this" 
    il.Emit(OpCodes.Ldarg_1); // get value passed to action 
    il.Emit(OpCodes.Conv_R8); 
    il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); 
    il.Emit(OpCodes.Ret); 
    
  4. đèo "này" để hành động bất cứ khi nào nó được gọi là:

    action(this, value); 
    

Dưới đây là chương trình đã thay đổi hoàn toàn cho các phương pháp không tĩnh:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Reflection; 
using System.Reflection.Emit; 

namespace ConsoleApplication15 
{ 
    class Program 
    { 
     public void F1<T>(IList<T> values) where T : struct 
     { 
      foreach (T value in values) 
       ConsumeValue(Convert.ToDouble(value)); 
     } 

     public Action<Program, T> GenerateAction<T>() 
     { 
      DynamicMethod method = new DynamicMethod(
       "action", MethodAttributes.Public | MethodAttributes.Static, 
       CallingConventions.Standard, 
       typeof(void), new Type[] { typeof(Program), typeof(T) }, 
       typeof(Program).Module, 
       false); 
      ILGenerator il = method.GetILGenerator(); 

      il.Emit(OpCodes.Ldarg_0); // get "this" 
      il.Emit(OpCodes.Ldarg_1); // get value passed to action 
      il.Emit(OpCodes.Conv_R8); 
      il.Emit(OpCodes.Call, typeof(Program).GetMethod("ConsumeValue")); 
      il.Emit(OpCodes.Ret); 

      return (Action<Program, T>)method.CreateDelegate(
       typeof(Action<Program, T>)); 
     } 

     public void F2<T>(IList<T> values) where T : struct 
     { 
      Action<Program, T> action = GenerateAction<T>(); 
      foreach (T value in values) 
       action(this, value); 
     } 

     private static Dictionary<Type, object> _Actions = 
      new Dictionary<Type, object>(); 
     public void F3<T>(IList<T> values) where T : struct 
     { 
      Object actionObject; 
      if (!_Actions.TryGetValue(typeof(T), out actionObject)) 
      { 
       actionObject = GenerateAction<T>(); 
       _Actions[typeof (T)] = actionObject; 
      } 
      Action<Program, T> action = (Action<Program, T>)actionObject; 
      foreach (T value in values) 
       action(this, value); 
     } 

     public void ConsumeValue(double value) 
     { 
     } 

     static void Main(string[] args) 
     { 
      Stopwatch sw = new Stopwatch(); 

      Program p = new Program(); 
      int[] ints = Enumerable.Range(1, 10000000).ToArray(); 
      bool[] bools = ints.Select(i => i % 2 == 0).ToArray(); 

      for (int pass = 1; pass <= 2; pass++) 
      { 
       sw.Reset(); 
       sw.Start(); 
       p.F1(ints); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F1 ints = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       p.F1(bools); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F1 bools = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       p.F2(ints); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F2 ints = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       p.F2(bools); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F2 bools = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       p.F3(ints); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F3 ints = " 
         + sw.ElapsedMilliseconds + "ms"); 

       sw.Reset(); 
       sw.Start(); 
       p.F3(bools); 
       sw.Stop(); 
       if (pass == 2) 
        Console.Out.WriteLine("F3 bools = " 
         + sw.ElapsedMilliseconds + "ms"); 
      } 
     } 
    } 
} 
+0

Rất tiếc. Rất thú vị, nó sẽ đưa tôi một thời gian để xác minh nó, nhưng âm thanh đầy hứa hẹn. – mark

+0

Hãy đảm bảo rằng bạn xác minh rằng bạn đang sử dụng đúng phiên bản của mã. Tôi vừa sửa một lỗi theo thứ tự các lệnh được phát ra. –

+0

Bạn thậm chí có thể tăng hiệu năng bằng cách lưu vào bộ nhớ đệm hành động được tạo ra trong một số khe đơn giống như la la 'ConvertAction .Instance'. Bằng cách đó bạn sẽ không phải gặp rắc rối xung quanh với một từ điển và lo lắng về an toàn luồng. – herzmeister

0

Bạn có thể sử dụng lớp Convert.

ConsumeValue(Convert.ToDouble(value)); 

Không chắc chắn về nội bộ của ToDouble ... nhưng có lẽ tốt nhất bạn có thể làm.

+0

Không, tôi không thể. Convert.ToDouble không phải là một phương thức chung. – mark

+0

Hoạt động tốt trong C# 3.0 (NET 3.5) –

+0

Điều này là do T được truyền tới đối tượng và sau đó là chuyển đổi Convert.ToDouble (đối tượng) được gọi là - bạn đã giao dịch quyền sở hữu một cách rõ ràng. Tự kiểm tra. – mark

0

Tại sao không chỉ thêm double quá tải đặc biệt cho F cùng với phiên bản chung?

public void F(IList<double> values) 
{ 
    foreach (double value in values) 
    { 
     ConsumeValue(value); 
    } 
} 

Bây giờ nếu bạn gọi F(someDoubleList) nó sẽ gọi cho phiên bản không chung chung, và với bất kỳ danh sách khác một generic sẽ được gọi.

+0

Tôi có phiên bản IList . Nó là một cái đã cho. Làm cách nào để biến nó thành IList ? – mark

+0

'if (typeof (T) == typeof (double)) IList dlist = (IList ) list' – thecoop

+0

Và nếu typeof (T) == typeof (int) thì sao? hoặc typeof (float) hoặc typeof (uint)? – mark

0

Mặc dù kịch bản vẫn không rõ ràng (xem nhận xét của tôi), điều này sẽ không bao giờ hoạt động. Bạn sẽ phải cung cấp một lớp hoặc phương thức tùy chỉnh có thể chuyển đổi từ T của bạn thành gấp đôi.

Các unboxing là thậm chí không liên quan, như các diễn viên trong

ConsumeValue((double)(object)value); 

sẽ ném một InvalidCastException nếu value không phải là một double riêng của mình. (xem this bài viết blog của Eric Lippert vì lý do tại sao.)

Bạn sẽ phải xử lý trước đầu vào, biến thể chung sẽ không hoạt động.

Edit:

Tôi muốn chọn Convert.ToDouble. Chỉ khi hiệu suất là ab-so-lu-te-ly ưu tiên hàng đầu, tôi sẽ sử dụng phương pháp động. Nó thêm đủ phức tạp để tránh nó, nếu có thể. Hiệu suất đạt được khoảng 50% có vẻ đáng kể, nhưng, trong kịch bản được Lasse đưa ra, trên máy của tôi, tôi thu được khoảng 150ms khi lặp lại trên 10000000 (mười triệu) mục, tiết kiệm 0,000015 mili giây mỗi lần lặp.

+0

Bạn nói đúng. Tôi chỉ muốn đưa ra một ví dụ đơn giản nhất có thể và kết thúc với mã chỉ đơn giản là xấu. Đã chỉnh sửa câu hỏi để sửa lỗi không may này. – mark

+0

Bạn có thể sử dụng Convert.ToDouble do các câu trả lời khác đề xuất. Nhưng như tôi hiểu bạn đang quan tâm bởi vấn đề hiệu suất gây ra bởi boxing/unboxing. Trong trường hợp đó Convert.ToDouble có thể là một lựa chọn tồi tệ hơn. Tuy nhiên, trừ khi các thử nghiệm đã chỉ ra rằng các chuyển đổi thực sự là một nút cổ chai, tôi sẽ đi cho nó. –

+0

Đã chỉnh sửa lại câu hỏi của tôi. – mark

4

Đó là một câu hỏi hay, tôi cũng có nhiệm vụ này và tôi đã sử dụng các biểu thức LINQ được biên dịch để thực hiện chuyển đổi tùy ý các loại giá trị đến và từ các tham số kiểu chung chung tránh boxing. Giải pháp rất hiệu quả và nhanh chóng. Nó lưu trữ một lambda biên dịch cho mỗi loại giá trị trong một singleton. Sử dụng được sạch sẽ và dễ đọc.

Dưới đây là một lớp học đơn giản mà không được công việc rất tốt:

public sealed class BoxingSafeConverter<TIn, TOut>   
{ 
    public static readonly BoxingSafeConverter<TIn, TOut> Instance = new BoxingSafeConverter<TIn, TOut>(); 
    private readonly Func<TIn, TOut> convert;   

    public Func<TIn, TOut> Convert 
    { 
     get { return convert; } 
    } 

    private BoxingSafeConverter() 
    { 
     if (typeof (TIn) != typeof (TOut)) 
     { 
      throw new InvalidOperationException("Both generic type parameters must represent the same type."); 
     } 
     var paramExpr = Expression.Parameter(typeof (TIn)); 
     convert = 
      Expression.Lambda<Func<TIn, TOut>>(paramExpr, // this conversion is legal as typeof(TIn) = typeof(TOut) 
       paramExpr) 
       .Compile(); 
    } 
} 

Bây giờ tưởng tượng rằng bạn muốn có một số lưu trữ với các đối tượng và đôi và bạn không muốn đôi của bạn sẽ được đóng hộp. Bạn có thể viết lớp như vậy với thu khí chung và setters theo cách sau:

public class MyClass 
{ 
    readonly List<double> doubles = new List<double>(); // not boxed doubles 
    readonly List<object> objects = new List<object>(); // all other objects 

    public void BoxingSafeAdd<T>(T val) 
    { 
     if (typeof (T) == typeof (double)) 
     { 
      // T to double conversion 
      doubles.Add(BoxingSafeConverter<T, double>.Instance.Convert(val)); 
      return; 
     } 

     objects.Add(val); 
    } 

    public T BoxingSafeGet<T>(int index) 
    { 
     if (typeof (T) == typeof (double)) 
     { 
      // double to T conversion 
      return BoxingSafeConverter<double, T>.Instance.Convert(doubles[index]); 
     } 

     return (T) objects[index]; // boxing-unsage conversion 
    } 
} 

Dưới đây là một số hiệu suất và bộ nhớ kiểm tra đơn giản của MyClass mà thấy rằng việc sử dụng các giá trị không có hộp bọc có thể giúp bạn tiết kiệm rất nhiều bộ nhớ, giảm áp lực GC và chi phí thực hiện rất nhỏ: chỉ khoảng 5-10%.

1. Với đấm bốc:

 const int N = 1000000; 
     MyClass myClass = new MyClass(); 

     double d = 0.0; 
     var sw = Stopwatch.StartNew(); 
     for (int i = 0; i < N; i++, d += 0.1) 
     { 
      myClass.BoxingSafeAdd((object)d); 
     } 
     Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds); 

     Console.WriteLine("Memory: {0} MB.", (double)GC.GetTotalMemory(false)/1024/1024); 

Kết quả:

Time: 130 ms 
Memory: 19.7345771789551 MB 

2.Nếu không có đấm bốc

 const int N = 1000000; 
     MyClass myClass = new MyClass(); 

     double d = 0.0; 
     var sw = Stopwatch.StartNew(); 
     for (int i = 0; i < N; i++, d += 0.1) 
     { 
      myClass.BoxingSafeAdd(d); 
     } 
     Console.WriteLine("Time: {0} ms", sw.ElapsedMilliseconds); 

     Console.WriteLine("Memory: {0} MB", (double)GC.GetTotalMemory(false)/1024/1024); 

Kết quả:

Time: 144 ms 
Memory: 12.4955024719238 MB 
+0

Điều này có vẻ rất tốt với tôi. Tôi ngạc nhiên khi thấy chi phí thực hiện (nhẹ). Tôi dự kiến ​​lambda biên dịch sẽ làm cho nó nhanh hơn đấm bốc. Bất kỳ ý tưởng tại sao nó không? – Timo

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