Unfortunately, afaik F# does not support user-defined compiler rewrite rules like Haskell. Therefore, unless Don & co. find time to provide optimizations using this technique, we are not likely to see them implemented in F#.
By the way, that was a great paper: I get how List/Seq/Array code could be made significantly faster with these techniques. I've just added most of the Seq operators to Jscript Enumerator (for work), as well as implemented most of Array/List on Jscript Array, so this was a very timely article. Thanks!
That's not entirely true. I believe F#'s annotations feature should give us some of the things we would need. But the question was posed to find out not only if we could implement stream fusion but if it was needed. Does the F# compiler suffer from the problem of generating a lot of intermediate structures to support a lot of the list/seq/array manipulation done in F#? If it does then I think a good feature request would be to enable stream fusion at some level for pure functions.
Does the F# compiler suffer from the problem of generating a lot of intermediate structures to support a lot of the list/seq/array manipulation done in F#? If it does then I think a good feature request would be to enable stream fusion at some level for pure functions.
Not sure about allocating intermediate structures, but the following code processes each stage of the pipeline independently: first, all elements are mapped, then all elements are filtered, then all elements go thru reduce_left. From what I read of Stream Fusion, a fusing compiler should be able to iterate over the elements once, and apply a fused set of methods to the stream, saving both iteration overhead as well as intermediate structures.
1 2 3 4 5 6 7 8 9
#light let test lst = lst |> List.map (fun x -> x * 2) |> List.filter (fun x -> x < 100) |> List.reduce_left (+) test [1;2;3;4;5;6]
Stream Fusion is cool stuff, and a good read... even I could understand it!
-- Jay
The only way to make that code better would be to do the following
1 2 3 4 5 6 7 8 9
#light let test (seq:'a Seq) = seq |> Seq.map (fun x -> x * 2) |> Seq.filter (fun x -> x < 100) |> Seq.fold (+) test seq{for i in 1..6 do yield i}
in which case I imagine each step in the pipeline should process the first thing from the sequence followed by the second and so forth. The problem is filter could consume more than one item out of the pipeline before emitting anything for the rest of the pipeline to make use of. You can essentially do the same thing with lazy lists, if they are described using LazyList.unfold. That should, at least in my mind, work the same way as a Stream. It still however suffers from the filter problem I outlined above which Streams are not susceptible to since they have a Skip Node. Also LazyList's need to memoize the entire results once the head of rest is calculated. Something Stream also doesn't do.
The other thing to make note of the only thing that I saw in the entire paper about rewrite rules was
forall s. stream(unstream s) -> s
asside from that I didnt see any other rewrite rules, at least for the basics shown in the paper. If you needed to build something a bit more complicated where that rewrite rule wouldnt eliminate all the intermediate Step constructor allocations. Then you would need a full rewrite facility. I would actually like to have a sufficient enough understanding of annotations to see if it could be used in some way to implement the rewrite rule facility, till then I guess I can just sit back and dream.
I remain unconvinced by geneverov's reply: unless I am mistaken, it still appears that idiomatic F# could benefit quite a bit by applying techniques from the Stream Fusion paper.
Let's look at the native x86 code generated for the following, rewritten from the example above so that it compiles on F# CTP (1.9.6.2).
1 2 3 4 5 6 7 8 9 10 11 12
#light let test (xs : int seq) = System.Diagnostics.Debugger.Break() xs |> Seq.map (fun x -> x * 2) |> Seq.filter (fun x -> x < 100) |> Seq.reduce (+) let v = test ( seq {for i in 1..6 do yield i} ) printfn "%i" v
Stepping through the optomized code shows the following disassembly for the map call:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
let test (xs : int seq) = System.Diagnostics.Debugger.Break() xs |> Seq.map (fun x -> x * 2) 00000000 push esi 00000001 push eax 00000002 mov dword ptr [esp],ecx 00000005 mov esi,edx 00000007 cmp dword ptr ds:[000D2E08h],0 0000000e je 00000015 00000010 call 79D47B47 >00000015 nop 00000016 mov eax,esi 00000018 add eax,eax 0000001a pop ecx 0000001b pop esi 0000001c ret
Stepping again shows the following code for the filter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
let test (xs : int seq) = System.Diagnostics.Debugger.Break() xs |> Seq.map (fun x -> x * 2) |> Seq.filter (fun x -> x < 100) 00000000 push esi 00000001 push eax 00000002 mov dword ptr [esp],ecx 00000005 mov esi,edx 00000007 cmp dword ptr ds:[000D2E08h],0 0000000e je 00000015 00000010 call 79D47B17 >00000015 nop 00000016 cmp esi,64h 00000019 setl al 0000001c movzx eax,al 0000001f pop ecx 00000020 pop esi 00000021 ret
And, finally, the reduce:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
let test (xs : int seq) = System.Diagnostics.Debugger.Break() xs |> Seq.map (fun x -> x * 2) |> Seq.filter (fun x -> x < 100) |> Seq.reduce (+) 00000000 push esi 00000001 push eax 00000002 mov dword ptr [esp],ecx 00000005 mov esi,edx 00000007 cmp dword ptr ds:[000D2E08h],0 0000000e je 00000015 00000010 call 79D47A8F 00000015 nop 00000016 mov eax,dword ptr [esp+0Ch] 0000001a add eax,esi 0000001c pop ecx 0000001d pop esi 0000001e ret 4
This appears to clearly depict overhead for each independent stage in the pipeline. I think the CLR does some magic, by pushing most of the sequence on the stack up front, then unwinding the stack as it calculates each stage and leaving some intermediate values in registers. None-the-less, unnecessary overhead (compared to ideal native code) does clearly exist here.
IMHO, the core concept from the Stream Fusion paper is to transform lists into streams (similar to sequences), yes, but then to <i>also <i>combine (fuse) all the pipeline stages into a <i>single <i>function call that is optomized as a whole. This is possible due to the way each stream "element" is normalized into essentially an "element option", as in Some(element) or None, allowing reduction of filtering down to an if guard. By fusing the pipeline, it should be possible to eliminate the setup for two out of the three calls on each loop iteration, including a number of pushes, pops, and calls. I suspect that inlining and standard optomization could further improve the pipeline performance. Finally, while I did not directly witness superflous objects getting created in this simple example, given the fact that each pipeline stage appeared independent leads me to suspect that there are optomizations and attendent performance gains to be had here, too, for a Stream Fusing compiler. -- Jay
IIRC, if you have something like this
1
map f $ filter g $ xs
the technique in the paper does not (at least not in general) fuse the definitions of f and g.
The usefulness of the paper in Haskell is that it eliminates intermediate constructions of Cons cells.
Compositions of simple seq functions in F# (e.g., Seq.map, Seq.filter) do not cause intermediate data construction because the methods MoveNext and get_Current are similarly composed.
The paper presents a technique for eliminating intermediate results in a composition of list functions by transforming list functions into stream functions. For the most part, streams from this paper are seq/IEnumerables in F#. Since F# programmers commonly and directly program in terms of streams, having the compiler transform and optimize list code in terms of streams is a little extravagant for the complexity it adds.
Even if your underlying data structure is an array or list you can still process it as a stream in F# through sub-typing, hence avoiding intermediate results.
1 2 3 4 5 6
let test seq = seq |> Seq.map (fun x -> x * 2) |> Seq.filter (fun x -> x < 100) |> Seq.fold (+) 0 test [| 1..6 |]
The compiler optimizations that make this paper work are not primarily user-defined rewrite rules, but rather standard general-purpose optimizations performed internally by GHC. It is not really feasible for FSC to perform many of these optimizations because it is not a pure language.
IEnumerable's MoveNext and Current are analogous to the stepper functions in this paper. The purpose of Skip is to make the stepper functions non-recursive to enable inlining optimizations. For IEnumerable, MoveNext can be recurisve because we can't optimize it in the same way anyway. Additionally the combination of MoveNext and Current avoid the need to construct an intermediate Step value.
So it's a good idea for Haskell, but not so much for F#. Essentially it's the same old trade-off between imperative and functional. Functional makes code easier to analyse for optimizations, imperative means you can just make the code faster yourself.
Thanks for the explaination.
Thanks for the explaination.
Topic tags
- f# × 3705
- websharper × 1897
- compiler × 286
- functional × 201
- ui next × 139
- c# × 121
- classes × 97
- web × 97
- .net × 84
- book × 84
- async × 76
- ui.next × 67
- bug × 54
- core × 49
- website × 49
- server × 45
- parallel × 43
- ui × 43
- enhancement × 41
- parsing × 41
- testing × 41
- trywebsharper × 41
- typescript × 37
- html × 35
- javascript × 35
- owin × 35
- asynchronous × 30
- monad × 28
- ocaml × 28
- tutorial × 27
- warp × 27
- haskell × 26
- sitelet × 25
- linq × 22
- workflows × 22
- wpf × 20
- fpish × 19
- introduction × 19
- silverlight × 19
- sitelets × 19
- monodevelop × 17
- rpc × 17
- suave × 17
- piglets × 16
- collections × 15
- feature request × 15
- jquery × 15
- templates × 15
- getting started × 14
- pipeline × 14
- kendoui × 13
- reactive × 12
- 4.1.0.171 × 11
- monads × 11
- opinion × 10
- 4.0.190.100-rc × 9
- deployment × 9
- fixed × 9
- formlets × 9
- in × 9
- json × 9
- plugin × 9
- proposal × 9
- scheme × 9
- solid × 9
- basics × 8
- concurrent × 8
- highcharts × 8
- how-to × 8
- python × 8
- 4.1.1.175 × 7
- complexity × 7
- documentation × 7
- visual studio × 7
- 4.1.2.178 × 6
- lisp × 6
- real-world × 6
- released in 4.0.192.103-rc × 6
- remoting × 6
- resources × 6
- scala × 6
- websharper ui.next × 6
- workshop × 6
- xaml × 6
- 4.0.193.110 × 5
- 4.2.3.236 × 5
- aspnetmvc × 5
- authentication × 5
- azure × 5
- bootstrap × 5
- conference × 5
- dsl × 5
- formlet × 5
- java × 5
- list × 5
- metaprogramming × 5
- ml × 5
- released in Zafir.4.0.188.91-beta10 × 5
- sql × 5
- visualstudio × 5
- websharper.forms × 5
- zafir × 5
- 4.0.192.106 × 4
- 4.0.195.127 × 4
- 4.1.0.38 × 4
- 4.2.1.86 × 4
- 4.2.6.118 × 4
- css × 4
- example × 4
- extensions × 4
- fsi × 4
- fsx × 4
- html5 × 4
- jqueryui × 4
- lift × 4
- reflection × 4
- remote × 4
- rest × 4
- spa × 4
- teaching × 4
- template × 4
- websocket × 4
- wontfix × 4
- 4.0.196.147 × 3
- 4.1.0.34 × 3
- 4.1.6.207 × 3
- 4.2.1.223-beta × 3
- 4.2.11.258 × 3
- 4.2.4.114 × 3
- 4.2.4.247 × 3
- 4.2.5.115 × 3
- 4.2.6.253 × 3
- 4.2.9.256 × 3
- ajax × 3
- alt.net × 3
- aml × 3
- asp.net mvc × 3
- canvas × 3
- cloudsharper × 3
- compilation × 3
- database × 3
- erlang × 3
- events × 3
- extension × 3
- file upload × 3
- forums × 3
- inline × 3
- issue × 3
- kendo × 3
- macro × 3
- mono × 3
- msbuild × 3
- mvc × 3
- pattern × 3
- piglet × 3
- released in Zafir.4.0.187.90-beta10 × 3
- svg × 3
- type provider × 3
- view × 3
- 4.1.1.64 × 2
- 4.1.5.203 × 2
- 4.1.7.232 × 2
- 4.2.10.257 × 2
- 4.2.3.111 × 2
- 4.2.5.249 × 2
- android × 2
- asp.net × 2
- beginner × 2
- blog × 2
- chart × 2
- client × 2
- client server app × 2
- clojure × 2
- computation expressions × 2
- constructor × 2
- corporate × 2
- courses × 2
- cufp × 2
- d3 × 2
- debugging × 2
- direct × 2
- discriminated union × 2
- docs × 2
- elm × 2
- endpoint × 2
- endpoints × 2
- enterprise × 2
- entity framework × 2
- event × 2
- f# interactive × 2
- fable × 2
- flowlet × 2
- formdata × 2
- forms × 2
- fsc × 2
- google maps × 2
- hosting × 2
- http × 2
- https × 2
- iis 8.0 × 2
- install × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- jquery mobile × 2
- keynote × 2
- lens × 2
- lenses × 2
- linux × 2
- listmodel × 2
- mac × 2
- numeric × 2
- oauth × 2
- obfuscation × 2
- offline × 2
- oop × 2
- osx × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- q&a × 2
- quotation × 2
- reference × 2
- released in Zafir.4.0.185.88-beta10 × 2
- rx × 2
- script × 2
- security × 2
- self host × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- tutorials × 2
- type × 2
- url × 2
- var × 2
- websharper.charting × 2
- websharper4 × 2
- websockets × 2
- wig × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- 4.0.194.126 × 1
- 4.1.3.184 × 1
- 4.1.4.189 × 1
- 4.2.0.214-beta × 1
- 4.2.12.259 × 1
- 4.2.2.231-beta × 1
- 4.2.8.255 × 1
- Canvas Sample Example × 1
- DynamicStyle Animated Style × 1
- Fixed in 4.0.190.100-rc × 1
- Released in Zafir.UI.Next.4.0.169.79-beta10 × 1
- SvgDynamicAttribute × 1
- WebComponent × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- actor × 1
- addin × 1
- agents × 1
- aggregation × 1
- agile × 1
- alter session × 1
- animation × 1
- anonymous object × 1
- apache × 1
- api × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net core × 1
- asp.net integration × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- attributes × 1
- authorization × 1
- b-tree × 1
- back button × 1
- badimageformatexception × 1
- bash script × 1
- batching × 1
- binding-vars × 1
- bistro × 1
- body × 1
- bundle × 1
- camtasia studio × 1
- cas protocol × 1
- charts × 1
- clarity × 1
- class × 1
- cli × 1
- clipboard × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- color zones × 1
- combinator × 1
- combinators × 1
- compile × 1
- compile code on server × 1
- config × 1
- confirm × 1
- content × 1
- context × 1
- context.usersession × 1
- continuation-passing style × 1
- coords × 1
- cordova × 1
- cors × 1
- coursera × 1
- cross-domain × 1
- csla × 1
- current_schema × 1
- custom content × 1
- data × 1
- data grid × 1
- datetime × 1
- debug × 1
- declarative × 1
- delete × 1
- devexpress × 1
- dhtmlx × 1
- dictionary × 1
- directattribute × 1
- disqus × 1
- distance × 1
- do binding × 1
- doc elt ui.next upgrade × 1
- docker × 1
- dojo × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- dynamic × 1
- eastern language × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- enums × 1
- error × 1
- etw × 1
- euclidean × 1
- eventhandlerlist × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- failed to translate × 1
- fake × 1
- fantomas × 1
- fear × 1
- float × 1
- form × 1
- form-data × 1
- forum × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- fsunit × 1
- function × 1
- functional style × 1
- game × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting-started × 1
- google × 1
- google.maps × 1
- grid × 1
- group × 1
- guide × 1
- hash × 1
- headers × 1
- hello world example × 1
- heroku × 1
- highchart × 1
- history × 1
- how to × 1
- html-templating × 1
- http405 × 1
- httpcontext × 1
- hubfs × 1
- i18n × 1
- ie 8 × 1
- if-doc × 1
- iis × 1
- image × 1
- images × 1
- inheritance × 1
- initialize × 1
- input × 1
- install "visual studio" × 1
- installer × 1
- int64 × 1
- interfaces × 1
- internet explorer × 1
- interop × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- iot × 1
- ipad × 1
- isomorphic × 1
- javascript optimization × 1
- javascript semanticui resources × 1
- jquery-plugin × 1
- jquery-ui × 1
- jquery-ui-datepicker × 1
- js × 1
- kendo datasource × 1
- kendochart × 1
- kendoui compiler × 1
- knockout × 1
- l10n × 1
- learning × 1
- library × 1
- libs × 1
- license × 1
- licensing × 1
- lineserieszonescfg × 1
- local setting × 1
- localization × 1
- logging × 1
- loop × 1
- macros × 1
- mailboxprocessor × 1
- mapping × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- mathjax × 1
- message × 1
- message passing × 1
- message-passing × 1
- meta × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- mix × 1
- mobile installation × 1
- mod_mono × 1
- modal × 1
- module × 1
- mouseevent × 1
- mouseposition × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- nunit × 1
- object relation mapper × 1
- object-oriented × 1
- om × 1
- onboarding × 1
- onclick × 1
- optimization × 1
- option × 1
- orm × 1
- os x × 1
- output-path × 1
- override × 1
- paper × 1
- parameter × 1
- persistence × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- post × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- privacy × 1
- private × 1
- profile × 1
- programming × 1
- project × 1
- project euler × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- proxy × 1
- ptvs × 1
- public × 1
- pure f# × 1
- purescript × 1
- qna × 1
- quant × 1
- query sitelet × 1
- question × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- ref × 1
- region × 1
- released in 4.0.190.100-rc × 1
- reporting × 1
- responsive design × 1
- rest api × 1
- rest sitelet × 1
- restful × 1
- round table × 1
- router × 1
- routing × 1
- rpc reverseproxy × 1
- runtime × 1
- sales × 1
- sample × 1
- sampleapp × 1
- scriptcs × 1
- scripting × 1
- search × 1
- self hosted × 1
- semanticui × 1
- sequence × 1
- serialisation × 1
- service × 1
- session-state × 1
- sharepoint × 1
- signals × 1
- sitelet website × 1
- sitelet.protect × 1
- sitlets × 1
- slickgrid × 1
- source code × 1
- sqlentityconnection × 1
- ssl × 1
- standards × 1
- static content × 1
- stickynotes × 1
- streamreader × 1
- stress × 1
- strong name × 1
- structures × 1
- submitbutton × 1
- subscribe × 1
- svg example html5 websharper.ui.next × 1
- sweetalert × 1
- system.datetime × 1
- system.reflection.targetinvocationexception × 1
- table storage × 1
- targets × 1
- tdd × 1
- templates ui.next × 1
- templating × 1
- text parsing × 1
- three.js × 1
- time travel × 1
- tls × 1
- tooltip × 1
- tracing × 1
- tsunamiide × 1
- turkish × 1
- twitter-bootstrap × 1
- type erasure × 1
- type inference × 1
- type providers × 1
- type-providers × 1
- typeprovider × 1
- ui next forms × 1
- ui-next × 1
- ui.next jqueryui × 1
- ui.next charting × 1
- ui.next formlets × 1
- ui.next forms × 1
- ui.next suave visualstudio × 1
- ui.next templating × 1
- unicode × 1
- unittest client × 1
- upload × 1
- usersession × 1
- validation × 1
- vb × 1
- vb.net × 1
- vector × 1
- view.map × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- vs2017 compiler zafir × 1
- vsix × 1
- web api × 1
- web-scraping × 1
- webapi × 1
- webcomponents × 1
- webforms × 1
- webgl × 1
- webrtc × 1
- webshaper × 1
- websharper async × 1
- websharper codemirror × 1
- websharper f# google × 1
- websharper forms × 1
- websharper reactive × 1
- websharper rpc × 1
- websharper sitelets routing × 1
- websharper warp × 1
- websharper-interface-generator × 1
- websharper.chartsjs × 1
- websharper.com × 1
- websharper.exe × 1
- websharper.owin × 1
- websharper.ui.next × 1
- websharper.ui.next jquery × 1
- websockets iis × 1
- why-websharper × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- www.grabbitmedia.com × 1
- xamarin × 1
- xml × 1
- yeoman × 1
- yield × 1
- zafir beta × 1
- zafir websharper4 × 1
- zarovizsga × 1
![]() |
Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy |
Built with WebSharper |
Knowing nothing about how F# handles list manipulations and consumption in comparison to Haskell. The question I would like to pose is, does F# need something like stream fusion?
[link:www.cse.unsw.edu.au]