【问题标题】:C# Generic Interfaces Design & PerformanceC# 通用接口设计与性能
【发布时间】:2018-09-11 14:18:17
【问题描述】:

我有一个关于泛型接口的基本问题。

案例一:

public interface IDataProcesser
{
    TOut Process<Tin,TOut>(Tin input);
}

案例 2:

public interface IDataProcesser<Tin,TOut>
{
    TOut Process(Tin input);
}

案例 1 会导致装箱/拆箱吗?与案例 2 相比,它的性能是否更低。在设计通用接口时是否有任何指导方针?

谢谢你, 拉维

【问题讨论】:

  • 这在很大程度上取决于TinTOut 的真实类型,无论如何,只需使用一些分析器进行测量。
  • 了解为什么您认为这里可能涉及拳击会很有趣。这两种方法之间不应该存在任何性能差异,即使存在差异,也会影响系统整体性能的重要差异吗?
  • Useful background material。您的问题距离需要答案还有几个阶段,到时候您将拥有的不仅仅是这些界面。
  • Boxing is the process of converting a value type to the type object.. 创建泛型的目的之一是帮助避免拳击。
  • 不会发生装箱/拆箱,因为您正在指定类型。我看不出性能不足。

标签: c# .net generics boxing


【解决方案1】:

方法一比方法二性能更好(至少一台我的笔记本电脑 - 请参阅下面的代码)。但是,这并不是这里真正重要的部分。

  1. 如果泛型类型TInTOut对接口有一些语义,或者接口上的大部分方法,那么你应该在接口上包含泛型描述。

    李>
  2. 如果接口上定义的只有一个或几个方法使用泛型类型,则应在方法上使用泛型描述。

但是,当您特别询问哪个性能更高时,我输入了一些快速代码并对其进行了测试。令我惊讶的是,第一种方法实际上性能更高。

方法一运行耗时 3669 毫秒,而方法二耗时 2715。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;

namespace Sandbox
{
    public interface IDataProcesser
    {
        TOut Process<TIn, TOut>(TIn input);
    }

    public interface IDataProcesser2<TIn, TOut>
    {
        TOut Process(TIn input);
    }


    class Class1 : IDataProcesser
    {
        public TOut Process<Tin, TOut>(Tin input)
        {
            return default(TOut);
        }
    }

    class Class2 : IDataProcesser2<int, long>
    {
        public long Process(int input)
        {
            return default(long);
        }
    }

    class Program
    {
        private static int _loopCount = 1000000000;

        static void Main(string[] args)
        {
            var warmupEquals = false;
            var equals1 = false;
            var equals2 = false;

            for (long i = 0; i < _loopCount; i++)
            {
                Class1 warmup = new Class1();
                var w1 = warmup.Process<int, long>(default(int)) == 0;
                warmupEquals = w1;
            }

            var sw = new Stopwatch();
            sw.Start();
            for (long i = 0; i < _loopCount; i++)
            {
                Class1 c1 = new Class1();
                var t1 = c1.Process<int, long>(default(int)) == 0;
                if (t1)
                {
                    equals1 = true;
                }
            }

            sw.Stop();
            Console.WriteLine("Method 1");
            Console.WriteLine(sw.ElapsedMilliseconds);

            sw.Restart();
            sw.Start();
            for (long i = 0; i < _loopCount; i++)
            {
                Class2 c2 = new Class2();
                var t2 = c2.Process(default(int)) == 0;
                if (t2)
                {
                    equals2 = true;
                }
            }

            sw.Stop();
            Console.WriteLine("Method 2");
            Console.WriteLine(sw.ElapsedMilliseconds);
            Console.WriteLine(warmupEquals);
            Console.WriteLine(equals1);
            Console.WriteLine(equals2);
            Console.ReadLine();
        }
    }
}

【讨论】:

  • 非常感谢您花时间编写代码,非常感谢。如果您也进行热身,您认为 Class2 的性能会提高吗?既然你预热了 Class1,处理器会在预热期间缓存指令吗?
  • 不,我不认为它会产生影响。如果它有所作为,我会感到有点惊讶——但你可以轻松地测试它,但在你自己的机器上运行代码。毕竟 - 硬件很重要:)
  • 在您的测试中,您在哪里说明了您正在分配对象并将分配计费到一个时间间隔,但是 解除分配 这些对象可能在 不同 时间间隔?在您的第一个秒表间隔中可能有零次收集,然后在第二个秒表间隔中,收集器停止世界并收集在第一个间隔中分配的所有对象,从而摆脱您的计时。使用秒表获得正确的时间属性非常棘手。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-09
  • 2018-12-16
  • 1970-01-01
  • 1970-01-01
  • 2017-07-17
  • 2014-03-21
相关资源
最近更新 更多