Skip to content

Performance of the diff algorithm #92

@guibou

Description

@guibou

In multiple cases, we observe that sydtest is hanging for a long time (multiples minutes) when the diff (generated by a failing shouldBe or golden test) is large.

The following example:

import Test.Syd
import Control.Monad
import System.Random

n = 10

main = sydTest $ do
  it "test" $ do
    numbers <- replicateM n (randomIO @Double)
    numbers' <- replicateM n (randomIO @Double)
    numbers `shouldBe` numbers'

Build with ghc -O2 Main.hs, it fails and the runtime of the test suite (mostly spent in the diff computation) varies with n:

  • n = 10: 13ms
  • n = 100: 203ms (15x with n = 10)
  • n = 1000: 29s (142x with n = 100)
  • n = 10000: (killed after 10 minutes).

Note that the diff algorithm is generating an impressive number of added/removed chunks, see:

image

In our codebase, we mitigated the issue by pre simplifying the diff. Mostly for golden tests, all of our golden tests are done on json output, so we have a "post process" in the goldenTestCompare logic which recursively replaces "big diff" nodes in the json tree by a smaller diff (e.g. for a json array of numbers, we replace the array by a string containing some statistical values about the array). However, it does not always work and users are able to break tests in ways which lead to sydtest hanging. Our CI is also hanging a lot.

A few possible action points:

a) Add a timeout on the diff algorithm in sydtest. If the diff takes more than (e.g. 100ms), just stop computing the diff and display a message. User can still do --golden-reset and use local (and possibly more efficient) diff tools. This won't fix the shouldBe unfortunately.
b) Improve the diff algorithm. Most of the time, git diff is able to diff these files in a matter of millisec. Note that having the current diff algorithm takes great care of generating the "smallest" diff possible and this usually leads to a lot of diff chunks and I suspect that this is the reason why it is slow. Unfortunately, having so much diff chunks makes also the diff someone not readable. For example, for me, it is more readable to have something like common prefix {- 1.3456 -} {+ 2.3457 +} common suffix instead of common prefix {- 1 -} {+ 2 +}.345 {- 6 -} {+ 7 +} common suffix
c) Could the diff algorithm be parametrized at runtime in sydtest configuration record?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions