From ba91a9a4d40db3a7383cbfde29f2753c3bfffad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Sun, 6 May 2018 16:09:36 +0200 Subject: [PATCH 1/9] Init from MarekSuchanek/FPCourse --- tutorials/11_performance-debug.md | 82 +++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tutorials/11_performance-debug.md diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md new file mode 100644 index 0000000..78134a0 --- /dev/null +++ b/tutorials/11_performance-debug.md @@ -0,0 +1,82 @@ +# Performance and Debugging + +## Performance + +### Measuring time and memory + +### Basic ideas + +### Boxed and unboxed types + +### Strictness + +### GHC optimization flags + +### Other optimization techniques + +### Paralelism + +## Debugging + +You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. + +For example: + +```haskell +func a b = trace ("func " ++ show a ++ " " ++ show b) undefined +``` + +If you need more than just that, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: + +* setting breakpoints and stepping, +* inspecting variables, +* tracing, +* working with exceptions, +* and so on. + +## Concurrency and optimizations + +Just like with GCC, you can use optimization flags with GHC. You can also drill deeper in your source code and optimize it by hand, use FFI, parallelism or concurrency, and so on in order to achieve faster computation. Good resource for that is [wiki.haskell.org/Performance](https://wiki.haskell.org/Performance) where you can look up hints for specific parts of you app and/or your compiler. + +For parallelism and concurrency visit [wiki.haskell.org/Parallel](https://wiki.haskell.org/Parallel). You can both: + +* run parallel threads with Control.Parallel, +* run simultaneous IO actions with forks. + +It is also possible to do distributed computations on clusters but it is far beyond the scope of this course. + +```haskell +--TODO: Control.Parallel simple example +``` + +## Benchmarking with Criterion + +If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. + +For simple usage, you just need to work with the `defaultMain` from [Criterion.Main](https://hackage.haskell.org/package/criterion/docs/Criterion-Main.html) as they show in their example: + +```haskell +import Criterion.Main + +fib m | m < 0 = error "negative!" + | otherwise = go m + where go 0 = 0 + go 1 = 1 + go n = go (n-1) + go (n-2) + +main = defaultMain [ + bgroup "fib" [ bench "1" $ whnf fib 1 + , bench "5" $ whnf fib 5 + , bench "11" $ whnf fib 11 + ] + ] +``` +It has very nice outputs with form of interactive HTML pages with charts and comparisons and have many options to use. + +## Further reading + +* [Haskell - Debugging](https://wiki.haskell.org/Debugging) +* [Haskell - Performance](https://wiki.haskell.org/Performance) +* [Haskell - Concurrency](https://wiki.haskell.org/Concurrency) +* [GHC - Concurrent and Parallel Haskell](https://downloads.haskell.org/~ghc/7.0.3/docs/html/users_guide/lang-parallel.html) + From 1d017aae9ce87e56d8f3a16c740e5dbdef505797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Mon, 7 May 2018 13:34:41 +0200 Subject: [PATCH 2/9] Improved order in outline --- tutorials/11_performance-debug.md | 76 +++++++++++++++---------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index 78134a0..dd8bcc3 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -1,40 +1,44 @@ # Performance and Debugging -## Performance - -### Measuring time and memory +## Measuring time and memory -### Basic ideas +### Benchmarking with Criterion -### Boxed and unboxed types +If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. -### Strictness +For simple usage, you just need to work with the `defaultMain` from [Criterion.Main](https://hackage.haskell.org/package/criterion/docs/Criterion-Main.html) as they show in their example: -### GHC optimization flags +```haskell +import Criterion.Main -### Other optimization techniques +fib m | m < 0 = error "negative!" + | otherwise = go m + where go 0 = 0 + go 1 = 1 + go n = go (n-1) + go (n-2) -### Paralelism +main = defaultMain [ + bgroup "fib" [ bench "1" $ whnf fib 1 + , bench "5" $ whnf fib 5 + , bench "11" $ whnf fib 11 + ] + ] +``` +It has very nice outputs with form of interactive HTML pages with charts and comparisons and have many options to use. -## Debugging +## Performance -You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. +### Basic ideas -For example: +### Boxed and unboxed types -```haskell -func a b = trace ("func " ++ show a ++ " " ++ show b) undefined -``` +### Strictness -If you need more than just that, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: +### GHC optimization flags -* setting breakpoints and stepping, -* inspecting variables, -* tracing, -* working with exceptions, -* and so on. +### Other optimization techniques -## Concurrency and optimizations +### Concurrency and optimizations Just like with GCC, you can use optimization flags with GHC. You can also drill deeper in your source code and optimize it by hand, use FFI, parallelism or concurrency, and so on in order to achieve faster computation. Good resource for that is [wiki.haskell.org/Performance](https://wiki.haskell.org/Performance) where you can look up hints for specific parts of you app and/or your compiler. @@ -49,29 +53,23 @@ It is also possible to do distributed computations on clusters but it is far bey --TODO: Control.Parallel simple example ``` -## Benchmarking with Criterion +## Debugging -If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. +You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. -For simple usage, you just need to work with the `defaultMain` from [Criterion.Main](https://hackage.haskell.org/package/criterion/docs/Criterion-Main.html) as they show in their example: +For example: ```haskell -import Criterion.Main +func a b = trace ("func " ++ show a ++ " " ++ show b) undefined +``` -fib m | m < 0 = error "negative!" - | otherwise = go m - where go 0 = 0 - go 1 = 1 - go n = go (n-1) + go (n-2) +If you need more than just that, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: -main = defaultMain [ - bgroup "fib" [ bench "1" $ whnf fib 1 - , bench "5" $ whnf fib 5 - , bench "11" $ whnf fib 11 - ] - ] -``` -It has very nice outputs with form of interactive HTML pages with charts and comparisons and have many options to use. +* setting breakpoints and stepping, +* inspecting variables, +* tracing, +* working with exceptions, +* and so on. ## Further reading From 4e076101cff106f3f8fa9f1e58419bc3f8dc58b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Mon, 7 May 2018 18:49:40 +0200 Subject: [PATCH 3/9] Basic stuff and from bonus types --- tutorials/11_performance-debug.md | 69 ++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index dd8bcc3..776d01b 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -1,7 +1,13 @@ # Performance and Debugging +During this tutorial we will take a look how to improve performance of Haskell program and how to debug it. We will use very simple example everywhere - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). + ## Measuring time and memory +When you want to check performance of program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. + +### Basic `time` + ### Benchmarking with Criterion If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. @@ -26,16 +32,75 @@ main = defaultMain [ ``` It has very nice outputs with form of interactive HTML pages with charts and comparisons and have many options to use. +### Measure allocations with Weigh + + ## Performance +Now we are able to measure something and compare algorithms, but how to improve the numbers we get if we really need it? + ### Basic ideas -### Boxed and unboxed types +When you are not satisfied with the perfomance of your application, then before any sophisticated optimization steps by using strictness, unboxed types, calling FFI, etc., you should consider if you prefer faster application over better readability. Then another important thing to think about is design, if it is not slow by using "naive" algorithm, using inappropriate data structure (List instead of Set or Map), etc. + +**Always** rethink your own code before using other optimization techniques! + +### Boxed vs. Unboxed types + +Now, we are going to briefly mention is the difference between boxed and unboxed types. Although it is a low-level concern and with regular Haskell programming, you can avoid these terms, it is good to know what is it about when you see it in other's code or in a documentation. + +To support laziness, parametric polymorphism, and other properties, by default Haskell data types are represented uniformly as a pointer to a closure on the heap. These are "boxed" values. An unboxed is represented directly by raw value (i.e., without any indirection). Using unboxed types can lead to time/space optimizations. Having always pointers to a heap-allocated object is fairly slow, so compilers attempt to replace these boxed values with unboxed raw values when possible. Unboxed values are a feature of some compilers that allow directly manipulating these low-level values. Since they behave differently than normal Haskell types, generally the type system is extended to type these unboxed values. + +In [GHC], unboxed values have a hash mark as a suffix to their name. For instance, the unboxed representation of 42 is 42#. However, you can't pass them to polymorphic functions (like `show` for instance). To allow that, you need to use constructor `I#` that takes an unboxed integer and returns the `Int` (wraps). You can observe [kind](https://wiki.haskell.org/Kind) (*kind of type*, we will look again at kinds with typeclasses) of boxed and unboxed types: + +* By default, kind of type is `*` (try in GHCi: `:kind Int`) +* Kind of unboxed type is `#` (try in GHCi: `:kind Int#`, first do `:set -fglasgow-exts`) + +```haskell +import GHC.Exts + +showUnboxedInt :: Int# -> String +showUnboxedInt n = "Unboxed: " ++ (show $ I# n) ++ "#" +``` + +(If you find kinds interesting, try to examine `:kind Maybe` and `:kind Either`.) -### Strictness +### Strictness with types + +In the previous lessons, we touched the topic of enforcing strictness with `!` in patterns ([bang patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html)) and in function application with `$!` operator. Similarly, we can use `!` with type fields like this: + +```haskell +data MyType = MyConstr Int !Int + +data MyRec = MyRecConstr { xA :: Int + , xB :: !Int + } +``` + +For both cases it means that when data constructor is evaluated, it must fully evaluate ([weak head normal form](https://wiki.haskell.org/Weak_head_normal_form)) the second parameter, but the first one will stay unevaluated in a lazy way. All depends on language implementation in the used compiler. + +#### Unpacking strict fields + +One of the most used optimization techniques when talking about unboxed types and strictness with [GHC] is [unpacking strict fields](https://wiki.haskell.org/Performance/Data_types#Unpacking_strict_fields). When a constructor field is marked strict, and it is a single-constructor type, then it is possible to ask GHC to unpack the contents of the field directly in its parent with `{-# UNPACK #-}` pragma: + +```haskell +data T1 = T1 {-# UNPACK #-} !(Int, Float) -- => T1 Int Float +data T2 = T2 Double {-# UNPACK #-} !Int -- => T2 Double Int# +``` + +We mention this just because of differences in performance of types we are going to describe now. You don't need to use strict or unboxed types within your work if you don't need to have time/space optimizations... ### GHC optimization flags +If you know optimization with GCC, then you won't be surprised how it works with GHC: + +* `-O0` = turn off all optimisation +* `-O` or `-O1` = generate good-quality code without taking too long about it +* `-O2` = apply every non-dangerous optimisation, even if it means significantly longer compile times (in most cases, there is no significant difference between `-O1` and `-O2`) + +Then there are also `-f*` platform-independent flags, that allows you turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). + + ### Other optimization techniques ### Concurrency and optimizations From 68f1b07fe2e9aab4493ed26d376ac413329e5c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Tue, 8 May 2018 10:43:43 +0200 Subject: [PATCH 4/9] Time and outline improvements --- tutorials/11_performance-debug.md | 39 +++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index 776d01b..260a900 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -2,12 +2,36 @@ During this tutorial we will take a look how to improve performance of Haskell program and how to debug it. We will use very simple example everywhere - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). +```haskell +module Main where + +-- | Naive recursive algorithm for n-th Fibonacci number +fibonacci :: Integer -> Integer +fibonacci 0 = 0 +fibonacci 1 = 1 +fibonacci n = fibonacci (n-1) + fibonacci (n-2) + +main = do + args <- getArgs + show . fibonacci . read . head $ args +``` + ## Measuring time and memory When you want to check performance of program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. ### Basic `time` +The `time` command is one of the well-known Linux commands for programmers. It can be used to show how long a command takes to run. That makes it Very useful if you are a developer and you want to test the performance of your program or script. Especially to compare time of programs written in other languages "from outside". For basic usage, you will get three numbers: + +- `real` = total time is taken to run the command (the same as if you use your normal stopwatch) +- `user` = amount of time that was spent in user mode +- `sys` = amount of time spent in kernel mode + +Then `user`+`sys` gives information how much actual CPU time your process used - in total on all cores. This number can be then higher than `real` if your program uses multiple threads. + +But `time` can do a bit more, you can tell how output should look like with additional "numbers" - number of page faults, average total memory use of the process in kilobytes, number of signals delivered to the process, number of socket messages received/send by the process, exit status of the command, and many others. + ### Benchmarking with Criterion If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. @@ -34,6 +58,7 @@ It has very nice outputs with form of interactive HTML pages with charts and com ### Measure allocations with Weigh +The package [weigh](https://hackage.haskell.org/package/weigh) provides simple interface to mesure the memory usage of a Haskell value or function. ## Performance @@ -63,8 +88,6 @@ showUnboxedInt :: Int# -> String showUnboxedInt n = "Unboxed: " ++ (show $ I# n) ++ "#" ``` -(If you find kinds interesting, try to examine `:kind Maybe` and `:kind Either`.) - ### Strictness with types In the previous lessons, we touched the topic of enforcing strictness with `!` in patterns ([bang patterns](https://ocharles.org.uk/blog/posts/2014-12-05-bang-patterns.html)) and in function application with `$!` operator. Similarly, we can use `!` with type fields like this: @@ -101,9 +124,7 @@ If you know optimization with GCC, then you won't be surprised how it works with Then there are also `-f*` platform-independent flags, that allows you turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). -### Other optimization techniques - -### Concurrency and optimizations +### Concurrency Just like with GCC, you can use optimization flags with GHC. You can also drill deeper in your source code and optimize it by hand, use FFI, parallelism or concurrency, and so on in order to achieve faster computation. Good resource for that is [wiki.haskell.org/Performance](https://wiki.haskell.org/Performance) where you can look up hints for specific parts of you app and/or your compiler. @@ -120,6 +141,10 @@ It is also possible to do distributed computations on clusters but it is far bey ## Debugging +Even if you are a good Haskell programmer, things can go wrong and especially in big projects it is a nontrivial challenge to find out where you did some mistake. Going thru the code in multiple functions, inner functions, various modules, etc. can be painful. Luckilly there are some ways how to debug Haskell program and some are pretty easy and similar to well-known. + +### Tracing with `Debug.Trace` + You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. For example: @@ -136,6 +161,10 @@ If you need more than just that, you can use [GHCi debugger](https://downloads.h * working with exceptions, * and so on. +### `debug` package + +### GHCi debugger + ## Further reading * [Haskell - Debugging](https://wiki.haskell.org/Debugging) From c758049a75d2978c8859b99d0761fef8022141e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Tue, 8 May 2018 12:19:28 +0200 Subject: [PATCH 5/9] Measuring time and memory --- tutorials/11_performance-debug.md | 148 ++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 10 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index 260a900..d19c5ac 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -3,9 +3,9 @@ During this tutorial we will take a look how to improve performance of Haskell program and how to debug it. We will use very simple example everywhere - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). ```haskell -module Main where +import System.Environment --- | Naive recursive algorithm for n-th Fibonacci number +-- | Naive recursive algorithm for n-th Fibonacci number fibonacci :: Integer -> Integer fibonacci 0 = 0 fibonacci 1 = 1 @@ -30,8 +30,24 @@ The `time` command is one of the well-known Linux commands for programmers. It c Then `user`+`sys` gives information how much actual CPU time your process used - in total on all cores. This number can be then higher than `real` if your program uses multiple threads. +``` +% /usr/bin/time -p runhaskell FibonacciNaive.hs 25 +75025 +real 0.33 +user 0.31 +sys 0.01 +``` + But `time` can do a bit more, you can tell how output should look like with additional "numbers" - number of page faults, average total memory use of the process in kilobytes, number of signals delivered to the process, number of socket messages received/send by the process, exit status of the command, and many others. +``` +% /usr/bin/time -f "Elapsed Time: %E\nExit Status: %X\nPage Faults: %F" runhaskell FibonacciNaive.hs 25 +75025 +Elapsed Time: 0:00.34 +Exit Status: 0 +Page Faults: 0 +``` + ### Benchmarking with Criterion If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. @@ -41,25 +57,77 @@ For simple usage, you just need to work with the `defaultMain` from [Criterion.M ```haskell import Criterion.Main -fib m | m < 0 = error "negative!" - | otherwise = go m - where go 0 = 0 - go 1 = 1 - go n = go (n-1) + go (n-2) +-- | Naive recursive algorithm for n-th Fibonacci number +fibonacci :: Integer -> Integer +fibonacci 0 = 0 +fibonacci 1 = 1 +fibonacci n = fibonacci (n-1) + fibonacci (n-2) main = defaultMain [ - bgroup "fib" [ bench "1" $ whnf fib 1 - , bench "5" $ whnf fib 5 - , bench "11" $ whnf fib 11 + bgroup "fib" [ bench " 5" $ whnf fibonacci 5 + , bench "10" $ whnf fibonacci 10 + , bench "25" $ whnf fibonacci 25 ] ] ``` + It has very nice outputs with form of interactive HTML pages with charts and comparisons and have many options to use. +``` +% runhaskell FibonacciNaiveCriterion.hs +benchmarking fib/ 5 +time 7.319 μs (6.980 μs .. 7.821 μs) + 0.966 R² (0.934 R² .. 0.995 R²) +mean 7.248 μs (6.966 μs .. 7.847 μs) +std dev 1.321 μs (805.8 ns .. 2.043 μs) +variance introduced by outliers: 96% (severely inflated) + +benchmarking fib/10 +time 81.34 μs (81.15 μs .. 81.54 μs) + 1.000 R² (1.000 R² .. 1.000 R²) +mean 81.58 μs (81.38 μs .. 81.85 μs) +std dev 811.3 ns (577.5 ns .. 1.191 μs) + +benchmarking fib/25 +time 111.6 ms (110.5 ms .. 112.2 ms) + 1.000 R² (1.000 R² .. 1.000 R²) +mean 112.1 ms (111.7 ms .. 112.9 ms) +std dev 853.6 μs (534.0 μs .. 1.215 ms) +variance introduced by outliers: 11% (moderately inflated) + +runhaskell FibonacciNaiveCriterion.hs 15.98s user 0.04s system 99% cpu 16.055 total +``` + ### Measure allocations with Weigh The package [weigh](https://hackage.haskell.org/package/weigh) provides simple interface to mesure the memory usage of a Haskell value or function. +```haskell +import Weigh + +-- | Naive recursive algorithm for n-th Fibonacci number +fibonacci :: Integer -> Integer +fibonacci 0 = 0 +fibonacci 1 = 1 +fibonacci n = fibonacci (n-1) + fibonacci (n-2) + +main = mainWith $ do + func "fib 5" fibonacci 5 + func "fib 10" fibonacci 10 + func "fib 25" fibonacci 25 +``` + +It provides nice output as plain text table, but it is also possible to change format to markdown. + +``` +% ./FibonacciNaiveWeigh + +Case Allocated GCs +fib 5 1,968 0 +fib 10 24,304 0 +fib 25 33,509,936 63 +``` + ## Performance Now we are able to measure something and compare algorithms, but how to improve the numbers we get if we really need it? @@ -70,6 +138,66 @@ When you are not satisfied with the perfomance of your application, then before **Always** rethink your own code before using other optimization techniques! +```haskell +import System.Environment + +-- | Improved recursive algorithm for n-th Fibonacci number +fibonacci :: Integer -> Integer +fibonacci n = fib 0 1 n + where + fib x _ 0 = x + fib x y rem = fib y (x+y) (rem-1) -- just "one-way" recursion! + +main = do + args <- getArgs + print . fibonacci . read . head $ args +``` + +Just a very simple re-thinking can have some impact: + +``` +% /usr/bin/time -p runhaskell FibonacciBetter.hs 25 +75025 +real 0.24 +user 0.22 +sys 0.02 +``` + +``` +% runhaskell FibonacciBetterCriterion.hs +benchmarking fib/ 5 +time 3.412 μs (3.235 μs .. 3.591 μs) + 0.988 R² (0.983 R² .. 0.998 R²) +mean 3.191 μs (3.129 μs .. 3.277 μs) +std dev 253.6 ns (168.4 ns .. 360.9 ns) +variance introduced by outliers: 82% (severely inflated) + +benchmarking fib/10 +time 5.930 μs (5.871 μs .. 6.013 μs) + 0.997 R² (0.994 R² .. 0.998 R²) +mean 6.209 μs (6.075 μs .. 6.464 μs) +std dev 625.6 ns (377.2 ns .. 1.088 μs) +variance introduced by outliers: 87% (severely inflated) + +benchmarking fib/25 +time 14.53 μs (14.31 μs .. 14.90 μs) + 0.990 R² (0.972 R² .. 0.999 R²) +mean 14.78 μs (14.40 μs .. 15.89 μs) +std dev 1.953 μs (712.6 ns .. 4.110 μs) +variance introduced by outliers: 91% (severely inflated) + +runhaskell FibonacciBetterCriterion.hs 15.90s user 0.07s system 100% cpu 15.954 total +``` + +``` +% ./FibonacciBetterWeigh + +Case Allocated GCs +fib 5 872 0 +fib 10 1,712 0 +fib 25 37,000 0 +``` + ### Boxed vs. Unboxed types Now, we are going to briefly mention is the difference between boxed and unboxed types. Although it is a low-level concern and with regular Haskell programming, you can avoid these terms, it is good to know what is it about when you see it in other's code or in a documentation. From 7d5a6f927cce0b184c08eac046667426d99002ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Tue, 8 May 2018 18:19:59 +0200 Subject: [PATCH 6/9] Parallel and FFI --- tutorials/11_performance-debug.md | 170 +++++++++++++++++++++++++++--- 1 file changed, 158 insertions(+), 12 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index d19c5ac..fd18428 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -11,6 +11,7 @@ fibonacci 0 = 0 fibonacci 1 = 1 fibonacci n = fibonacci (n-1) + fibonacci (n-2) +main :: IO () main = do args <- getArgs show . fibonacci . read . head $ args @@ -63,6 +64,7 @@ fibonacci 0 = 0 fibonacci 1 = 1 fibonacci n = fibonacci (n-1) + fibonacci (n-2) +main :: IO () main = defaultMain [ bgroup "fib" [ bench " 5" $ whnf fibonacci 5 , bench "10" $ whnf fibonacci 10 @@ -111,6 +113,7 @@ fibonacci 0 = 0 fibonacci 1 = 1 fibonacci n = fibonacci (n-1) + fibonacci (n-2) +main :: IO () main = mainWith $ do func "fib 5" fibonacci 5 func "fib 10" fibonacci 10 @@ -143,11 +146,12 @@ import System.Environment -- | Improved recursive algorithm for n-th Fibonacci number fibonacci :: Integer -> Integer -fibonacci n = fib 0 1 n +fibonacci = fib 0 1 where fib x _ 0 = x - fib x y rem = fib y (x+y) (rem-1) -- just "one-way" recursion! + fib x y n = fib y (x+y) (n-1) -- just "one-way" recursion! +main :: IO () main = do args <- getArgs print . fibonacci . read . head $ args @@ -251,29 +255,84 @@ If you know optimization with GCC, then you won't be surprised how it works with Then there are also `-f*` platform-independent flags, that allows you turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). +### Concurrency and Parallelism -### Concurrency +Haskell (of course) supports parallelism or concurrency in order to achieve faster and efficient computation. For parallelism and concurrency visit [wiki.haskell.org/Parallel](https://wiki.haskell.org/Parallel). You can both: -Just like with GCC, you can use optimization flags with GHC. You can also drill deeper in your source code and optimize it by hand, use FFI, parallelism or concurrency, and so on in order to achieve faster computation. Good resource for that is [wiki.haskell.org/Performance](https://wiki.haskell.org/Performance) where you can look up hints for specific parts of you app and/or your compiler. - -For parallelism and concurrency visit [wiki.haskell.org/Parallel](https://wiki.haskell.org/Parallel). You can both: - -* run parallel threads with Control.Parallel, +* run parallel threads with [Control.Parallel](http://hackage.haskell.org/package/parallel/docs/Control-Parallel.html), * run simultaneous IO actions with forks. It is also possible to do distributed computations on clusters but it is far beyond the scope of this course. ```haskell ---TODO: Control.Parallel simple example +import Control.Parallel + +parfib 0 = return 1 +parfib 1 = return 1 +parfib n = do + n1 <- parfib (n - 1) + n2 <- parfib (n - 2) + n3 <- (n1 `par` (n2 `seq` (return (n1 + n2 + 1)))) + return n3 + +main = do x <- parfib 30; print x +``` + +GHC supports running programs in parallel on an SMP (symmetric multiprocessor) or multi-core machine. Just compile your program using the `-threaded` switch and then run it with RTS option `-N ` (where `` is number of simultaneous threads). See [GHC docs](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/parallel.html) for more information. + +### FFI + +As many other programming languages, Haskell supports [FFI (Foreign Function Interface)](https://wiki.haskell.org/Foreign_Function_Interface) that allows to to cooperate with programs written with other languages. We've already could see that in the example of Haste in DS Wizard where there were some JS bindings. But you can also use it to call some functions from C++ or Rust: + +```cpp +extern "C"{ // need to expose with extern, could use header file .h or .hpp for that + extern int fib(int n) { + if(n < 0) return -1; + int x = 0, y = 1, tmp; + while(n-- > 0) { + tmp = x; + x = y; + y = tmp + x; + } + return x; + } +} +``` + +```haskell +{-# LANGUAGE ForeignFunctionInterface #-} + +import Foreign.C +import System.Environment + +foreign import ccall "fib" cfib :: CInt -> CInt + +main :: IO () +main = do + args <- getArgs + print . cfib . read . head $ args ``` +``` +% gcc -c -o fib.o fib.cpp +% ghc --make -o ffi_fib FibonacciFFI.hs fib.o +Linking ffi_fib ... +% /usr/bin/time -p ./ffi_fib 25 +75025 +real 0.00 +user 0.00 +sys 0.00 +``` + +Simirarly, there is `foreign export` to expose some Haskell functions to other FFIs. Nice example is here: [jarrett/cpphs](https://github.com/jarrett/cpphs). + ## Debugging Even if you are a good Haskell programmer, things can go wrong and especially in big projects it is a nontrivial challenge to find out where you did some mistake. Going thru the code in multiple functions, inner functions, various modules, etc. can be painful. Luckilly there are some ways how to debug Haskell program and some are pretty easy and similar to well-known. ### Tracing with `Debug.Trace` -You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. +You should already know how to use GHC and GHCi to compile, link and examine Haskell programs. The simplest tool to use for debugging is the `trace` from [Debug.Trace](https://hackage.haskell.org/package/base.0/docs/Debug-Trace.html) which outputs the trace message given as its first argument, before returning the second argument as its result. There are many more *traces* defined for different cases: `traceShow`, `traceId`, `traceStack`, `traceIO`, `traceM`, etc. So you can use it for custom debugging output anywhere in the code. For example: @@ -281,7 +340,57 @@ For example: func a b = trace ("func " ++ show a ++ " " ++ show b) undefined ``` -If you need more than just that, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: +Or better usage with our example of fibonacci numbers to see the calls: + +```haskell +module Main where + +import Debug.Trace + +-- | Naive recursive algorithm for n-th Fibonacci number +fib1 :: Integer -> Integer +fib1 0 = trace "fib1 0" 0 +fib1 1 = trace "fib1 1" 1 +fib1 n = trace ("fib1 " ++ show n) (fib1 (n-1) + fib1 (n-2)) + +-- | Improved recursive algorithm for n-th Fibonacci number +fib2 :: Integer -> Integer +fib2 = fib 0 1 + where + fib x _ 0 = trace "fib2 0" x + fib x y n = trace ("fib2 " ++ show n) (fib y (x+y) (n-1)) + +main :: IO () +main = do + print (fib1 4) + putStrLn "------------" + print (fib2 4) +``` + +``` +% runhaskell FibonacciTrace.hs +fib1 4 +fib1 2 +fib1 0 +fib1 1 +fib1 3 +fib1 1 +fib1 2 +fib1 0 +fib1 1 +3 +------------ +fib2 4 +fib2 3 +fib2 2 +fib2 1 +fib2 0 +3 +``` + +### GHCi debugger + +If you need better debugger, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: * setting breakpoints and stepping, * inspecting variables, @@ -289,14 +398,51 @@ If you need more than just that, you can use [GHCi debugger](https://downloads.h * working with exceptions, * and so on. +``` +Prelude> :l FibonacciNaive.hs +[1 of 1] Compiling Main ( FibonacciNaive.hs, interpreted ) +Ok, modules loaded: Main. +*Main> :break 9 +Breakpoint 0 activated at FibonacciNaive.hs:9:10-60 +*Main> fib1 5 +Stopped in Main.fib1, FibonacciNaive.hs:9:10-60 +_result :: Integer = _ +n :: Integer = 5 +[FibonacciNaive.hs:9:10-60] *Main> :continue +fib1 5 +Stopped in Main.fib1, FibonacciNaive.hs:9:10-60 +_result :: Integer = _ +n :: Integer = 3 +[FibonacciNaive.hs:9:28-33] *Main> :show breaks +[0] Main FibonacciNaive.hs:9:10-60 +[FibonacciNaive.hs:9:28-33] *Main> :abandon +*Main> +``` + ### `debug` package -### GHCi debugger +An intersting solution brins also the [debug](https://hackage.haskell.org/package/debug) package (and related extensions). It uses *Template Haskell* to examine the code and algorithms. + +```haskell +{-# LANGUAGE TemplateHaskell, ViewPatterns, PartialTypeSignatures #-} +{-# OPTIONS_GHC -Wno-partial-type-signatures #-} +module QuickSort(quicksort) where +import Data.List +import Debug + +debug [d| + quicksort :: Ord a => [a] -> [a] + quicksort [] = [] + quicksort (x:xs) = quicksort lt ++ [x] ++ quicksort gt + where (lt, gt) = partition (<= x) xs + |] +``` ## Further reading * [Haskell - Debugging](https://wiki.haskell.org/Debugging) * [Haskell - Performance](https://wiki.haskell.org/Performance) * [Haskell - Concurrency](https://wiki.haskell.org/Concurrency) +* [Real World Haskell - Concurrent and Multicore Programming](http://book.realworldhaskell.org/read/concurrent-and-multicore-programming.html) * [GHC - Concurrent and Parallel Haskell](https://downloads.haskell.org/~ghc/7.0.3/docs/html/users_guide/lang-parallel.html) From 1a3e0a31e04d5213f6c38ed8af3888b6aa186f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Thu, 10 May 2018 16:38:33 +0200 Subject: [PATCH 7/9] Unboxed types example --- tutorials/11_performance-debug.md | 32 +++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index fd18428..8831df8 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -14,7 +14,7 @@ fibonacci n = fibonacci (n-1) + fibonacci (n-2) main :: IO () main = do args <- getArgs - show . fibonacci . read . head $ args + print . fibonacci . read . head $ args ``` ## Measuring time and memory @@ -208,17 +208,37 @@ Now, we are going to briefly mention is the difference between boxed and unboxed To support laziness, parametric polymorphism, and other properties, by default Haskell data types are represented uniformly as a pointer to a closure on the heap. These are "boxed" values. An unboxed is represented directly by raw value (i.e., without any indirection). Using unboxed types can lead to time/space optimizations. Having always pointers to a heap-allocated object is fairly slow, so compilers attempt to replace these boxed values with unboxed raw values when possible. Unboxed values are a feature of some compilers that allow directly manipulating these low-level values. Since they behave differently than normal Haskell types, generally the type system is extended to type these unboxed values. -In [GHC], unboxed values have a hash mark as a suffix to their name. For instance, the unboxed representation of 42 is 42#. However, you can't pass them to polymorphic functions (like `show` for instance). To allow that, you need to use constructor `I#` that takes an unboxed integer and returns the `Int` (wraps). You can observe [kind](https://wiki.haskell.org/Kind) (*kind of type*, we will look again at kinds with typeclasses) of boxed and unboxed types: +In GHC, unboxed values have a hash mark as a suffix to their name. For instance, the unboxed representation of 42 is 42#. However, you can't pass them to polymorphic functions (like `show` for instance). To allow that, you need to use constructor `I#` that takes an unboxed integer and returns the `Int` (wraps). You can observe [kind](https://wiki.haskell.org/Kind) (*kind of type*, we will look again at kinds with typeclasses) of boxed and unboxed types: * By default, kind of type is `*` (try in GHCi: `:kind Int`) -* Kind of unboxed type is `#` (try in GHCi: `:kind Int#`, first do `:set -fglasgow-exts`) +* Kind of unboxed type is `#` (try in GHCi: `:kind Int#`) ```haskell +{-# LANGUAGE MagicHash #-} +module Main where + import GHC.Exts -showUnboxedInt :: Int# -> String -showUnboxedInt n = "Unboxed: " ++ (show $ I# n) ++ "#" +-- | Naive recursive algorithm for n-th Fibonacci number with +-- unboxed Int types +fibonacci :: Int# -> Int# +fibonacci 0# = 0# +fibonacci 1# = 1# +fibonacci n = fibonacci (n -# 1#) +# fibonacci (n -# 2#) + +main :: IO () +main = print (I# (fibonacci 25#)) +``` + ``` +% /usr/bin/time -p runhaskell FibonacciUnboxed.hs +75025 +real 0.30 +user 0.27 +sys 0.03 +``` + +For more information, visit [GHC.Exts]() and [GHC.Prim](). ### Strictness with types @@ -243,7 +263,7 @@ data T1 = T1 {-# UNPACK #-} !(Int, Float) -- => T1 Int Float data T2 = T2 Double {-# UNPACK #-} !Int -- => T2 Double Int# ``` -We mention this just because of differences in performance of types we are going to describe now. You don't need to use strict or unboxed types within your work if you don't need to have time/space optimizations... +We mention this just because of differences in performance of types we are going to describe now. You don't need to use strict or unboxed types within your work if you don't need to have time/space optimizations and if yes, consider reading [Haskell High Performance Programming](https://github.com/TechBookHunter/Free-Haskell-Books/blob/master/book/Haskell%20High%20Performance%20Programming.pdf). ### GHC optimization flags From d71a70cccba7d7024ea72c6ebeb612896a071585 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Such=C3=A1nek?= Date: Thu, 10 May 2018 16:46:51 +0200 Subject: [PATCH 8/9] Grammar fix --- tutorials/11_performance-debug.md | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index 8831df8..7b1c82a 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -1,6 +1,6 @@ # Performance and Debugging -During this tutorial we will take a look how to improve performance of Haskell program and how to debug it. We will use very simple example everywhere - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). +During this tutorial, we will take a look how to improve the performance of Haskell program and how to debug it. We will use very simple example everywhere - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). ```haskell import System.Environment @@ -19,11 +19,11 @@ main = do ## Measuring time and memory -When you want to check performance of program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. +When you want to check the performance of a program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. ### Basic `time` -The `time` command is one of the well-known Linux commands for programmers. It can be used to show how long a command takes to run. That makes it Very useful if you are a developer and you want to test the performance of your program or script. Especially to compare time of programs written in other languages "from outside". For basic usage, you will get three numbers: +The `time` command is one of the well-known Linux commands for programmers. It can be used to show how long a command takes to run. That makes it Very useful if you are a developer and you want to test the performance of your program or script. Especially to compare the time of programs written in other languages "from outside". For basic usage, you will get three numbers: - `real` = total time is taken to run the command (the same as if you use your normal stopwatch) - `user` = amount of time that was spent in user mode @@ -39,7 +39,7 @@ user 0.31 sys 0.01 ``` -But `time` can do a bit more, you can tell how output should look like with additional "numbers" - number of page faults, average total memory use of the process in kilobytes, number of signals delivered to the process, number of socket messages received/send by the process, exit status of the command, and many others. +But `time` can do a bit more, you can tell how output should look like with additional "numbers" - number of page faults, average total memory use of the process in kilobytes, number of signals delivered to the process, number of socket messages received/sent by the process, exit status of the command, and many others. ``` % /usr/bin/time -f "Elapsed Time: %E\nExit Status: %X\nPage Faults: %F" runhaskell FibonacciNaive.hs 25 @@ -51,7 +51,7 @@ Page Faults: 0 ### Benchmarking with Criterion -If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analysing benchmarks and a set of driver functions that makes it easy to build and run benchmarks, and to analyse their results. +If you are interested in such optimizations and improving your application or comparing various algorithms or their implementations, then you might find interesting to use a benchmarking library. In Haskell is the most used one called [Criterion](http://www.serpentine.com/criterion/). It provides a powerful but simple way to measure software performance. It provides both a framework for executing and analyzing benchmarks and a set of driver functions that makes it easy to build and run benchmarks and to analyze their results. For simple usage, you just need to work with the `defaultMain` from [Criterion.Main](https://hackage.haskell.org/package/criterion/docs/Criterion-Main.html) as they show in their example: @@ -73,7 +73,7 @@ main = defaultMain [ ] ``` -It has very nice outputs with form of interactive HTML pages with charts and comparisons and have many options to use. +It has very nice outputs with a form of interactive HTML pages with charts and comparisons and has many options to use. ``` % runhaskell FibonacciNaiveCriterion.hs @@ -102,7 +102,7 @@ runhaskell FibonacciNaiveCriterion.hs 15.98s user 0.04s system 99% cpu 16.055 t ### Measure allocations with Weigh -The package [weigh](https://hackage.haskell.org/package/weigh) provides simple interface to mesure the memory usage of a Haskell value or function. +The package [weigh](https://hackage.haskell.org/package/weigh) provides a simple interface to measure the memory usage of a Haskell value or function. ```haskell import Weigh @@ -120,7 +120,7 @@ main = mainWith $ do func "fib 25" fibonacci 25 ``` -It provides nice output as plain text table, but it is also possible to change format to markdown. +It provides a nice output as plain text table, but it is also possible to change the format to markdown. ``` % ./FibonacciNaiveWeigh @@ -137,7 +137,7 @@ Now we are able to measure something and compare algorithms, but how to improve ### Basic ideas -When you are not satisfied with the perfomance of your application, then before any sophisticated optimization steps by using strictness, unboxed types, calling FFI, etc., you should consider if you prefer faster application over better readability. Then another important thing to think about is design, if it is not slow by using "naive" algorithm, using inappropriate data structure (List instead of Set or Map), etc. +When you are not satisfied with the performance of your application, then before any sophisticated optimization steps by using strictness, unboxed types, calling FFI, etc., you should consider if you prefer faster application over better readability. Then another important thing to think about is design if it is not slow by using "naive" algorithm, using an inappropriate data structure (List instead of Set or Map), etc. **Always** rethink your own code before using other optimization techniques! @@ -269,11 +269,11 @@ We mention this just because of differences in performance of types we are going If you know optimization with GCC, then you won't be surprised how it works with GHC: -* `-O0` = turn off all optimisation +* `-O0` = turn off all optimization * `-O` or `-O1` = generate good-quality code without taking too long about it -* `-O2` = apply every non-dangerous optimisation, even if it means significantly longer compile times (in most cases, there is no significant difference between `-O1` and `-O2`) +* `-O2` = apply every non-dangerous optimization, even if it means significantly longer compile times (in most cases, there is no significant difference between `-O1` and `-O2`) -Then there are also `-f*` platform-independent flags, that allows you turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). +Then there are also `-f*` platform-independent flags, that allows you to turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). ### Concurrency and Parallelism @@ -298,11 +298,11 @@ parfib n = do main = do x <- parfib 30; print x ``` -GHC supports running programs in parallel on an SMP (symmetric multiprocessor) or multi-core machine. Just compile your program using the `-threaded` switch and then run it with RTS option `-N ` (where `` is number of simultaneous threads). See [GHC docs](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/parallel.html) for more information. +GHC supports running programs in parallel on an SMP (symmetric multiprocessor) or multi-core machine. Just compile your program using the `-threaded` switch and then run it with RTS option `-N ` (where `` is the number of simultaneous threads). See [GHC docs](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/parallel.html) for more information. ### FFI -As many other programming languages, Haskell supports [FFI (Foreign Function Interface)](https://wiki.haskell.org/Foreign_Function_Interface) that allows to to cooperate with programs written with other languages. We've already could see that in the example of Haste in DS Wizard where there were some JS bindings. But you can also use it to call some functions from C++ or Rust: +As with many other programming languages, Haskell supports [FFI (Foreign Function Interface)](https://wiki.haskell.org/Foreign_Function_Interface) that allows co-operating with programs written with other languages. We've already could see that in the example of Haste in DS Wizard where there were some JS bindings. But you can also use it to call some functions from C++ or Rust: ```cpp extern "C"{ // need to expose with extern, could use header file .h or .hpp for that @@ -344,11 +344,11 @@ user 0.00 sys 0.00 ``` -Simirarly, there is `foreign export` to expose some Haskell functions to other FFIs. Nice example is here: [jarrett/cpphs](https://github.com/jarrett/cpphs). +Similarly, there is `foreign export` to expose some Haskell functions to other FFIs. Nice example is here: [jarrett/cpphs](https://github.com/jarrett/cpphs). ## Debugging -Even if you are a good Haskell programmer, things can go wrong and especially in big projects it is a nontrivial challenge to find out where you did some mistake. Going thru the code in multiple functions, inner functions, various modules, etc. can be painful. Luckilly there are some ways how to debug Haskell program and some are pretty easy and similar to well-known. +Even if you are a good Haskell programmer, things can go wrong and especially in big projects it is a nontrivial challenge to find out where you did some mistake. Going thru the code in multiple functions, inner functions, various modules, etc. can be painful. Luckilly, there are some ways how to debug Haskell program and some are pretty easy and similar to well-known. ### Tracing with `Debug.Trace` @@ -360,7 +360,7 @@ For example: func a b = trace ("func " ++ show a ++ " " ++ show b) undefined ``` -Or better usage with our example of fibonacci numbers to see the calls: +Or better usage with our example of Fibonacci numbers to see the calls: ```haskell module Main where @@ -410,7 +410,7 @@ fib2 0 ### GHCi debugger -If you need better debugger, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: +If you need a better debugger, you can use [GHCi debugger](https://downloads.haskell.org/~ghc/7.4.1/docs/html/users_guide/ghci-debugger.html) (other compilers, such as Hugs, have some different), which allows: * setting breakpoints and stepping, * inspecting variables, @@ -441,7 +441,7 @@ n :: Integer = 3 ### `debug` package -An intersting solution brins also the [debug](https://hackage.haskell.org/package/debug) package (and related extensions). It uses *Template Haskell* to examine the code and algorithms. +An interesting solution brings also the [debug](https://hackage.haskell.org/package/debug) package (and related extensions). It uses *Template Haskell* to examine the code and algorithms. ```haskell {-# LANGUAGE TemplateHaskell, ViewPatterns, PartialTypeSignatures #-} From 03154d0e9d4d5d84265bb39cc9dfdaf0ebffdf25 Mon Sep 17 00:00:00 2001 From: Robert Pergl Date: Thu, 10 May 2018 18:25:36 +0200 Subject: [PATCH 9/9] a few fixes --- tutorials/11_performance-debug.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tutorials/11_performance-debug.md b/tutorials/11_performance-debug.md index 7b1c82a..264bad0 100644 --- a/tutorials/11_performance-debug.md +++ b/tutorials/11_performance-debug.md @@ -1,6 +1,6 @@ # Performance and Debugging -During this tutorial, we will take a look how to improve the performance of Haskell program and how to debug it. We will use very simple example everywhere - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). +During this tutorial, we will take a look how to improve the performance of a Haskell program and how to debug it. We will use very simple example - [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number). ```haskell import System.Environment @@ -17,7 +17,7 @@ main = do print . fibonacci . read . head $ args ``` -## Measuring time and memory +## Measuring time and memory When you want to check the performance of a program and compare two programs or algorithms in terms of time or memory consumption, you need to measure it. @@ -273,7 +273,7 @@ If you know optimization with GCC, then you won't be surprised how it works with * `-O` or `-O1` = generate good-quality code without taking too long about it * `-O2` = apply every non-dangerous optimization, even if it means significantly longer compile times (in most cases, there is no significant difference between `-O1` and `-O2`) -Then there are also `-f*` platform-independent flags, that allows you to turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). +Then there are also `-f*` platform-independent flags, that allows you to turn on and off individual optimizations. For more information, please visit [GHC documentation](http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/using-optimisation.html). ### Concurrency and Parallelism @@ -435,8 +435,8 @@ _result :: Integer = _ n :: Integer = 3 [FibonacciNaive.hs:9:28-33] *Main> :show breaks [0] Main FibonacciNaive.hs:9:10-60 -[FibonacciNaive.hs:9:28-33] *Main> :abandon -*Main> +[FibonacciNaive.hs:9:28-33] *Main> :abandon +*Main> ``` ### `debug` package