F # code optimization or is it really that slow?

I was looking for a way to make the correct algorithmic coding using .NET with all the advantages of modern languages ​​(for example, I like strong type checking, operator overloading, lambdas, general algorithms). Usually I write my algorithms (mainly imitation of images) in C ++. Since F # as a language seems pretty interesting, I played a little, but it seems very slow. As the simplest test, I just manipulated the array a bit -> image brightness increase:

let r1 = rgbPixels |> Array.map (fun x -> x + byte(10) ) 

It, apparently, is a factor at least 8 times slower than the compared implementation in C ++, which is even worse for more complex algorithms, for example. 2D convolution. Is there a faster way or am I skipping any specific compiler settings (yes, creating a release with optimization on ...)? I am ready to pay for a good and high abstraction, but such overhead is not very pleasant (I will need to parallelize it to 8 cores to compensate :)) - at least it destroys the motivation to learn further ... My other option would be leaving my heavier algos in C ++ and the interface with manged C ++, but this is not nice, since managing a managed shell will be a pretty heavy burden.

+6
source share
2 answers

If you're worried about performance, one of the important things to keep in mind is that F # doesn’t change anything by default. This requires copying in many naive implementations of algorithms, such as those described by you.

EDIT: I have no idea why, but simple tests of the following code give lower Array.map results. Be sure to analyze any algorithm that you are trying to execute with such optimizations. However, I have very similar results between for and map .

Array.map creates a new array for the result of the operation, instead you want Array.iteri .

 rgbPixels |> Array.iteri (fun ix -> rgbPixels.[i] <- x + 10uy) 

Please note that this can be completed in your own module as shown below.

 module ArrayM = let map fa = a |> Array.iteri (fun ix -> a.[i] <- fx) 

Unfortunately, this is a necessary evil, since one of the main tenants of functional programming is to stick to immutable objects in the same way as your algorithm allows, and then somehow finish, change the mutation, where critical performance is. If you know that your performance is critical to you, you will need to start with such assistants.

Also note that there is probably a library that provides this functionality, I just don't know about it.

+6
source

I think it is safe to say that idiomatic F # often does not match the performance of optimized C ++ for manipulating arrays for several reasons:

  • Access to arrays is checked at the array boundaries in .NET to ensure memory security. The CLR JIT compiler can speed up the check of the boundaries of any stereotypical code, but this usually requires the use of a for loop with explicit restrictions, and not more than the idiomatic constructions of F #.
  • Usually for using abstractions like lambdas (e.g. fun i -> ... ) there is a small amount of overhead. In narrow loops, these small overheads can be significant compared to the work performed in the loop body.
  • To my knowledge, the JR CLR compiler does not use SSE commands to the same extent as C ++ compilers.

The other side of the book

  • You will never have a buffer overflow in F # code.
  • Your code will be easier to reason about. As a consequence of a given level of code complexity, you can often implement a more complex algorithm in F # than in C ++.
  • If necessary, you can write a uniomatic code that will work with C ++ speed, and there are also opportunities for interacting with unsafe C ++ code if you find that you need to write C ++ to meet your performance requirements.
+5
source

Source: https://habr.com/ru/post/907879/


All Articles