Are you ensuring that you're benchmarking equivalent cases? For favorable partitions, the F# version is also O(n log n). Here's my reasoning, where T(n) is the time to calculate qsort over a list of size n where the two partitions are of equal size. Then the partition function itself is O(n), as is the append, so that T(n) = 2T(n/2) + O(n), which by the Master Theorem means that T(n) = O(n log n).

To test this, define the following function:

Unfortunately, for less fortunate arrangements of numbers, the complexity can be much worse. For instance, when sorting

To test this, define the following function:

let rec lst = function | 0 -> [] | n -> let l = lst (n-1) let k = pown 2 (n - 1) k :: l @ List.map (fun x -> x + k) lThen

`lst n`

is a list of 2^n - 1 numbers where the first number is the median, and the first number of each partition is also the median of that partition, etc., so that partitions are always of equal size. On my computer, `qsort (lst 18)`

takes about 1.8 seconds whereas `qsort (lst 19)`

takes about 3.7 seconds, which is consistent with a complexity of O(n log n) and inconsistent with a complexity of O(n^2).Unfortunately, for less fortunate arrangements of numbers, the complexity can be much worse. For instance, when sorting

`[1 .. 100000]`

or `List.rev [1 .. 100000]`

, the complexity is at least O(n^2) and you'll see very poor performance. I think that for random lists, the result should still be O(n log n), which can be tested by something like:let r = System.Random() in qsort [for i in 1 .. 100000 -> r.Next()]This runs in ~0.8 seconds on my computer, and I think that the Haskell benchmark you're citing is also run on a random list. I don't think that laziness will prevent Haskell's performance from degrading when given a pessimally ordered list, either.

Hi kvb & brianmcm,

Thank you guys for the quick responses.

@kvb: You are right: I tested with [1..10000] which is definitely one of the input-cases that cause very bad performance (maybe only reversed sorted inputs show even worse performance?).

To do some direct comparison of Haskell and F# I will install GHC later and run some tests with the same inputs. When I saw the Haskell programm running on the machine of a friend of mine the output of the sorting-result in Haskell started quickly after the program was started but the speed of the output seemed to be dependent on the size of the list to sort. I am not sure whether this is linked to the overall laziness of Haskell which might be able to determine when the next element for output has been determined.

I have not programmed in Haskell yet, I just noticed that all IO happens in terms of monads. I am not sure whether it is correct to think of the execution process of IO operations in the bind operation of the IO monad...? (sorry to ask this on a a F# forum)

Andreas

Thank you guys for the quick responses.

@kvb: You are right: I tested with [1..10000] which is definitely one of the input-cases that cause very bad performance (maybe only reversed sorted inputs show even worse performance?).

To do some direct comparison of Haskell and F# I will install GHC later and run some tests with the same inputs. When I saw the Haskell programm running on the machine of a friend of mine the output of the sorting-result in Haskell started quickly after the program was started but the speed of the output seemed to be dependent on the size of the list to sort. I am not sure whether this is linked to the overall laziness of Haskell which might be able to determine when the next element for output has been determined.

I have not programmed in Haskell yet, I just noticed that all IO happens in terms of monads. I am not sure whether it is correct to think of the execution process of IO operations in the bind operation of the IO monad...? (sorry to ask this on a a F# forum)

Andreas

Here is the result of the quick comparision that I did (sorting an already sorted list with the naive qsort that I posted above):

So in this case it looks like that the code generated by the GHC is approximately by a constant factor of 10-14 faster than the code generated by F#. Native code execution vs. jitted CLR code might of course be a small advantage for Haskell. What it clearly shows is that the overall runtime complexity of the naive list qsort in F# is the same as the Haskell version. So sorry for bothering you guys .. I should have done these comparision-test before posting my initial post.

Andreas

PS: I used the following haskell source:

To compile I used "ghc -c -O qsort.hs"

Haskell: 10000: 1.01 secs 20000: 4.48 secs 30000: 11.84 secs 40000: 27.38 secs 50000: 59.05 secs F#: 10000: 14.086 secs 20000: 65.036 secs 30000: 157.997 secs 40000: 302.095 secs 50000: "Process is terminated due to StackOverflowException." Execution time of F# relative to Haskell: 14.1 / 1.0 = ~14.1 times 65.0 / 4.5 = ~14.4 times 158.0 / 11.8 = ~13.4 times 302 / 27.9 = ~10.8 times

So in this case it looks like that the code generated by the GHC is approximately by a constant factor of 10-14 faster than the code generated by F#. Native code execution vs. jitted CLR code might of course be a small advantage for Haskell. What it clearly shows is that the overall runtime complexity of the naive list qsort in F# is the same as the Haskell version. So sorry for bothering you guys .. I should have done these comparision-test before posting my initial post.

Andreas

PS: I used the following haskell source:

import System.Environment (getArgs) qsort :: Ord a => [a] -> [a] qsort [] = [] qsort (x:xs) = qsort ys ++ x : qsort zs where (ys, zs) = partition (< x) xs main :: IO () main = do (x:xs) <- getArgs let count = read x :: Int print (last (qsort [1..count]))

To compile I used "ghc -c -O qsort.hs"

Actually the real bottleneck appears to be the use of "generic comparison" in the partition. Simply put a type constraint on qsort and the performance nearly matches Haskell on 20000 elements (11.9 seconds vs 11.2 seconds on my machine). That is, write

let rec qsort (l:int list) = ...

Actually the real bottleneck appears to be the use of "generic comparison" in the partition. Simply put a type constraint on qsort and the performance nearly matches Haskell on 20000 elements (11.9 seconds vs 11.2 seconds on my machine). That is, write let rec qsort (l:int list) = ...

Interesting. What would happen if we give Haskell some type hint to use unboxed element (is it !Int or something like that) ?

Just have to point out that it's one thing to say you know about List.sort, but another to not mention it in the supposed benchmarks. The code below runs in 17ms on my box, which is about 3500 times faster than your Haskell example.

let l = [1..50000] let sw = System.Diagnostics.Stopwatch.StartNew() let r = List.sort l sw.Stop() printfn "%A" sw.ElapsedMilliseconds

Yes, the built-in List.sort functions of both Haskell and F# are many times more efficient than the naive list qsort that I used for the benchmark...

This thread was somewhat disconcerting.

What options do you have if you have elegantly and succinctly represented the problem in F#, but you find that it does not perform and you have nothing to tweak. Do you have to abandon it?

Quicksort is a classic poster boy for functional languages. When you have to implement something like that, I would expect F# to be the go-to language.

What options do you have if you have elegantly and succinctly represented the problem in F#, but you find that it does not perform and you have nothing to tweak. Do you have to abandon it?

Quicksort is a classic poster boy for functional languages. When you have to implement something like that, I would expect F# to be the go-to language.

What options do you have if you have elegantly and succinctly represented the problem in F#, but you find that it does not perform and you have nothing to tweak. Do you have to abandon it?

Having a background that includes both embedded systems programming and optimization in high-level languages, I've had to deal with this problem in every language from C onwards.

My general approach is this:

1) Decide what's most important for this particular block of code. Is it performance, maintainability, implementing a specific algorithm?

2) If it's performance, and the algorithm isn't making the grade, is there an equally elegant algorithm that does?

3) If there's not an elegant algorithm, and performance is paramount, there's simply no alternative to the ugly code block. Think of it as the pumbling behind the walls that has to be there sometimes even in an elegant hotel, lol. (And sometimes I will leave a comment saying: "I would have done it this way had it been fast enough...")

So I wince when I have to pick option 3, but I remember what Robert Browning said:

"Ah, but a man's reach should exceed his grasp, or what's a heaven for?"

And it keeps me from feeling bad about the fact that I've had to write something ugly in an otherwise elegant language.

-Neil

While we are at it(about performance), this read is quite interestingThat's good food for thought.

[link:labs.oracle.com]

What is disconcerting is the fact that anyone gives any pragmatic credence to a 'functional quicksort'. If you want to sort, use an array, use a built-in sort method, and it will be much much much faster than anything pure/lazy in Haskell. When has anyone in the history of the world ever needed a lazy functional sort (other than to demo Haskell elegance)?

If you find something pragmatic that addresses a real world problem, that is solved more elegantly and performantly in Haskell, then post that. This whole thread (and title) is ridiculous. Haskell has to be very very good at immutable lazy programming, because that's pretty much all you can do in Haskell. In F# we have mutability, which when used in locally-scoped small doses, is likely to outperform Haskell without sacrificing elegance or referential transparency. This whole thread smells to me like philosphers debating angels dancing on pinheads, rather that real-world programmers debating run-time performance.

If you find something pragmatic that addresses a real world problem, that is solved more elegantly and performantly in Haskell, then post that. This whole thread (and title) is ridiculous. Haskell has to be very very good at immutable lazy programming, because that's pretty much all you can do in Haskell. In F# we have mutability, which when used in locally-scoped small doses, is likely to outperform Haskell without sacrificing elegance or referential transparency. This whole thread smells to me like philosphers debating angels dancing on pinheads, rather that real-world programmers debating run-time performance.

Yes, the built-in List.sort functions of both Haskell and F# are many times more efficient than the naive list qsort that I used for the benchmark...

List.sort I believe is using mutable data structure(or even calling some underlying .NET thing, similar to Python/Perl where it is implemented in C) whereas Haskell is still implemented in Haskell and not pulling in other support.

So from a 'language' perspective, Haskell is still much more efficient when programming in immutable style. F# allows immutable style with a performance penalty.

Yes, this is an enormously poor algorithm in a strict language with immutable lists; I think the perf may be worse than O(N^2).

In Haskell, lists are lazy, and this affects the big-oh of the 'append' operation '++', I think it can be O(1) by just saving the 'lazy thunk to compute the rest of the list'. (You could do the same thing in F# using the LazyList library in the F# PowerPack.)

Briefly, this boils down to 'why is this algorithm faster than that one' and the answer is 'they have different Big-oh'. Haskell, being lazy, has completely difference performance characteristics for 'syntactically similar' algorithms coded in a strict language.

In Haskell, lists are lazy, and this affects the big-oh of the 'append' operation '++', I think it can be O(1) by just saving the 'lazy thunk to compute the rest of the list'. (You could do the same thing in F# using the LazyList library in the F# PowerPack.)

Briefly, this boils down to 'why is this algorithm faster than that one' and the answer is 'they have different Big-oh'. Haskell, being lazy, has completely difference performance characteristics for 'syntactically similar' algorithms coded in a strict language.

# Topic tags

- f# × 3676
- compiler × 264
- functional × 201
- websharper × 147
- c# × 119
- classes × 96
- web × 95
- book × 84
- .net × 82
- async × 72
- parallel × 43
- server × 43
- parsing × 41
- testing × 41
- asynchronous × 30
- monad × 28
- ocaml × 28
- haskell × 26
- tutorial × 26
- html × 23
- workflows × 22
- linq × 21
- fpish × 19
- introduction × 19
- silverlight × 19
- wpf × 19
- collections × 14
- pipeline × 14
- templates × 12
- monads × 11
- opinion × 10
- reactive × 10
- plugin × 9
- scheme × 9
- sitelets × 9
- solid × 9
- basics × 8
- concurrent × 8
- deployment × 8
- how-to × 8
- javascript × 8
- python × 8
- complexity × 7
- jquery × 7
- lisp × 6
- real-world × 6
- scala × 6
- workshop × 6
- xaml × 6
- conference × 5
- dsl × 5
- java × 5
- metaprogramming × 5
- ml × 5
- piglets × 5
- visual studio × 5
- formlets × 4
- fsi × 4
- lift × 4
- sql × 4
- teaching × 4
- alt.net × 3
- aml × 3
- enhancement × 3
- erlang × 3
- highcharts × 3
- list × 3
- piglet × 3
- reflection × 3
- sitelet × 3
- type provider × 3
- blog × 2
- clojure × 2
- compilation × 2
- computation expressions × 2
- corporate × 2
- courses × 2
- cufp × 2
- enterprise × 2
- entity framework × 2
- events × 2
- f# interactive × 2
- fsc × 2
- google maps × 2
- html5 × 2
- http × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- kendo × 2
- keynote × 2
- mvc × 2
- numeric × 2
- obfuscation × 2
- oop × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- rx × 2
- script × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- type × 2
- typescript × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- addin × 1
- agents × 1
- agile × 1
- alter session × 1
- android × 1
- anonymous object × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net integration × 1
- asp.net mvc × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- b-tree × 1
- badimageformatexception × 1
- batching × 1
- bistro × 1
- bootstrap × 1
- bug × 1
- bundle × 1
- camtasia studio × 1
- canvas × 1
- class × 1
- client × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- combinator × 1
- confirm × 1
- constructor × 1
- continuation-passing style × 1
- coords × 1
- coursera × 1
- csla × 1
- css × 1
- current_schema × 1
- custom content × 1
- data × 1
- database × 1
- declarative × 1
- delete × 1
- dhtmlx × 1
- discriminated union × 1
- distance × 1
- docs × 1
- documentation × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- error × 1
- etw × 1
- euclidean × 1
- event × 1
- example × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- fantomas × 1
- fear × 1
- float × 1
- flowlet × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- function × 1
- functional style × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting started × 1
- google × 1
- group × 1
- hash × 1
- hello world example × 1
- history × 1
- hosting × 1
- httpcontext × 1
- https × 1
- hubfs × 1
- ie 8 × 1
- if-doc × 1
- inheritance × 1
- installer × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- ipad × 1
- issue × 1
- kendochart × 1
- kendoui × 1
- learning × 1
- license × 1
- licensing × 1
- macro × 1
- macros × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- message × 1
- message passing × 1
- message-passing × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- module × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- object relation mapper × 1
- object-oriented × 1
- offline × 1
- om × 1
- option × 1
- orm × 1
- osx × 1
- owin × 1
- paper × 1
- parameter × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- programming × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- ptvs × 1
- quant × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- reference × 1
- remoting × 1
- rest × 1
- restful × 1
- round table × 1
- routing × 1
- runtime × 1
- scriptcs × 1
- scripting × 1
- service × 1
- session-state × 1
- sitelet website × 1
- sitlets × 1
- sqlentityconnection × 1
- standards × 1
- stickynotes × 1
- stress × 1
- strong name × 1
- structures × 1
- svg × 1
- tdd × 1
- template × 1
- text parsing × 1
- time travel × 1
- tracing × 1
- tsunamiide × 1
- type inference × 1
- type providers × 1
- typeprovider × 1
- upload × 1
- vb × 1
- vb.net × 1
- vector × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- visualstudio × 1
- web api × 1
- webapi × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- xml × 1
- yield × 1
- zarovizsga × 1

Copyright (c) 2011-2012 IntelliFactory. All rights reserved. Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us |
Built with WebSharper |

just for fun I implemented one of the famous Haskell qsort language introduction samples in F#. I was surprised how much slower F# performed compared to Haskell.

A naive qsort on a list in Haskell looks like this:

A nice article about the performance of such a naive implementation can be found here. Basically the article says that sorting 100,000 ints takes less than 3 seconds (on a 1.5GHz Apple Powerbook with 1GB of RAM).

Here is an implementation fo a similar naive qsort in F#:

When I first tried to sort 100,000 elements this way in the F# interactive console I thought that it got stuck because it did not respond for a very long time. After trying a few times I realized that it simply runs extremely slow compared to the Haskell code, e.g. sorting 10,000

`int`

s took 15 seconds, 20,000 took 67 seconds (4,6x), 30,000 took 165 seconds (11x) ... so sorting 100,000 elements this way was completely impractical. Using lists qsort no longer follows the O(n log n) performance expectation but seems to become nearly O(n^2).I guess the list concatenation dominates the hole algorithm in F# since it requires the copying of the immutable list that is prepended to generate a new element that links to the appended list... But I wonder how the Haskell GHC compiler can eliminate these expensive operations? Has anybody an idea how lists are represented in Haskell in memory and why the code is so much faster with Haskell than F#?

Please understand that I do not want to criticize F#. I am pretty new to functional programming...

Andreas

PS: Of course I know that

`List.sort`

exists...