【问题标题】:Span is slow (not optimized correctly)跨度很慢(未正确优化)
【发布时间】:2021-05-19 14:56:05
【问题描述】:

我这里有这种情况:

public class Bla<T> where T : unmanaged
{
    private unsafe T* _data;
    private int _dataLength;

    public T this[int index]
    {
        get => Span[index];
        set => Span[index] = value;
    }
    public unsafe Span<T> Span => new Span<T>(_data, _dataLength);
}

上面的类有一个指向某些数据的指针,以及一个指定数据长度的字段。还有一个属性Span,它只是创建一个新的Span_data

现在在一个调用Bla 十亿次索引器的基准测试中,我发现它比这慢了大约 20%

public class Bla<T> where T : unmanaged
{
    private T[] _data;

    public T this[int index]
    {
        get => _data[index];
        set => _data[index] = value;
    }
}

请注意,在这两个示例中,_data 的分配方式完全相同相同。这里的问题很清楚;每次我们想要访问_data 时创建一个新的Span 并不是最优的。

我每次在索引器中创建Span 的原因是出于安全考虑。跨度提供了一些必要的边界检查哪些通过指针访问没有。

我可以只添加一个 if 语句来检查边界,但这使得它几乎比 Span 方法慢 2x。 (只检查索引是否小于 0 或等于长度)

我该怎么做才能让C# 更好地优化代码?或者甚至有可能吗?我测试了.NET 5.NET 6.0 Preview 4,结果都差不多。

【问题讨论】:

  • Memory&lt;T&gt; 可以作为您班级中的一个字段。可能是您的索引器的更好选择。将 Span 留在您可能想要访问多个元素的任何地方。
  • @JeremyLakeman 不幸的是,我无法从指针创建Memory&lt;T&gt;,而且Memory&lt;T&gt; 甚至没有索引器。
  • 边界检查究竟如何使处理时间加倍?你用的是浮点数还是什么?如果您当前的逻辑与整数比较所用的时间相同,那么它已经快如闪电了。
  • @JohnWu 我认为这与分支预测有关。我只是在做 2 个简单的整数比较

标签: c# .net optimization


【解决方案1】:

不断创建 span 实例确实不是免费的(即使它是值类型)。我们看不到调用代码,但我预计如果该代码被调整以避免索引器,而是获取跨度一次,并执行多个操作该跨度 - 这可能会有所帮助,尤其是对于 for/foreach 循环数据。另请注意,如果Bla&lt;T&gt; 只是暂时使用,则可以将其写为ref struct,其意义在于ref struct 可以将跨度直接存储为字段 (而不是指针和长度),避免一些开销。

特别是在重构消费代码以考虑跨度时:SIMD。在许多情况下,您可以有效地将跨度强制转换为向量类型(通过MemoryMarshal),以使用块运算符执行额外的优化。

【讨论】:

    【解决方案2】:

    如何让 C# 更好地优化代码?

    您是否考虑过没有办法更好地优化代码?您需要额外的功能(边界检查)。您自己的实现很慢,.NET 实现在优化方面已经遥遥领先,但您无法以相同的速度获得额外的功能。那是不会发生的。更多代码、更多指令、更多执行周期。

    如果您发布了一个用于测量的完整程序,我们可能会说更多,但仅从外观来看,您似乎必须决定是否要放弃边界检查速度或安全但慢一点。这个问题已经存在很长时间了,远早于计算机。安全快是不可能的,你必须优先考虑一个。

    【讨论】:

    • 但是,如果您将 _data 用作数组,那么它的性能会更高,但仍能保持安全性。有没有办法从指针中获取数组?
    • 我想这一切都取决于你想做什么?我指针后面的数据不可变?你有记忆吗?然后将其复制成您需要的任何形式。这是一次性的实例化成本。可能是值得的。也许不吧。我们不能说。
    • @nvoigt 只是说:跨度几乎总是比复制某些东西更可取;整个“具有任意内存的零副本”是 为什么存在跨度
    猜你喜欢
    • 1970-01-01
    • 2014-01-09
    • 1970-01-01
    • 2021-03-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-05
    相关资源
    最近更新 更多