-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Internal Cache blows up with many constrain's #460
Comments
Thanks for the detailed write-up! I've never looked into performance characteristics, and there might very well be a memory leak in there. Let me take some time to investigate. |
Is there a reason why you can't collect all the "must-be-constraint" Additionally, I can also imagine adding a variant of What do you think? |
In the test example all the data Lang a
= Lit Bool
| Ref a
| Neg (Lang a)
| Or (Lang a)
| And (Lang a)
| Foo !String (Lang a) (Lang a) The semantics of which are identical to normal propositional logic except for the myFormula :: Lang String
myFormula = a /\ (~b => (a \/ c)) /\ (Foo a e) \/ ((a => b) \/ (c => a)) Then i do a left fold (or prefix traversal) of the AST. i end up with This leads to having Hopefully that makes sense. I can mock up a more worked example if necessary. |
Yes; something that I can run an experiment with would be awesome! Do you know if this is also an issue when you don't use query features at all? Looks like your use case does call for queries, but I'm curious if the culprit is in the query-layer or if this sort of bad behavior can be triggered from regular Symbolic code. |
Interesting I observe the same behavior with only -- | the test runner, this time f returns a predicate and we have no query
-- | operations whatsoever
testS :: ([S.SBool] -> S.Predicate) -> Int -> IO S.SatResult
testS f n = S.sat $ (S.sBools $! stringList n) >>= f
-- | I fold over the string of SBools here constraining at each accumulation,
-- this seems to blow up the internal cache severely leading to about 95% GC
-- time
badS :: [S.SBool] -> S.Predicate
badS prop' = do b <- foldM (helper) S.true prop'
S.constrain b
return b
-- | combine the current sbool with the accumulated sbool, constrain the
-- two and then return the accumulated result
where helper x acc = do let b = x S.&&& acc
S.constrain b
return b
-- | identical to the bad version but I do not constrain for each accumulation
goodS :: [S.SBool] -> S.Predicate
goodS prop' = do b <- foldM (helper) S.true prop'
S.constrain b
return b
-- | this helper is equivalent to just foldr' (S.&&&)
where helper x acc = do let b = x S.&&& acc
return b
main = do
-- putStrLn "Running Good:\n"
-- goodRes <- testS goodS 1000
putStrLn "Running Bad:\n"
badRes <- testS badS 1000
-- just ensuring evaluation
-- print (length $ show goodRes)
print (length $ show badRes) Good test:
Bad Test:
|
It's actually good to know that 'Symbolic' has the same issue. Currently we store constraints in an IORef that looks like this:
That's one list that you'll keep on overwriting all the time. We really need to replace that with a much better data-structure. It may not help solve the issue you're facing, but replacing it with something like a sequence would definitely help. I'll see if I can code this up sometime tomorrow; shouldn't be that hard. Hopefully it'll fix the issue, but if not, it's an improvement that won't be a wasted effort. I should have something you can try out sometime tomorrow. |
I just pushed in some changes to improve how constraints are stored internally. I'm not sure if it'll fix the issue, but hopefully, it won't make it worse! Can you build from master and give it a shot? I'm curious if it'll make any difference. We can then investigate further. |
Will do! |
Thanks! I very much doubt this'll solve the problem; as the issue is really with constructing those constraints in the first place; which isn't addressed by this change. But performance is hard to predict, maybe we'll be pleasantly surprised! |
In the end, it may not be something we can fix easily; but I'd like to understand it better. Right now I don't see any smoking guns. Would it be possible for you to create a github repo and scripts etc? I'd like to clone it so I can run the profiler myself to experiment. I'm hoping that's not a lot of work for you! |
Sure I can set this up tonight. You prefer |
stack is just fine; cabal is OK too; whichever you already have. a "doit" script that spits out these graphs would be fantastic! |
Hi! I got some local stuff setup so I can get the profiling data myself; no set-up necessary. I'll dig into this and hopefully at least understand where the memory is going. Thanks! |
Repo is up here, let me know if you have any issues or questions and thanks for the help. |
Thanks! These'll definitely come in handy for future performance studies as well. |
I spent some time looking at this; unfortunately the |
I've managed to confuse myself.. I decided to simplify the program a bit, and got this: import Control.Monad
import Data.SBV
bad = sat $ do bs <- mapM (const free_) [1..1000]
foldM go sTrue bs
where go x acc = let s = x .&& acc
in do constrain s
return s
good = sat $ do bs <- mapM (const free_) [1..1000]
foldM go sTrue bs
where go x acc = return $ x .&& acc
main = do print =<< bad
print =<< good So far as I can tell, this is essentially the same as your program. But when I compile and run this, both As a matter of fact, I also compiled it, and got the same result. Something really fishy is going on here! |
I was unable to reproduce this. I've setup a benchmark poorly named
The only alteration I made to the program you posted above was printing the string length of the
The test repo should pull down |
This is so bizarre. I did the following in a brand new directory; no stack, no nothing:
The main runs so quickly and prints those numbers in succession with no wait. Are you saying it doesn't do that for you, if you repeat this in a brand new directory? |
The above was on a Mac. I also just tried the exact same on a linux box, running GHC 8.0.1 (yeah, very old!), and got the exact same speedy output. |
NB. Please run this without compiling for profiling or any RTS options; just pure GHC compilation with no fancy flags. I'm now concerned that the profiling annotations that GHC inserts are causing blow-up, though I don't understand why that should be the case. Would be good to have you confirm or refute that independently! |
Extremely strange! I was able to reproduce your findings:
|
Great, glad you can replicate! So, I have to admit I'm not entirely sure what this means:
Does it mean run it like a million times or something? There's a minor difference obviously, and maybe that's getting amplified? (Although I don't see how that command can manage to run it a million times, but I've never been a big user of I think there's still a problem in there somewhere; but without understanding the real root cause, it'll be tough to fix! |
From the user manual here: https://docs.haskellstack.org/en/stable/GUIDE/#components-test-and-bench It seems it merely builds it in this particular case; and just runs it once. So, this suggests the bad behavior is triggered when SBV is profiled? That doesn't make much sense to me. You obviously didn't run into this problem while benchmarking something else, I presume? Does the bad behavior exhibit itself in your larger context with no profile arguments? I'm really curious now! |
Confirm with the test repo. If you set profiling to
|
Great sleuthing! Thanks for the analysis. I'm at a loss, unfortunately. I'm not sure why profiling is causing issues here. I'm open to ideas on how to further debug. |
Regarding ➜ sbv460 git:(master) ✗ cat package.yaml
name: sbv460
version: 0.1.0.0
github: "githubuser/sbv460"
license: BSD3
author: "Author name here"
maintainer: "[email protected]"
copyright: "2019 Author name here"
extra-source-files:
- README.md
- ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/sbv460#readme>
dependencies:
- base >= 4.7 && < 5
- sbv >= 8.0.5
library:
source-dirs: src
executables:
sbv460-profile:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- sbv460
benchmarks:
# benchmark that tests the simplified program levent posted
# https://github.com/LeventErkok/sbv/issues/460#issuecomment-469590832
newBad:
main: Main.hs
source-dirs:
- bench/LeventTest
- src
ghc-options:
- -O2
- -threaded
- -rtsopts
- -with-rtsopts=-s
bad:
main: Main.hs
source-dirs:
- bench/Bad
- src
ghc-options:
- -O2
- -threaded
- -rtsopts
- -with-rtsopts=-s
good:
main: Main.hs
source-dirs:
- bench/Good
- src
ghc-options:
- -O2
- -threaded
- -rtsopts
- -with-rtsopts=-s
tests:
sbv460-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- sbv460 notice the |
Thanks for the explanation; good to know. So, do we concur that profiling SBV causes the issue here, or do you suspect something else might be going on? |
I've gotta say this is also above my pay grade, but I wonder if enabling profiling like this forces values in the |
Does that explain why "good" behaves ok? If profiling hangs on to state, then I'd expect both to exhibit the same behavior. I don't understand why having a call to If we can replicate this on a toy program (i.e., without SBV), I think it would be well worth asking the GHC folks; or at least on stack-overflow or something. I'm afraid with SBV in the loop, however, it'll be hard for anyone else to dig into it. Do you think you might be able to replicate with something simpler? (I know this is a lot to ask!) |
Also: You clearly must've hit some other issue before you decided to profile. We still need to address the original issue I suppose. What caused you to go down the profile path? |
I can try to replicate without |
Great; please do keep me in the loop; if SBV is contributing to this in any way I'd like to fix it. (Pull requests most welcome!) There'll be a release of SBV sometime this weekend. Should I hold off before you get some further insight? Or should we go with the release? (The next-next release, I'm afraid, won't be for quite a while after this one.) |
Nah go ahead with the release. I think we have good evidence this is isolated to just profiling and when profiling is turned off SBV is performant. I'll pull the new release, verify the bug, and experiment this weekend. Thanks for the quick responses and excellent work Levent! |
Sounds good. I'm going to close this ticket then; but please re-open it if you spot anything funky going on in SBV proper. Even if it only shows up during profiling, I'd like to at least understand it better. Much appreciated! |
Hey Levent,
I have a computation that exists entirely in the
Query
monad. I recently did some profiling and it looks like the internalCache
is blowing up in size leading to about 95% GC use. So I'm looking for help in understanding why this behavior is occurring and how to avoid it.The basic computation is a fold over a custom abstract syntax tree where I accumulate on
SBool
s. I've been able to reproduce this with the following tiny program that folds a list ofSBool
s in the query monad:I just commented out the lines for each test and ran with
stack bench --profile cache-test --benchmark-arguments='+RTS -hc -s -RTS'
; I get the following results:for good:
with heap profile.
and for bad:
with heap profile
Notice the discrepancy in the
GC
time and calculatedProductivity
and the difference in the y-axis of the heap profiles. So I assume it is the case that(constrain $ a and b and c) /= ((constrain (a and b) >> return (a and b) >>= constrain . (and c))
. Any suggestions for whats happening here? I've already reduced the amount ofconstrain
s in my computation to a maximum, but I'm dealing with about 16000 variables which easily blows up the cache.Let me know if I can further assist in any way and thanks for the help.
The text was updated successfully, but these errors were encountered: