HasCallStack - runtime costs?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

HasCallStack - runtime costs?

Johannes Waldmann-2
Dear Cafe,

the new (8.*) call stack feature
https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
is certainly nice for debugging during development.

But how costly is it at runtime? I notice a 5 percent slowdown.

That's not a problem if there's an easy way to switch
this off for production (without changing the code).

Related: how to make code that uses it,
compile with older ghcs that don't have it.

I made this hack: do not import GHC.Stack.Types, but instead

{-# language CPP, MultiParamTypeClasses #-}

#if (__GLASGOW_HASKELL__ < 710)
{-# language NullaryTypeClasses #-}
#endif

module Stack
( HasCallStack )
where

#if (__GLASGOW_HASKELL__ >= 800)
import GHC.Stack.Types
#else
class HasCallStack
instance HasCallStack
#endif

When I compile with 8.rc2, and change ">= 800" to ">= 900",
I am getting the 5 percent speedup mentioned above.

But does it really do what I hope it does
(remove all runtime overhead that call stacks may have)?

When I compile with 7.10.3, I am getting 5 .. 10 percent faster again.

My code does nothing fancy (w.r.t. types and libraries),
it just uses Data.IntMap heavily. And it has some

class Semiring s where
  zero :: s
  one  :: s
  plus :: HasCallStack => s -> s -> s
  times :: HasCallStack => s -> s -> s

- J.W.
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Eric Seidel-3
On Fri, Mar 4, 2016, at 06:53, Johannes Waldmann wrote:
> Dear Cafe,
>
> the new (8.*) call stack feature
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
> is certainly nice for debugging during development.
>
> But how costly is it at runtime? I notice a 5 percent slowdown.

HasCallStack is really just a type class with a couple special rules for
building dictionaries in GHC's constraint solver. So at runtime each
function with a HasCallStack constraint takes an extra CallStack
argument. I don't know how to quantify the performance implications
beyond that, but you're right that HasCallStack is not free.

> That's not a problem if there's an easy way to switch
> this off for production (without changing the code).

Since HasCallStack is not a feature of the RTS, but actually part of the
generated code, there's not really an easy way to disable it without
changing the code. As much as I dislike CPP, I think it's the best
solution for a toggleable HasCallStack, something like

#if DEBUG
#define HASCALLSTACK (HasCallStack)
#else
#define HASCALLSTACK ()
#endif

foo :: HASCALLSTACK => a -> b

ought to work.
 

> Related: how to make code that uses it,
> compile with older ghcs that don't have it.
>
> I made this hack: do not import GHC.Stack.Types, but instead
>
> {-# language CPP, MultiParamTypeClasses #-}
>
> #if (__GLASGOW_HASKELL__ < 710)
> {-# language NullaryTypeClasses #-}
> #endif
>
> module Stack
> ( HasCallStack )
> where
>
> #if (__GLASGOW_HASKELL__ >= 800)
> import GHC.Stack.Types
> #else
> class HasCallStack
> instance HasCallStack
> #endif

This might be a nice addition to the base-compat package.

> When I compile with 8.rc2, and change ">= 800" to ">= 900",
> I am getting the 5 percent speedup mentioned above.
>
> But does it really do what I hope it does
> (remove all runtime overhead that call stacks may have)?

It should remove all the overhead of call stacks for calling functions
you wrote. If you import a function with a HasCallStack constraint
there's no way to disable the overhead for that function (for good
reason, it might use the CallStack!).

> When I compile with 7.10.3, I am getting 5 .. 10 percent faster again.
>
> My code does nothing fancy (w.r.t. types and libraries),
> it just uses Data.IntMap heavily. And it has some
>
> class Semiring s where
>   zero :: s
>   one  :: s
>   plus :: HasCallStack => s -> s -> s
>   times :: HasCallStack => s -> s -> s

I'm curious, why do plus and times take a CallStack? I wouldn't expect
them to be partial, so it seems like unnecessary overhead.

Eric
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Johannes Waldmann-2
In reply to this post by Johannes Waldmann-2
Thanks, Eric.


> If you import a function with a HasCallStack constraint
> there's no way to disable the overhead

But then this means that each library
should avoid these constraints?

Or not, because this rightly scares away users
from calling nontotal functions?


> ... with a couple special rules for
> building dictionaries in GHC's constraint solver.

then activation of these special rules could be a compiler switch?
Though it'd probably create a mess with separate compilation/linking.
But switching via CPP will do the same.


> class Semiring s where
>   plus :: HasCallStack => s -> s -> s
>   times :: HasCallStack => s -> s -> s
> I'm curious, why do plus and times take a CallStack?

because I want to (be able to) debug implementations.

simplified example:

{-# language ConstrainedClassMethods #-}

import GHC.Stack.Types

class C a where
  p ::                 a -> Int
  q :: HasCallStack => a -> Int

instance C () where
  p x = error "huh"
  q x = error "huh"


Calling  q  gives more information:


*Main> p ()
*** Exception: huh
CallStack (from HasCallStack):
  error, called at CS.hs:10:9 in main:Main

*Main> q ()
*** Exception: huh
CallStack (from HasCallStack):
  error, called at CS.hs:11:9 in main:Main
  q, called at <interactive>:44:1 in interactive:Ghci1
  it, called at <interactive>:44:1 in interactive:Ghci1


I did not see another way than to change the type
of the method in the class. (Which looks terrible of course.)


- J.
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Michael Sloan
In reply to this post by Eric Seidel-3
On Fri, Mar 4, 2016 at 8:30 AM, Eric Seidel <[hidden email]> wrote:
On Fri, Mar 4, 2016, at 06:53, Johannes Waldmann wrote:
> Dear Cafe,
>
> the new (8.*) call stack feature
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
> is certainly nice for debugging during development.
>
> But how costly is it at runtime? I notice a 5 percent slowdown.

HasCallStack is really just a type class with a couple special rules for
building dictionaries in GHC's constraint solver. So at runtime each
function with a HasCallStack constraint takes an extra CallStack
argument. I don't know how to quantify the performance implications
beyond that, but you're right that HasCallStack is not free.

Hmm, is it no longer an implicit parameter?  I thought the new
HasCallStack stuff was just a constraint synonym like

type HasCallStack = (?callStack :: CallStack)

For the most part the distinction doesn't matter, but it does when it
comes to constraints in instance heads.  The issue is that constraints
are not solved at their callsites.  Do we want `HasCallStack` to be
able to be present in an instance head, and potentially get a location
quite different from the actual use site?  I would find this confusing,
though sometimes helpful.

Implicit parameters are a good match for this callstack stuff, because
they have the restriction that they cannot be used as supeclass
constraints.

Does your work on the callstack stuff (thanks!) introduce backwards
compatibility issues?  I'm working on a library that will make extensive
use of 7.10's implementation of callstacks.
 
 
> That's not a problem if there's an easy way to switch
> this off for production (without changing the code).

Since HasCallStack is not a feature of the RTS, but actually part of the
generated code, there's not really an easy way to disable it without
changing the code. As much as I dislike CPP, I think it's the best
solution for a toggleable HasCallStack, something like

#if DEBUG
#define HASCALLSTACK (HasCallStack)
#else
#define HASCALLSTACK ()
#endif

foo :: HASCALLSTACK => a -> b

ought to work.

> Related: how to make code that uses it,
> compile with older ghcs that don't have it.
>
> I made this hack: do not import GHC.Stack.Types, but instead
>
> {-# language CPP, MultiParamTypeClasses #-}
>
> #if (__GLASGOW_HASKELL__ < 710)
> {-# language NullaryTypeClasses #-}
> #endif
>
> module Stack
> ( HasCallStack )
> where
>
> #if (__GLASGOW_HASKELL__ >= 800)
> import GHC.Stack.Types
> #else
> class HasCallStack
> instance HasCallStack
> #endif

This might be a nice addition to the base-compat package.

> When I compile with 8.rc2, and change ">= 800" to ">= 900",
> I am getting the 5 percent speedup mentioned above.
>
> But does it really do what I hope it does
> (remove all runtime overhead that call stacks may have)?

It should remove all the overhead of call stacks for calling functions
you wrote. If you import a function with a HasCallStack constraint
there's no way to disable the overhead for that function (for good
reason, it might use the CallStack!).

> When I compile with 7.10.3, I am getting 5 .. 10 percent faster again.
>
> My code does nothing fancy (w.r.t. types and libraries),
> it just uses Data.IntMap heavily. And it has some
>
> class Semiring s where
>   zero :: s
>   one  :: s
>   plus :: HasCallStack => s -> s -> s
>   times :: HasCallStack => s -> s -> s

I'm curious, why do plus and times take a CallStack? I wouldn't expect
them to be partial, so it seems like unnecessary overhead.

Eric
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Michael Sloan


On Sat, Mar 5, 2016 at 12:23 AM, Michael Sloan <[hidden email]> wrote:
On Fri, Mar 4, 2016 at 8:30 AM, Eric Seidel <[hidden email]> wrote:
On Fri, Mar 4, 2016, at 06:53, Johannes Waldmann wrote:
> Dear Cafe,
>
> the new (8.*) call stack feature
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
> is certainly nice for debugging during development.
>
> But how costly is it at runtime? I notice a 5 percent slowdown.

HasCallStack is really just a type class with a couple special rules for
building dictionaries in GHC's constraint solver. So at runtime each
function with a HasCallStack constraint takes an extra CallStack
argument. I don't know how to quantify the performance implications
beyond that, but you're right that HasCallStack is not free.

Hmm, is it no longer an implicit parameter?  I thought the new
HasCallStack stuff was just a constraint synonym like

type HasCallStack = (?callStack :: CallStack)

For the most part the distinction doesn't matter, but it does when it
comes to constraints in instance heads.  The issue is that constraints
are not solved at their callsites.  Do we want `HasCallStack` to be
able to be present in an instance head, and potentially get a location
quite different from the actual use site?  I would find this confusing,
though sometimes helpful.

I realized I could answer my own questions simply by trying it out on GHC 8 RC2!

    instance HasCallStack => Read Void where
      read _ = prettyStack callStack ++ "\nCan't possibly read Void!" 

does indeed produce the error

    • Illegal implicit parameter ‘?callStack::CallStack’
    • In the context: HasCallStack
      While checking an instance declaration
      In the instance declaration for ‘Read Void’

Implicit parameters are a good match for this callstack stuff, because
they have the restriction that they cannot be used as supeclass
constraints.

Does your work on the callstack stuff (thanks!) introduce backwards
compatibility issues?  I'm working on a library that will make extensive
use of 7.10's implementation of callstacks.

I tried it out, and it does have compatibility with the implicit parameter
still!  Good stuff.

However, I was making use of a potentially unintentional aspect
of the 7.10 API, which is that `getCallStack` can be used in a record
update of `CallStack`.  This is useful for annotating callstacks with
helpful debugging info, like the arguments used for an invocation.
The existing format for the message works quite well for this, since
it's just the function name.  So, appending parameters results in
"fn a1 a2 a3", etc.

Is it too late in the GHC release cycle to have add an additional
GHC.Stack.Internal module which enables modification of CallStack?

I realize that it might be quite intentional that CallStack can't be modified,
as that could make it less useful as a source of trustable diagnostic info.
How about only exposing access to the top:

getCallStackHeadMessage :: CallStack -> Maybe String
setCallStackHeadMessage :: String -> CallStack
modifyCallStackHeadMessage :: (String -> String) -> CallStack -> CallStack

Or, if we're feeling lensy

callStackHeadMessage :: Functor f => (String -> f String) -> (CallStack -> f CallStack)

Since only the top can be modified, we can trust that the entries of
the CallStack are accurate.

 
> That's not a problem if there's an easy way to switch
> this off for production (without changing the code).

Since HasCallStack is not a feature of the RTS, but actually part of the
generated code, there's not really an easy way to disable it without
changing the code. As much as I dislike CPP, I think it's the best
solution for a toggleable HasCallStack, something like

#if DEBUG
#define HASCALLSTACK (HasCallStack)
#else
#define HASCALLSTACK ()
#endif

foo :: HASCALLSTACK => a -> b

ought to work.

> Related: how to make code that uses it,
> compile with older ghcs that don't have it.
>
> I made this hack: do not import GHC.Stack.Types, but instead
>
> {-# language CPP, MultiParamTypeClasses #-}
>
> #if (__GLASGOW_HASKELL__ < 710)
> {-# language NullaryTypeClasses #-}
> #endif
>
> module Stack
> ( HasCallStack )
> where
>
> #if (__GLASGOW_HASKELL__ >= 800)
> import GHC.Stack.Types
> #else
> class HasCallStack
> instance HasCallStack
> #endif

This might be a nice addition to the base-compat package.

> When I compile with 8.rc2, and change ">= 800" to ">= 900",
> I am getting the 5 percent speedup mentioned above.
>
> But does it really do what I hope it does
> (remove all runtime overhead that call stacks may have)?

It should remove all the overhead of call stacks for calling functions
you wrote. If you import a function with a HasCallStack constraint
there's no way to disable the overhead for that function (for good
reason, it might use the CallStack!).

> When I compile with 7.10.3, I am getting 5 .. 10 percent faster again.
>
> My code does nothing fancy (w.r.t. types and libraries),
> it just uses Data.IntMap heavily. And it has some
>
> class Semiring s where
>   zero :: s
>   one  :: s
>   plus :: HasCallStack => s -> s -> s
>   times :: HasCallStack => s -> s -> s

I'm curious, why do plus and times take a CallStack? I wouldn't expect
them to be partial, so it seems like unnecessary overhead.

Eric
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe



_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Eric Seidel-3
In reply to this post by Johannes Waldmann-2
On Fri, Mar 4, 2016, at 09:18, Johannes Waldmann wrote:
> > If you import a function with a HasCallStack constraint
> > there's no way to disable the overhead
>
> But then this means that each library
> should avoid these constraints?
>
> Or not, because this rightly scares away users
> from calling nontotal functions?

I think it's a matter of judgment. For non-recursive functions like head
and tail, the overhead is probably not large enough to be noticeable.
With recursive functions, on the other hand, the overhead compounds
itself as you'll get a new callstack entry for each recursive call. So
if you really want your recursive function to take a CallStack, you
might refactor it so the recursion is handled by a helper function that
doesn't take a CallStack.

> > ... with a couple special rules for
> > building dictionaries in GHC's constraint solver.
>
> then activation of these special rules could be a compiler switch?
> Though it'd probably create a mess with separate compilation/linking.
> But switching via CPP will do the same.

Yes this would have (roughly) the same effect as my CPP suggestion, the
problem being that you'd have to recompile every module that used
HasCallStack.
 
Eric
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Eric Seidel-3
In reply to this post by Michael Sloan
On Sat, Mar 5, 2016, at 03:46, Michael Sloan wrote:
> I tried it out, and it does have compatibility with the implicit
> parameter
> still!  Good stuff.

It is still an implicit parameter under the hood, but we're trying to
hide it because we don't really care about the name of the implicit
parameter. In a future release we may decide to replace the implicit
parameter with a custom extension to the constraint solver, so I would
avoid using the implicit parameter :)

> However, I was making use of a potentially unintentional aspect
> of the 7.10 API, which is that `getCallStack` can be used in a record
> update of `CallStack`.  This is useful for annotating callstacks with
> helpful debugging info, like the arguments used for an invocation.
> The existing format for the message works quite well for this, since
> it's just the function name.  So, appending parameters results in
> "fn a1 a2 a3", etc.
>
> Is it too late in the GHC release cycle to have add an additional
> GHC.Stack.Internal module which enables modification of CallStack?

This is probably the main source of backwards-incompatiblity. CallStack
is no longer a simple wrapper around a [(String,SrcLoc)], so the record
update trick can't be done. Instead you should be able to extract the
list as before with getCallStack, tweak it to your liking, and then make
a new CallStack with GHC.Stack.fromCallSiteList (I'm not sure if that
went in before RC2 but it is in the branch).

The other backwards-incompatibility that springs to mind is that
show{CallStack,SrcLoc} were renamed to pretty{CallStack,SrcLoc}.

> Does your work on the callstack stuff (thanks!) introduce backwards
> compatibility issues?  I'm working on a library that will make extensive
> use of 7.10's implementation of callstacks.

Cool! Just be aware that 7.10's implementation had a nasty bug that
broke the chain between a CallStack in the context and a use of the
CallStack inside a let-binder.

Eric
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Johannes Waldmann-2
In reply to this post by Johannes Waldmann-2
Eric wrote earlier:

> If you import a function with a HasCallStack constraint
> there's no way to disable the overhead ...

> For non-recursive functions like head and tail,
> the overhead is probably not large enough to be noticeable.

Now I'm confused: should this mean that head and tail
are "functions with a HasCallStack constraint"?
If I ask ghci for their type, it just says head :: [a] -> a
and that's also what's in the source
https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/libraries/base-4.9.0.0/src/GHC-List.html#head
but the Callstack explanation in the user manual
does show a different version of head
https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack

I am not particularly concerned about head and tail here
but generally about how to find out whether my code
is taking a performance penalty.

I think the documentation (user manual Section 9.31)
is rather brief about the semantics. It speaks about
solving "HasCallStack" constraints - but they could also
be propagated? There is secondary documentation like
https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack
but it does not tell what is actually implemented,
or I don't see it.

- J.W.

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Jonas Scholl
I think the documentation for HasCallStack is not quite correct. As far
as I understood, head and tail are not changed to contain call stacks as
this would indeed carry a performance-penalty. Maybe the documentation
should call the function myHead to distinguish it from the library
function. The user-manual also uses errorWithCallStack, but I think base
calls this function just error and adds errorWithoutStackTrace (at least
thats what I find in
https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/libraries/base-4.9.0.0/src/GHC-Err.html).


On 03/07/2016 06:21 PM, Johannes Waldmann wrote:

> Eric wrote earlier:
>
>> If you import a function with a HasCallStack constraint
>> there's no way to disable the overhead ...
>
>> For non-recursive functions like head and tail,
>> the overhead is probably not large enough to be noticeable.
>
> Now I'm confused: should this mean that head and tail
> are "functions with a HasCallStack constraint"?
> If I ask ghci for their type, it just says head :: [a] -> a
> and that's also what's in the source
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/libraries/base-4.9.0.0/src/GHC-List.html#head
> but the Callstack explanation in the user manual
> does show a different version of head
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
>
> I am not particularly concerned about head and tail here
> but generally about how to find out whether my code
> is taking a performance penalty.
>
> I think the documentation (user manual Section 9.31)
> is rather brief about the semantics. It speaks about
> solving "HasCallStack" constraints - but they could also
> be propagated? There is secondary documentation like
> https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack
> but it does not tell what is actually implemented,
> or I don't see it.
>
> - J.W.
>
> _______________________________________________
> Haskell-Cafe mailing list
> [hidden email]
> http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe
>


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe

signature.asc (484 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: HasCallStack - runtime costs?

Eric Seidel-3
In reply to this post by Johannes Waldmann-2


On Mon, Mar 7, 2016, at 09:21, Johannes Waldmann wrote:

> Eric wrote earlier:
>
> > If you import a function with a HasCallStack constraint
> > there's no way to disable the overhead ...
>
> > For non-recursive functions like head and tail,
> > the overhead is probably not large enough to be noticeable.
>
> Now I'm confused: should this mean that head and tail
> are "functions with a HasCallStack constraint"?
> If I ask ghci for their type, it just says head :: [a] -> a
> and that's also what's in the source
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/libraries/base-4.9.0.0/src/GHC-List.html#head
> but the Callstack explanation in the user manual
> does show a different version of head
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
>
> I am not particularly concerned about head and tail here
> but generally about how to find out whether my code
> is taking a performance penalty.
>
> I think the documentation (user manual Section 9.31)
> is rather brief about the semantics. It speaks about
> solving "HasCallStack" constraints - but they could also
> be propagated? There is secondary documentation like
> https://ghc.haskell.org/trac/ghc/wiki/ExplicitCallStack
> but it does not tell what is actually implemented,
> or I don't see it.

I'm sorry for the confusion, to be clear a function only gets a
call-stack if it has a HasCallStack constraint in its type. I used head
as an example of a function that you might want to give a call-stack,
but base *does not* do this at the moment.

GHC will infer HasCallStack constraints for functions as it would infer
class constraints, but I have a mostly-done patch to disable this
inference for top-level functions, it should make it in to the final 8.0
release. I'll try to expand the documentation as well, perhaps some more
examples would help?

Thanks,
Eric
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe