F# For循环输入列表
人们经常使用F# For循环输入列表,f#,control-flow,F#,Control Flow,人们经常使用 for i in [0 .. 10] do something 但如果创建一个列表,然后进行迭代,那么在我看来,使用它会更有意义 for i = 0 to 10 do something 没有创建不必要的列表,但具有相同的行为。 我错过什么了吗?(我想是这样的)你是对的,在[0..10]中为I编写“do something”会生成一个列表,它确实有很大的开销。虽然您也可以省略方括号,但在这种情况下,它只构建了一个惰性序列(而且,事实证明编译器甚至优化了这种情况)。我通常更喜欢在
for i in [0 .. 10] do something
但如果创建一个列表,然后进行迭代,那么在我看来,使用它会更有意义
for i = 0 to 10 do something
没有创建不必要的列表,但具有相同的行为。
我错过什么了吗?(我想是这样的)你是对的,在[0..10]中为I编写
“do something”
会生成一个列表,它确实有很大的开销。虽然您也可以省略方括号,但在这种情况下,它只构建了一个惰性序列(而且,事实证明编译器甚至优化了这种情况)。我通常更喜欢在0中编写。。100 do
,因为它看起来与在序列上迭代的代码相同
使用F#interactive的#time
功能进行简单分析:
for i in [ 0 .. 10000000 ] do // 3194ms (yikes!)
last <- i
for i in 0 .. 10000000 do // 3ms
last <- i
for i = 0 to 10000000 do // 3ms
last <- i
for i in seq { 0 .. 10000000 } do // 709ms (smaller yikes!)
last <- i
[0..10000000]do//3194ms(yikes!)中的i的
最后前一种形式需要语言中的特殊构造(对于var from…to…by),这是古代编程语言遵循的方式:
- Fortran中的“do”循环
- 对于var:=expr到expr(以帕斯卡为单位)
- 等等
后一种形式(表示某物中的var)更为通用。它适用于普通列表,但也适用于生成器(如python)等。在运行列表之前,可能不需要构建完整列表。这允许在可能无限的列表上写入循环
无论如何,一个好的编译器/解释器应该认识到相当频繁的特例[expr1..expr2],并避免中间列表的计算和存储。给出了一种不同的答案,但希望对某些人来说有趣
在这种情况下,F#编译器无法应用快速for循环优化,这是正确的。好消息是,F#编译器是开源的,我们可以改进它的行为
这是我的免费赠品:
快速for循环优化发生在。这是一个相当原始的时刻,对我们来说是一个很好的机会来提高
// Detect the compiled or optimized form of a 'for <elemVar> in <startExpr> .. <finishExpr> do <bodyExpr>' expression over integers
// Detect the compiled or optimized form of a 'for <elemVar> in <startExpr> .. <step> .. <finishExpr> do <bodyExpr>' expression over integers when step is positive
let (|CompiledInt32ForEachExprWithKnownStep|_|) g expr =
match expr with
| Let (_enumerableVar, RangeInt32Step g (startExpr, step, finishExpr), _,
Let (_enumeratorVar, _getEnumExpr, spBind,
TryFinally (WhileLoopForCompiledForEachExpr (_guardExpr, Let (elemVar,_currentExpr,_,bodyExpr), m), _cleanupExpr))) ->
let spForLoop = match spBind with SequencePointAtBinding(spStart) -> SequencePointAtForLoop(spStart) | _ -> NoSequencePointAtForLoop
Some(spForLoop,elemVar,startExpr,step,finishExpr,bodyExpr,m)
| _ ->
None
let DetectFastIntegerForLoops g expr =
match expr with
| CompiledInt32ForEachExprWithKnownStep g (spForLoop,elemVar,startExpr,step,finishExpr,bodyExpr,m)
// fast for loops only allow steps 1 and -1 steps at the moment
when step = 1 || step = -1 ->
mkFastForLoop g (spForLoop,m,elemVar,startExpr,(step = 1),finishExpr,bodyExpr)
| _ -> expr
您如何确定这是您需要匹配的模式?我经常采用的方法是使用正确的属性编写一个简单的F#程序,并在编译过程中放置一个断点来检查表达式。由此,我创建了要匹配的模式:
让我们把这两种模式放在一起:
let (|ExtractInt32Range|_|) g expr =
match expr with
| RangeInt32Step g range -> Some range
| SeqRangeInt32Step g range -> Some range
| _ -> None
compiledint22foreachxprwithknownstep
更新为使用ExtractInt32Range
overRangeInt32Step
完整的解决方案如下:
let (|SeqRangeInt32Step|_|) g expr =
match expr with
// detect '[n .. m]'
| Expr.App(Expr.Val(toList,_,_),_,[TType_var _],
[Expr.App(Expr.Val(seq,_,_),_,[TType_var _],
[Expr.Op(TOp.Coerce, [TType_app (seqT, [TType_var _]); TType_var _],
[RangeInt32Step g (startExpr, step, finishExpr)], _)],_)],_)
when
valRefEq g toList (ValRefForIntrinsic g.seq_to_list_info) &&
valRefEq g seq g.seq_vref &&
tyconRefEq g seqT g.seq_tcr ->
Some(startExpr, step, finishExpr)
| _ -> None
let (|ExtractInt32Range|_|) g expr =
match expr with
| RangeInt32Step g range -> Some range
| SeqRangeInt32Step g range -> Some range
| _ -> None
// Detect the compiled or optimized form of a 'for <elemVar> in <startExpr> .. <finishExpr> do <bodyExpr>' expression over integers
// Detect the compiled or optimized form of a 'for <elemVar> in <startExpr> .. <step> .. <finishExpr> do <bodyExpr>' expression over integers when step is positive
let (|CompiledInt32ForEachExprWithKnownStep|_|) g expr =
match expr with
| Let (_enumerableVar, ExtractInt32Range g (startExpr, step, finishExpr), _,
Let (_enumeratorVar, _getEnumExpr, spBind,
TryFinally (WhileLoopForCompiledForEachExpr (_guardExpr, Let (elemVar,_currentExpr,_,bodyExpr), m), _cleanupExpr))) ->
let spForLoop = match spBind with SequencePointAtBinding(spStart) -> SequencePointAtForLoop(spStart) | _ -> NoSequencePointAtForLoop
Some(spForLoop,elemVar,startExpr,step,finishExpr,bodyExpr,m)
| _ ->
None
let(| SeqRangeInt32Step | | |)g expr=
匹配表达式
//检测“[n..m]”
|Expr.App(Expr.Val(toList,u,u),u,[t类型u变量u],
[Expr.App(Expr.Val(seq,u,u),[t类型u变量],
[Expr.Op(TOp.胁迫,[t类型应用程序(seqT,[t类型变量]);t类型变量],
[RangeInt32步骤g(开始、步骤、完成expr)],[uu1],[u2],[u2],[u2]
什么时候
valRefEq g toList(ValrefFor内部g.seq至列表信息)&&
valRefEq g seq g.seq_vref&&
tyconRefEq g序列g.seq_tcr->
一些(startExpr、step、finishExpr)
|无
let(| ExtractInt32Range | |)g expr=
匹配表达式
|RangeInt32步骤g范围->某些范围
|SeqRangeInt32步骤g范围->某些范围
|无
//检测“for in.”的编译或优化形式。。整数上的do'表达式
//检测“for in…”的编译或优化形式。。阶跃为正时整数上的do表达式
let(| compiledint22foreachexprwithknownstep | | |)g expr=
匹配表达式
|Let(_enumerableVar,ExtractInt32Range g(startExpr,step,finishExpr)),
Let(_enumeratorVar,_getEnumExpr,spBind,
尝试最终(当LoopforCompiledForEachExpr(\u-guardExpr,Let(elemVar,\u-currentExpr,\u,bodyExpr,m),\u-cleanupExpr))时->
让spForLoop=将spBind与SequencePointAtBinding(spStart)->SequencePointAtForLoop(spStart)| |->NoSequencePointTorLoop匹配
一些(spForLoop、elemVar、startExpr、step、finishExpr、bodyExpr、m)
| _ ->
没有一个
使用简单的测试程序
let print v =
printfn "%A" v
[<EntryPoint>]
let main argv =
for x in [0..10] do
print x
0
让我们打印v=
打印fn“%A”v
[]
让主argv=
对于[0..10]中的x,请执行以下操作
打印x
0
在进行优化之前,相应的C#代码应该是这样的(IL代码更易于检查,但如果不习惯,则可能有点难以理解):
//测试
[入口点]
公共静态int main(字符串[]argv)
{
FSharpList FSharpList=SeqModule.ToList(Operators.CreateSequence(Operators.OperatorIntrinsics.RangeInt32(0,1,10));
IEnumerator枚举器=((IEnumerable)fSharpList).GetEnumerator();
尝试
{
while(枚举数.MoveNext())
{
测试。打印(枚举器。当前);
}
}
最后
{
IDisposable disposable=作为IDisposable的枚举器;
if(一次性!=null)
{
一次性的,一次性的;
}
}
返回0;
}
F#创建一个列表,然后使用枚举器对其进行迭代。难怪它比经典的for循环要慢
应用优化后,我们得到以下代码:
// Test
[EntryPoint]
public static int main(string[] argv)
{
for (int i = 0; i < 11; i++)
{
Test.print<int>(i);
}
return 0;
}
//测试
[入口点]
公共静态int main(字符串[]argv)
{
对于(int i=0;i<11;i++)
{
测试打印(一);
}
返回0;
}
这是一个显著的进步
所以,偷取这个密码,发布一个公关,享受荣耀。当然,您需要添加单元测试和发出的IL代码测试,这可能有点棘手,无法找到合适的级别,请检查此项以获取灵感
PS.可能还应该支持[|0..10}]
seq{0..10}
另外,对于0L..10L中的v执行打印v
以及对于0..2..10中的v执行打印v
在F#中也是无效的。只是出于好奇,我在启用llvm优化器的mono上尝试了这一点。这没用;我得到了大致相同的结果。我不确定第一个结果
// Test
[EntryPoint]
public static int main(string[] argv)
{
FSharpList<int> fSharpList = SeqModule.ToList<int>(Operators.CreateSequence<int>(Operators.OperatorIntrinsics.RangeInt32(0, 1, 10)));
IEnumerator<int> enumerator = ((IEnumerable<int>)fSharpList).GetEnumerator();
try
{
while (enumerator.MoveNext())
{
Test.print<int>(enumerator.Current);
}
}
finally
{
IDisposable disposable = enumerator as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
return 0;
}
// Test
[EntryPoint]
public static int main(string[] argv)
{
for (int i = 0; i < 11; i++)
{
Test.print<int>(i);
}
return 0;
}