【问题标题】:Does encapsulating a quantity in a struct for type safety have performance implications?将数量封装在结构中以实现类型安全是否会对性能产生影响?
【发布时间】:2014-11-03 06:17:21
【问题描述】:

我使用这个约定(受 F# 单元的启发)来捕获某些类别的编程错误:

public struct Inch : IComparable<Inch>
{
    public readonly float Value;
    public Inch(int value) : this() { Value = value; }
    public static implicit operator Inch(float value) { return new Inch(value); }
    public int CompareTo(Inch other) { return Value.CompareTo(other.Value); }
    public override string ToString() { return Value.ToString(); }
}

加法等操作就是这样进行的:

Inch a, b;
Inch result = a.Value + b.Value;

这允许Inch 以值类型的低开销传递,其优点是它不会被意外分配给普通的float。 (我发现允许在相反方向进行隐式转换,即浮点数到Inches,通常不会导致错误。)

问题:在显示的加法示例中特别是是否存在已知的性能问题。同样,这个问题只是关于性能,我对语义没有任何疑问。

【问题讨论】:

  • 对于那些因过于宽泛而投票结束的人,我已将问题改写为非常狭窄。
  • 您的问题并不狭窄。您非常具体地将其缩小到给定的上下文。如果是这样,您可以在该上下文中分析代码并自己找出问题是否存在。您在问代码将如何在可能存在的每个单独的上下文中执行,由于范围,这当然是无法回答的。
  • 不,我不是这么问的。我在问这种方法是否存在已知问题,而不是一揽子验证。我承担使用此代码的风险。 :)
  • 如何缩小范围?在任何可能的情况下询问任何可能罗科的已知问题不再合理。
  • 我们在我们的一个应用程序中尝试了类似的方法,但成本太高(加法等基本操作的开销是 2 到 5 倍),所以我们备份了。但是进行了很多的计算。

标签: c# performance struct units-of-measurement


【解决方案1】:

这里最大的语义问题是使用implicit 运算符,它允许将任何float 值视为Inch 单元。您应该删除该运算符,或将其更改为 explicit

最大的性能问题是缺少重写的 EqualsGetHashCode 方法。您应该在default implementation provided by ValueType which may be slower slower 上覆盖这两种方法。一些运行时环境可能会检测到该类不直接或间接包含任何具有引用类型的字段并提供这些方法的有效实现,但对此不做任何保证。

为了提高可用性,您可以定义一个执行加法的operator +,这样您就可以拥有以下内容:

Inch a, b;
Inch result = a + b;

当然,operator - 也是如此。

理论上,该结构的性能开销接近于零,但它将取决于运行时环境的许多方面,尤其是内联方法的能力。关于开销是否可观察和/或可接受的最终答案只能通过使用预期输入分析预期环境中类型的使用来确定。

【讨论】:

  • 如前所述,我对语义没有问题;该方法实际上确实捕获了错误。您能否解释一下为什么您认为 Equals 和 GetHashCode 的默认实现较慢?只有一个字段,所以我希望性能与普通浮点数相同。
  • @bright 我已经用其他参考资料更新了我的答案,但我对隐式运算符语义的担忧非常强烈,以至于如果不包含反对它的声明,我会不舒服地提供答案。跨度>
  • 阅读链接后,我明白您对 Equals 和 GetHashCode 的含义 - 逐字节比较可能比优化的浮点比较慢。在那种情况下 - Equals 的什么实现将提供与 float 相同的性能? Value.Equals(((Inch)other).Value) 中涉及的转换似乎效率低下。
  • 仅供参考,这就是这种方法有效的原因。假设 Inch 和 Meter 在某处(从浮点数)初始化,并在将一个分配给另一个之前通过一堆代码(具有类型安全性)。这种方法将捕获错误,而如果两者都是浮动的,则不会找到错误。只能通过显式指定 Inch 来从裸浮点分配,即 Inch foo = 5;您不能在此处使用“var”。类似地,像 Inch result = a.Value + b.Value 这样的操作清楚地表明了一些不寻常的事情。唯一没有被捕获的情况是将浮点数传递给接受英寸的函数。
  • @bright Meter x, y; Inch result = x.Value + y.Value;。如果xy 在另一个不可见的范围内声明,审阅者无法确定添加在语义上是否正确。
【解决方案2】:

我在 LINQPad 4 中测试了以下代码并进行了全面优化:

    struct IntWrapper
    {
        readonly int x;

        public IntWrapper(int x)
        {
            this.x = x;
        }

        //[MethodImpl(MethodImplOptions.AggressiveInlining)] doesn't seem to matter
        public static implicit operator IntWrapper(int x)
        {
            return new IntWrapper(x);
        }

        //[MethodImpl(MethodImplOptions.AggressiveInlining)] doesn't seem to matter
        public static IntWrapper operator +(IntWrapper x, IntWrapper y)
        {
            return new IntWrapper(x.x + y.x);
        }
    }

    void Main()
    {
        var random = new Random();
        var sw = new Stopwatch();
        var xs = new List<int>(500);
        var mode = new Dictionary<int, int>();

        for (var j = 0; j < 500; j++)
        {
            sw.Start();

            for (var i = 0; i < 1000000; i++)
            {
                var x = random.Next();
                var y = random.Next();

                var z = x + y;
            }

            sw.Stop();

            var elasped = (int)sw.ElapsedMilliseconds;

            xs.Add(elasped);

            int count;

            if(!mode.TryGetValue(elasped, out count))
                mode.Add(elasped, 1);
            else
                mode[elasped] = count + 1;

            Console.WriteLine(elasped);

            sw.Reset();
        }

        Console.WriteLine(xs.Average());
        Console.WriteLine(Math.Sqrt(xs.Select(x => Math.Pow(x, 2)).Sum() / (xs.Count())));

        var max = 0;
        var key = 0;

        foreach (var memo in mode)
            if(memo.Value > max)
            {
                max = memo.Value;
                key = memo.Key;
            }

        Console.WriteLine(key);

    }

System.Int32IntWrapper 的情况下,我在 500 次试验中观察到平均(平均值)约为 30ms25ms 的众数。

有趣的是,我记得在某处读到过,ValueType` 类的 ValueType 字段上的 readonly 修饰符会导致性能下降,但我在这个实验中没有观察到这样的损失。

【讨论】:

    猜你喜欢
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-01
    • 2013-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多