Performance F#已检查计算的性能影响?
使用选中的模块是否会影响性能?我用int类型的序列进行了测试,没有发现明显的差异。有时检查的版本更快,有时未检查的版本更快,但通常不会太快Performance F#已检查计算的性能影响?,performance,f#,integer-overflow,Performance,F#,Integer Overflow,使用选中的模块是否会影响性能?我用int类型的序列进行了测试,没有发现明显的差异。有时检查的版本更快,有时未检查的版本更快,但通常不会太快 Seq.initInfinite (fun x-> x) |> Seq.item 1000000000;; 基本上,我是想弄清楚总是开支票是否会有任何负面影响。(我遇到了一个不是很明显的溢出,所以我现在扮演一个被抛弃的恋人,他不想再让我伤心。)我能想到的不总是使用Checked的唯一非人为原因是如果有一些性能上的问题,但是我还没有看到过。当你衡
Seq.initInfinite (fun x-> x) |> Seq.item 1000000000;;
基本上,我是想弄清楚总是开支票是否会有任何负面影响。(我遇到了一个不是很明显的溢出,所以我现在扮演一个被抛弃的恋人,他不想再让我伤心。)我能想到的不总是使用Checked的唯一非人为原因是如果有一些性能上的问题,但是我还没有看到过。当你衡量性能时,通常不应该把
Seq
包括在内,因为Seq
会增加很多开销(至少与int操作相比),所以你可能会把大部分时间花在Seq
上,而不是花在你喜欢测试的代码上
我为(+)
编写了一个小测试程序:
因此,使用Checked似乎增加了一些开销。int add的成本应该小于循环开销,因此选中的开销高于2x
可能更接近4x
出于好奇,我们可以使用诸如ILSpy
之类的工具检查IL代码:
未选中:
IL_0000: nop
IL_0001: ldarg.2
IL_0002: ldarg.0
IL_0003: bge.s IL_0014
IL_0005: ldarg.0
IL_0006: ldarg.1
IL_0007: ldc.i4.1
IL_0008: add
IL_0009: ldarg.2
IL_000a: ldc.i4.1
IL_000b: add
IL_000c: starg.s i
IL_000e: starg.s a
IL_0010: starg.s c
IL_0012: br.s IL_0000
; if i < c then
00007FF926A611B3 cmp esi,ebx
00007FF926A611B5 jge 00007FF926A611BD
; i + 1
00007FF926A611B7 inc esi
; a + 1
00007FF926A611B9 inc edi
; loop (a + 1) (i + 1)
00007FF926A611BB jmp 00007FF926A611B3
检查:
IL_0000: nop
IL_0001: ldarg.2
IL_0002: ldarg.0
IL_0003: bge.s IL_0014
IL_0005: ldarg.0
IL_0006: ldarg.1
IL_0007: ldc.i4.1
IL_0008: add.ovf
IL_0009: ldarg.2
IL_000a: ldc.i4.1
IL_000b: add.ovf
IL_000c: starg.s i
IL_000e: starg.s a
IL_0010: starg.s c
IL_0012: br.s IL_0000
; if i < c then
00007FF926A62613 cmp esi,ebx
00007FF926A62615 jge 00007FF926A62623
; a + 1
00007FF926A62617 add edi,1
; Overflow?
00007FF926A6261A jo 00007FF926A6262D
; i + 1
00007FF926A6261C add esi,1
; Overflow?
00007FF926A6261F jo 00007FF926A6262D
; loop (a + 1) (i + 1)
00007FF926A62621 jmp 00007FF926A62613
唯一的区别是,未选中使用add
,选中使用add.ovf
add.ovf
是通过溢出检查添加的
通过查看jittedx86_64
代码,我们可以更深入地挖掘
未选中:
IL_0000: nop
IL_0001: ldarg.2
IL_0002: ldarg.0
IL_0003: bge.s IL_0014
IL_0005: ldarg.0
IL_0006: ldarg.1
IL_0007: ldc.i4.1
IL_0008: add
IL_0009: ldarg.2
IL_000a: ldc.i4.1
IL_000b: add
IL_000c: starg.s i
IL_000e: starg.s a
IL_0010: starg.s c
IL_0012: br.s IL_0000
; if i < c then
00007FF926A611B3 cmp esi,ebx
00007FF926A611B5 jge 00007FF926A611BD
; i + 1
00007FF926A611B7 inc esi
; a + 1
00007FF926A611B9 inc edi
; loop (a + 1) (i + 1)
00007FF926A611BB jmp 00007FF926A611B3
;如果我
检查:
IL_0000: nop
IL_0001: ldarg.2
IL_0002: ldarg.0
IL_0003: bge.s IL_0014
IL_0005: ldarg.0
IL_0006: ldarg.1
IL_0007: ldc.i4.1
IL_0008: add.ovf
IL_0009: ldarg.2
IL_000a: ldc.i4.1
IL_000b: add.ovf
IL_000c: starg.s i
IL_000e: starg.s a
IL_0010: starg.s c
IL_0012: br.s IL_0000
; if i < c then
00007FF926A62613 cmp esi,ebx
00007FF926A62615 jge 00007FF926A62623
; a + 1
00007FF926A62617 add edi,1
; Overflow?
00007FF926A6261A jo 00007FF926A6262D
; i + 1
00007FF926A6261C add esi,1
; Overflow?
00007FF926A6261F jo 00007FF926A6262D
; loop (a + 1) (i + 1)
00007FF926A62621 jmp 00007FF926A62613
;如果我
现在,检查开销的原因显而易见。在每次操作之后,抖动插入条件指令jo
,如果设置了溢出标志,该指令将跳转到引发溢出异常的代码
这表明整数加法的开销小于1个时钟周期。它小于1个时钟周期的原因是现代CPU可以并行执行某些指令
图表还显示,CPU正确预测的分支大约需要1-2个时钟周期
因此,假设吞吐量至少为2,在未检查的示例中,两个整数相加的代价应该是1个时钟周期
在选中的示例中,我们执行add,jo,add,jo
。在这种情况下,CPU很可能无法并行化,其成本应该在4-6个时钟周期左右
另一个有趣的区别是加法的顺序改变了。对于选中的添加,操作的顺序很重要,但是对于未选中的抖动(和CPU),移动操作具有更大的灵活性,可能会提高性能
长话短说;对于像(+)
这样的廉价操作,与未选中的相比,选中的的开销应该在4x-6x
左右
这假定没有溢出异常。.NET异常的成本可能是整数加法的10万倍。请注意,您的示例中似乎没有使用任何算术运算符,因此这可能不是对其效率的良好测试…可能是这样,但是整数类型在FSharp.Core.Operators.Checked中被重载,所以问题的一部分是确定当溢出可能不是一个问题时是否会影响int类型的正常使用。这些不是您看到的类型,而是用于转换为数值类型的函数。这些类型完全相同,因此您当前的测试不会显示任何内容。如果你用checked做一些算术运算,将会有一些额外的IL指令,所以它看起来会稍微慢一点,但很少有足够的担心。谢谢你的澄清。
; if i < c then
00007FF926A62613 cmp esi,ebx
00007FF926A62615 jge 00007FF926A62623
; a + 1
00007FF926A62617 add edi,1
; Overflow?
00007FF926A6261A jo 00007FF926A6262D
; i + 1
00007FF926A6261C add esi,1
; Overflow?
00007FF926A6261F jo 00007FF926A6262D
; loop (a + 1) (i + 1)
00007FF926A62621 jmp 00007FF926A62613