在F#中,我们有几种非常好的设计时类型安全解决方案:类型别名和单用例结构联合(并且开始没有隐式转换!):
// type aliases are erased at compile time
type Offset = int64<offset>
// no allocations
[<Struct>]
type Offset = Offset of int64C#的替代方案是什么?
我从未见过标记结构(包含单个元素)的实际使用,但如果我们添加显式类型转换,则可以得到非常类似于F#中的类型别名的设计时行为。也就是说,IDE会抱怨类型不匹配,因此必须显式地转换值。
下面是一些POC代码:
public struct Offset {
private readonly long _value;
private Offset(long value) {
_value = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator Offset(long value) {
return new Offset(value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static explicit operator long(Offset offset) {
return offset._value;
}
}
public interface IIndex<T> {
Offset OffsetOf(T value);
T AtOffset(Offset offset);
}
public class SmapleUsage
{
public void Test(IIndex<long> idx)
{
// without explicit cast we have nice red squiggles
var valueAt = idx.AtOffset((Offset)123);
long offset = (long)idx.OffsetOf(42L);
}
}所以,IDE的事情很好!但我要问的是什么是性能的影响和其他的缺点,为了避免“只是衡量它”的评论只是衡量它和停止写这个问题的最初.但结果却是违反直觉的:
[Test]
public void OffsetTests() {
var array = Enumerable.Range(0, 1024).ToArray();
var sw = new Stopwatch();
for (int rounds = 0; rounds < 10; rounds++) {
sw.Restart();
long sum = 0;
for (int rp = 0; rp < 1000000; rp++) {
for (int i = 0; i < array.Length; i++) {
sum += GetAtIndex(array, i);
}
}
sw.Stop();
if (sum < 0) throw new Exception(); // use sum after loop
Console.WriteLine($"Index: {sw.ElapsedMilliseconds}");
sw.Restart();
sum = 0;
for (int rp = 0; rp < 1000000; rp++) {
for (int i = 0; i < array.Length; i++) {
sum += GetAtOffset(array, (Offset)i);
}
}
if (sum < 0) throw new Exception(); // use sum after loop
sw.Stop();
Console.WriteLine($"Offset: {sw.ElapsedMilliseconds}");
sw.Restart();
sum = 0;
for (int rp = 0; rp < 1000000; rp++) {
for (int i = 0; i < array.Length; i++) {
sum += array[i];
}
}
if (sum < 0) throw new Exception(); // use sum after loop
sw.Stop();
Console.WriteLine($"Direct: {sw.ElapsedMilliseconds}");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetAtIndex(int[] array, long index) {
return array[index];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetAtOffset(int[] array, Offset offset) {
return array[(long)offset];
}令人惊讶的是,在i7@2.2Hzx64/Release上,使用Offset的情况在每一轮测试中都明显更快--典型值如下:
Int64: 1046
Offset: 932
Direct: 730与仅仅使用int64相比,我希望得到相同或更慢的结果。那么,这里发生了什么?你能复制同样的差异或发现一些不足,例如,如果我测量不同的东西?
发布于 2016-08-27 15:42:43
1.一旦在Int64测试中将for (int i = 0;替换为for (long i = 0;,其性能将与直接测试相同。
在使用int时,它生成这样的x86-64指令:
inc ecx
cmp ecx,0F4240h在使用long时,它生成这样的x86-64指令:
inc rcx
cmp rcx,0F4240h 因此,使用32位寄存器ecx或其64位版本的rcx的唯一不同之处在于,后者由于CPU的设计而速度更快。
2.在偏移测试中使用long作为迭代器,您将看到类似的性能。
3.,因为代码是在发布模式下优化的,所以使用Int64或Offset几乎没有区别,但是在某些时候,指令是稍微重新排列的。
在使用偏移量时(减少一条指令):
movsxd rdx,eax
movsxd r8,r14d
cmp rdx,r8
jae <address> 在使用Int64时(多一条指令):
movsxd rdx,r14d
movsxd r8,eax
cmp r8,rdx
jae <address>
movsxd rdx,eax 4.直接测试是最快的,因为它不使用上面第3段所示的指令进行数组边界检查。这种优化是在编写如下循环时发生的:
for (var i=0; i<array.Length; i++) { ... array[i] ... }通常,如果索引超出了数组的范围,它会抛出IndexOutOfRangeException,但在本例中编译器知道这是不可能发生的,所以它省略了检查。
然后,即使在其他测试中有额外的指令,它们由于CPU分支预测器而具有类似的性能,如果需要的话,CPU分支预测器会提前开始运行指令,如果条件失败,则丢弃结果。
https://stackoverflow.com/questions/39179385
复制相似问题