Skip to content

Commit

Permalink
[docs] whitespace, docs on Performance and capitalisation of properties
Browse files Browse the repository at this point in the history
 - Added information about how I want it, to the DEVGUIDE
 - Added link to DEVGUIDE in README
 - Fixed minor formatting transgressions
 - Added docs on perf testing and calling `testSequenced`.
  • Loading branch information
haf committed Jan 3, 2017
1 parent 4ed8254 commit 339a009
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 120 deletions.
11 changes: 11 additions & 0 deletions DEVGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@
- Make your test for your change
- Make your change
- If appropriate, write docs in README.md.
1. For variables: prefer `test` over `t`. Prefer `test` over
`sequencedTestCode`.
1. Try to stick to max 80 chars wide lines, unless it improves readability to
let the line be longer. `set tw=80` can be used in vim.
1. If you have a function that takes a list of tests, `let run` then it's
better to name the list/seq `ts` than `l` for three reasons:
1. You may be using a sequence an not a list in the future, so `l` is not
necessarily forwards-compatible.
2. When iterating `ts`, it becomes natural to read and understand each
element as `t`: `Seq.mapi (fun i t -> ...)`
3. `l` is easily confused with `I` and `1`, depending on typeface.

## Code style

Expand Down
47 changes: 32 additions & 15 deletions Expecto.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,15 @@ let expecto =
]

let inline popCount (i:uint16) =
let mutable v = uint32 i
v <- v - ((v >>> 1) &&& 0x55555555u)
v <- (v &&& 0x33333333u) + ((v >>> 2) &&& 0x33333333u)
((v + (v >>> 4) &&& 0xF0F0F0Fu) * 0x1010101u) >>> 24
let mutable v = uint32 i
v <- v - ((v >>> 1) &&& 0x55555555u)
v <- (v &&& 0x33333333u) + ((v >>> 2) &&& 0x33333333u)
((v + (v >>> 4) &&& 0xF0F0F0Fu) * 0x1010101u) >>> 24

let inline popCount16 i =
let mutable v = i - ((i >>> 1) &&& 0x5555us)
v <- (v &&& 0x3333us) + ((v >>> 2) &&& 0x3333us)
((v + (v >>> 4) &&& 0xF0Fus) * 0x101us) >>> 8
let mutable v = i - ((i >>> 1) &&& 0x5555us)
v <- (v &&& 0x3333us) + ((v >>> 2) &&& 0x3333us)
((v + (v >>> 4) &&& 0xF0Fus) * 0x101us) >>> 8

[<Tests>]
let popcountTest =
Expand All @@ -551,18 +551,27 @@ let performance =
testSequenced <| testList "performance" [

testCase "1 <> 2" <| fun _ ->
let test() = Expect.isFasterThan (fun () -> 1) (fun () -> 2) "1 equals 2 should fail"
assertTestFailsWithMsgContaining "same" (test,Normal)
let test () =
Expect.isFasterThan (fun () -> 1) (fun () -> 2) "1 equals 2 should fail"
assertTestFailsWithMsgContaining "same" (test, Normal)

testCase "half is faster" <| fun _ ->
Expect.isFasterThan (fun () -> repeat10000 log 76.0) (fun () -> repeat10000 log 76.0 |> ignore; repeat10000 log 76.0) "half is faster"
Expect.isFasterThan (fun () -> repeat10000 log 76.0)
(fun () -> repeat10000 log 76.0 |> ignore; repeat10000 log 76.0)
"half is faster"

testCase "double is faster should fail" <| fun _ ->
let test() = Expect.isFasterThan (fun () -> repeat10000 log 76.0 |> ignore; repeat10000 log 76.0) (fun () -> repeat10000 log 76.0) "double is faster should fail"
let test () =
Expect.isFasterThan (fun () -> repeat10000 log 76.0 |> ignore; repeat10000 log 76.0)
(fun () -> repeat10000 log 76.0)
"double is faster should fail"
assertTestFailsWithMsgContaining "slower" (test, Normal)

ptestCase "same function is faster should fail" <| fun _ ->
let test() = Expect.isFasterThan (fun () -> repeat100000 log 76.0) (fun () -> repeat100000 log 76.0) "same function is faster should fail"
let test () =
Expect.isFasterThan (fun () -> repeat100000 log 76.0)
(fun () -> repeat100000 log 76.0)
"same function is faster should fail"
assertTestFailsWithMsgContaining "equal" (test, Normal)

testCase "matrix" <| fun _ ->
Expand All @@ -571,25 +580,33 @@ let performance =
let a = Array2D.init n n (fun _ _ -> rand.NextDouble())
let b = Array2D.init n n (fun _ _ -> rand.NextDouble())
let c = Array2D.zeroCreate n n

let reset() =
for i = 0 to n-1 do
for j = 0 to n-1 do
c.[i,j] <- 0.0

let mulIJK() =
for i = 0 to n-1 do
for j = 0 to n-1 do
for k = 0 to n-1 do
c.[i,k] <- c.[i,k] + a.[i,j] * b.[j,k]

let mulIKJ() =
for i = 0 to n-1 do
for k = 0 to n-1 do
let mutable t = 0.0
for j = 0 to n-1 do
t <- t + a.[i,j] * b.[j,k]
c.[i,k] <- t
Expect.isFasterThanSub (fun measurer -> reset(); measurer mulIKJ ()) (fun measurer -> reset(); measurer mulIJK ()) "ikj faster than ijk"
Expect.isFasterThanSub (fun measurer -> reset(); measurer mulIKJ ())
(fun measurer -> reset(); measurer mulIJK ())
"ikj faster than ijk"

testCase "popcount" <| fun _ ->
let test() = Expect.isFasterThan (fun () -> repeat10000 (popCount16 >> int) 987us) (fun () -> repeat10000 (popCount >> int) 987us) "popcount 16 faster than 32 fails"
assertTestFailsWithMsgContaining "slower" (test,Normal)
let test () =
Expect.isFasterThan (fun () -> repeat10000 (popCount16 >> int) 987us)
(fun () -> repeat10000 (popCount >> int) 987us)
"popcount 16 faster than 32 fails"
assertTestFailsWithMsgContaining "slower" (test, Normal)
]
67 changes: 42 additions & 25 deletions Expecto/Expect.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ module Expecto.Expect
()

open System
open Expecto.Logging
open Expecto.Logging.Message
type HSet<'a> = System.Collections.Generic.HashSet<'a>

/// Expects f to throw an exception.
Expand Down Expand Up @@ -325,35 +327,50 @@ let stringHasLength (subject : string) (length : int) format =

/// Expect the streams to byte-wise equal.
let streamsEqual (s1 : IO.Stream) (s2 : IO.Stream) format =
let buf = Array.zeroCreate<byte> 2
let rec compare pos =
match s1.Read(buf, 0, 1), s2.Read(buf, 1, 1) with
| x, y when x <> y ->
Tests.failtestf "%s. Not equal at pos %d" format pos
| 0, _ ->
()
| _ when buf.[0] <> buf.[1] ->
Tests.failtestf "%s. Not equal at pos %d" format pos
| _ ->
compare (pos + 1)
compare 0

/// Expects function `f1` is faster than `f2`. Measurer used to measure only a subset of the functions.
/// Statistical test to 99.99% confidence level.
let buf = Array.zeroCreate<byte> 2
let rec compare pos =
match s1.Read(buf, 0, 1), s2.Read(buf, 1, 1) with
| x, y when x <> y ->
Tests.failtestf "%s. Not equal at pos %d" format pos
| 0, _ ->
()
| _ when buf.[0] <> buf.[1] ->
Tests.failtestf "%s. Not equal at pos %d" format pos
| _ ->
compare (pos + 1)
compare 0

/// Expects function `f1` is faster than `f2`. Measurer used to measure only a
/// subset of the functions. Statistical test to 99.99% confidence level.
let isFasterThanSub (f1:Performance.Measurer<_,_>->'a) (f2:Performance.Measurer<_,_>->'a) format =
let toString (s:Performance.SampleStatistics) = sprintf "%.4f \u00B1 %.4f ms" s.Mean s.MeanStandardError
let toString (s:Performance.SampleStatistics) =
sprintf "%.4f \u00B1 %.4f ms" s.mean s.meanStandardError

match Performance.timeCompare f1 f2 with
| Performance.ResultNotTheSame (r1,r2)-> Tests.failtestf "%s. Expected function results to be the same (%A vs %A)." format r1 r2
| Performance.ResultNotTheSame (r1, r2)->
Tests.failtestf "%s. Expected function results to be the same (%A vs %A)."
format r1 r2
| Performance.MetricTooShort (s,p) ->
Tests.failtestf "%s. Expected metric (%s) to be much longer than the machine resolution (%s)." format (toString s) (toString p)
Tests.failtestf "%s. Expected metric (%s) to be much longer than the machine resolution (%s)."
format (toString s) (toString p)
| Performance.MetricEqual (s1,s2) ->
Tests.failtestf "%s. Expected f1 (%s) to be faster than f2 (%s) but are equal." format (toString s1) (toString s2)
Tests.failtestf "%s. Expected f1 (%s) to be faster than f2 (%s) but are equal."
format (toString s1) (toString s2)
| Performance.MetricMoreThan (s1,s2) ->
Tests.failtestf "%s. Expected f1 (%s) to be faster than f2 (%s) but is ~%.0f%% slower." format (toString s1) (toString s2) ((s1.Mean/s2.Mean-1.0)*100.0)
Tests.failtestf "%s. Expected f1 (%s) to be faster than f2 (%s) but is ~%.0f%% slower."
format (toString s1) (toString s2) ((s1.mean/s2.mean-1.0)*100.0)
| Performance.MetricLessThan (s1,s2) ->
printfn "%s. f1 (%s) is ~%.0f%% faster than f2 (%s)." format (toString s1) ((1.0-s1.Mean/s2.Mean)*100.0) (toString s2)

/// Expects function `f1` is faster than `f2`.
/// Statistical test to 99.99% confidence level.
Impl.logger.info (
eventX "{message}. f1 ({sample1}) is {percent} faster than f2 ({sample2})."
>> setField "message" format
>> setField "sample1" (toString s1)
>> setField "percent" (sprintf "~%.0f%%" ((1.0-s1.mean/s2.mean)*100.0))
>> setField "sample2" (toString s2))
//printfn "%s. f1 (%s) is ~%.0f%% faster than f2 (%s)." format (toString s1) ((1.0-s1.mean/s2.mean)*100.0) (toString s2)

/// Expects function `f1` is faster than `f2`. Statistical test to 99.99%
/// confidence level.
let isFasterThan (f1:unit->'a) (f2:unit->'a) format =
isFasterThanSub (fun measurer -> measurer f1 ()) (fun measurer -> measurer f2 ()) format
isFasterThanSub (fun measurer -> measurer f1 ())
(fun measurer -> measurer f2 ())
format
Loading

0 comments on commit 339a009

Please sign in to comment.