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