www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Overloading Lazy Vs. Non-Lazy

reply dsimcha <dsimcha yahoo.com> writes:
An issue that's come up here several times before is that enforce()
effectively disables inlining of the function it's used in.  From reading some
disassemblies, the reason seems to be because of all the ASM code that's
required for lazy parameters.  I wonder if either of the following is feasible:

1.  When a function takes a lazy parameter, the compiler automatically
generates two versions under the hood:  One that actually takes a non-lazy
parameter and is used when the value is known at compile time and another that
works like current lazy functions.  The only problem here is that this might
create issues when using function pointers/delegates.

2.  Allow overloading of lazy and non-lazy functions, with the rule that the
lazy version gets called whenever the value must be computed at runtime and
the non-lazy version gets called if the value is statically known and thus
there's no evaluation to speak of.
Aug 11 2010
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
dsimcha:

 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.

This is a problem because many Phobos functions, even small ones, are sprinkled with enforce(). If the purpose of enforce() is just to stop the program/thread even in release mode, then it may be replaced by something light and simpler, that doesn't stop the inliner. Bye, bearophile
Aug 11 2010
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 11 Aug 2010 09:19:01 -0400, dsimcha <dsimcha yahoo.com> wrote:

 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.  From  
 reading some
 disassemblies, the reason seems to be because of all the ASM code that's
 required for lazy parameters.  I wonder if either of the following is  
 feasible:

 1.  When a function takes a lazy parameter, the compiler automatically
 generates two versions under the hood:  One that actually takes a  
 non-lazy
 parameter and is used when the value is known at compile time and  
 another that
 works like current lazy functions.  The only problem here is that this  
 might
 create issues when using function pointers/delegates.

 2.  Allow overloading of lazy and non-lazy functions, with the rule that  
 the
 lazy version gets called whenever the value must be computed at runtime  
 and
 the non-lazy version gets called if the value is statically known and  
 thus
 there's no evaluation to speak of.

Hold on, can't we have enforce and lenforce (lazy enforce)? From a simple grep, 99% of enforce instances are: enforce(condition) -or- enforce(condition, "compile-time-string") -or- enforce(condition, "compile-time-string" ~ type.stringof) There are a few ones which actually would be allocating strings at runtime, but seriously, can't we just modify those to lenforce and get inlining everywhere else? I think phobos would see both a huge speedup and a smaller footprint immediately if enforce did not take a lazy arg. -Steve
Aug 11 2010
parent Tomek =?UTF-8?B?U293acWEc2tp?= <just ask.me> writes:
Steven Schveighoffer napisał:

 Hold on, can't we have enforce and lenforce (lazy enforce)?
 
 From a simple grep, 99% of enforce instances are:
 
 enforce(condition)
 -or-
 enforce(condition, "compile-time-string")
 -or-
 enforce(condition, "compile-time-string" ~ type.stringof)
 
 There are a few ones which actually would be allocating strings at
 runtime, but seriously, can't we just modify those to lenforce and get
 inlining everywhere else?  I think phobos would see both a huge speedup
 and a smaller footprint immediately if enforce did not take a lazy arg.

I like this idea. But > 1 option makes you think and that's painful;) BTW, am I the only person surprised that in a language whose generic code to fit the need can transform like Optimus Prime himself you can't do lazy <-> non-lazy automagically? Tomek
Aug 11 2010
prev sibling next sibling parent reply Brad Roberts <braddr puremagic.com> writes:
On 8/11/2010 6:19 AM, dsimcha wrote:
 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.  From reading some
 disassemblies, the reason seems to be because of all the ASM code that's
 required for lazy parameters.  I wonder if either of the following is feasible:
 
 1.  When a function takes a lazy parameter, the compiler automatically
 generates two versions under the hood:  One that actually takes a non-lazy
 parameter and is used when the value is known at compile time and another that
 works like current lazy functions.  The only problem here is that this might
 create issues when using function pointers/delegates.
 
 2.  Allow overloading of lazy and non-lazy functions, with the rule that the
 lazy version gets called whenever the value must be computed at runtime and
 the non-lazy version gets called if the value is statically known and thus
 there's no evaluation to speak of.

It's the throw that blocks inlining right now. Lazy might also disable inlining too, I haven't looked for that case. Either way, that's purely a quality of implementation issue. I don't think it'd be a good idea to bend the library all that much to work around that kind of limitation. It's something that will get better as time permits. Later, Brad
Aug 11 2010
parent reply Don <nospam nospam.com> writes:
Steven Schveighoffer wrote:
 On Thu, 12 Aug 2010 02:00:00 -0400, Brad Roberts <braddr puremagic.com> 
 wrote:
 
 On 8/11/2010 6:19 AM, dsimcha wrote:
 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.  From 
 reading some
 disassemblies, the reason seems to be because of all the ASM code that's
 required for lazy parameters.  I wonder if either of the following is 
 feasible:

 1.  When a function takes a lazy parameter, the compiler automatically
 generates two versions under the hood:  One that actually takes a 
 non-lazy
 parameter and is used when the value is known at compile time and 
 another that
 works like current lazy functions.  The only problem here is that 
 this might
 create issues when using function pointers/delegates.

 2.  Allow overloading of lazy and non-lazy functions, with the rule 
 that the
 lazy version gets called whenever the value must be computed at 
 runtime and
 the non-lazy version gets called if the value is statically known and 
 thus
 there's no evaluation to speak of.

It's the throw that blocks inlining right now. Lazy might also disable inlining too, I haven't looked for that case. Either way, that's purely a quality of implementation issue. I don't think it'd be a good idea to bend the library all that much to work around that kind of limitation. It's something that will get better as time permits.

Well, there's something to be said about preventing the bloated generation of code that accompanies lazy.

Inlining a lazy function that only returns a compile-time constant could inline very nicely. The delegate would disappear completely.
 But throwing preventing inlining is a bad one, that should be fixed.
 
 -Steve

Aug 12 2010
next sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Don (nospam nospam.com)'s article
 Steven Schveighoffer wrote:
 On Thu, 12 Aug 2010 02:00:00 -0400, Brad Roberts <braddr puremagic.com>
 wrote:

 On 8/11/2010 6:19 AM, dsimcha wrote:
 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.  From
 reading some
 disassemblies, the reason seems to be because of all the ASM code that's
 required for lazy parameters.  I wonder if either of the following is
 feasible:

 1.  When a function takes a lazy parameter, the compiler automatically
 generates two versions under the hood:  One that actually takes a
 non-lazy
 parameter and is used when the value is known at compile time and
 another that
 works like current lazy functions.  The only problem here is that
 this might
 create issues when using function pointers/delegates.

 2.  Allow overloading of lazy and non-lazy functions, with the rule
 that the
 lazy version gets called whenever the value must be computed at
 runtime and
 the non-lazy version gets called if the value is statically known and
 thus
 there's no evaluation to speak of.

It's the throw that blocks inlining right now. Lazy might also disable inlining too, I haven't looked for that case. Either way, that's purely a quality of implementation issue. I don't think it'd be a good idea to bend the library all that much to work around that kind of limitation. It's something that will get better as time permits.

Well, there's something to be said about preventing the bloated generation of code that accompanies lazy.

inline very nicely. The delegate would disappear completely.

Just to clarify: enforce() calls a function called bailOut() if necessary that actually does the throwing. This of course is not inlined w.r.t. enforce(). However, the lazy parameter also prevents inlining of enforce() itself and generates a ton of code at the ASM level. Don is right, though. In principle, when a statically known delegate D (lazy is just syntactic sugar for delegates) that returns a statically known constant is passed to a statically known function F, D can be inlined w.r.t. F and F can be inlined w.r.t. its caller. The problem, though, is that F needs to be inlined first so that the compiler can optimize F w.r.t. the parameters passed to it from a specific caller. However, F won't look worth inlining until the compiler realizes that it can inline D w.r.t. F. Therefore, the compiler would need an inliner that uses something more sophisticated than a simple greedy algorithm to discover this optimization opportunity.
Aug 12 2010
prev sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
On Thursday, August 12, 2010 13:47:40 dsimcha wrote:
 Just to clarify:  enforce() calls a function called bailOut() if necessary
 that actually does the throwing.  This of course is not inlined w.r.t.
 enforce(). However, the lazy parameter also prevents inlining of enforce()
 itself and generates a ton of code at the ASM level.
 
 Don is right, though.  In principle, when a statically known delegate D
 (lazy is just syntactic sugar for delegates) that returns a statically
 known constant is passed to a statically known function F, D can be
 inlined w.r.t. F and F can be inlined w.r.t. its caller.
 
 The problem, though, is that F needs to be inlined first so that the
 compiler can optimize F w.r.t. the parameters passed to it from a specific
 caller.  However, F won't look worth inlining until the compiler realizes
 that it can inline D w.r.t. F.  Therefore, the compiler would need an
 inliner that uses something more sophisticated than a simple greedy
 algorithm to discover this optimization opportunity.

Well, from everything I've heard about the inliner, we're going to need a smarter one at some point. Whether we need one soon is another matter, but at some point, we really should have an inliner capable of handling these sort of cases. It sounds like dmd's inliner is pretty simplistic, and that's going to make it harder to make stuff like Phobos properly efficient. - Jonathan M Davis
Aug 12 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 12 Aug 2010 02:00:00 -0400, Brad Roberts <braddr puremagic.com>  
wrote:

 On 8/11/2010 6:19 AM, dsimcha wrote:
 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.  From  
 reading some
 disassemblies, the reason seems to be because of all the ASM code that's
 required for lazy parameters.  I wonder if either of the following is  
 feasible:

 1.  When a function takes a lazy parameter, the compiler automatically
 generates two versions under the hood:  One that actually takes a  
 non-lazy
 parameter and is used when the value is known at compile time and  
 another that
 works like current lazy functions.  The only problem here is that this  
 might
 create issues when using function pointers/delegates.

 2.  Allow overloading of lazy and non-lazy functions, with the rule  
 that the
 lazy version gets called whenever the value must be computed at runtime  
 and
 the non-lazy version gets called if the value is statically known and  
 thus
 there's no evaluation to speak of.

It's the throw that blocks inlining right now. Lazy might also disable inlining too, I haven't looked for that case. Either way, that's purely a quality of implementation issue. I don't think it'd be a good idea to bend the library all that much to work around that kind of limitation. It's something that will get better as time permits.

Well, there's something to be said about preventing the bloated generation of code that accompanies lazy. But throwing preventing inlining is a bad one, that should be fixed. -Steve
Aug 12 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 11 Aug 2010 18:10:53 -0400, Tomek Sowiński <just ask.me> wrote:

 Steven Schveighoffer napisał:

 Hold on, can't we have enforce and lenforce (lazy enforce)?

 From a simple grep, 99% of enforce instances are:

 enforce(condition)
 -or-
 enforce(condition, "compile-time-string")
 -or-
 enforce(condition, "compile-time-string" ~ type.stringof)

 There are a few ones which actually would be allocating strings at
 runtime, but seriously, can't we just modify those to lenforce and get
 inlining everywhere else?  I think phobos would see both a huge speedup
 and a smaller footprint immediately if enforce did not take a lazy arg.

I like this idea. But > 1 option makes you think and that's painful;)

One possibility is to have enforce force you to pass in a compile-time evaluatable string. I don't really know if this is possible. But then at least the compiler could prevent you from accidentally using the wrong enforce.
 BTW, am I the only person surprised that in a language whose generic  
 code to fit the need can transform like Optimus Prime himself you
 can't do lazy <-> non-lazy automagically?

It's been suggested before (by me even): http://d.puremagic.com/issues/show_bug.cgi?id=1817 But that would be limited to templates only. A non-template function which takes a lazy argument could not make any conversions. I'd rather see an overload, or just separate the names. -Steve
Aug 12 2010
prev sibling next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 12 Aug 2010 16:47:40 -0400, dsimcha <dsimcha yahoo.com> wrote:

 == Quote from Don (nospam nospam.com)'s article
 Steven Schveighoffer wrote:
 On Thu, 12 Aug 2010 02:00:00 -0400, Brad Roberts  

 wrote:

 On 8/11/2010 6:19 AM, dsimcha wrote:
 An issue that's come up here several times before is that enforce()
 effectively disables inlining of the function it's used in.  From
 reading some
 disassemblies, the reason seems to be because of all the ASM code  



 required for lazy parameters.  I wonder if either of the following  



 feasible:

 1.  When a function takes a lazy parameter, the compiler  



 generates two versions under the hood:  One that actually takes a
 non-lazy
 parameter and is used when the value is known at compile time and
 another that
 works like current lazy functions.  The only problem here is that
 this might
 create issues when using function pointers/delegates.

 2.  Allow overloading of lazy and non-lazy functions, with the rule
 that the
 lazy version gets called whenever the value must be computed at
 runtime and
 the non-lazy version gets called if the value is statically known  



 thus
 there's no evaluation to speak of.

It's the throw that blocks inlining right now. Lazy might also disable inlining too, I haven't looked for that case. Either way, that's purely a quality of implementation issue. I don't think it'd be a good idea to bend the library all that much to work around that kind of limitation. It's something


 will get
 better as time permits.

Well, there's something to be said about preventing the bloated generation of code that accompanies lazy.

inline very nicely. The delegate would disappear completely.

Just to clarify: enforce() calls a function called bailOut() if necessary that actually does the throwing. This of course is not inlined w.r.t. enforce(). However, the lazy parameter also prevents inlining of enforce() itself and generates a ton of code at the ASM level. Don is right, though. In principle, when a statically known delegate D (lazy is just syntactic sugar for delegates) that returns a statically known constant is passed to a statically known function F, D can be inlined w.r.t. F and F can be inlined w.r.t. its caller. The problem, though, is that F needs to be inlined first so that the compiler can optimize F w.r.t. the parameters passed to it from a specific caller. However, F won't look worth inlining until the compiler realizes that it can inline D w.r.t. F. Therefore, the compiler would need an inliner that uses something more sophisticated than a simple greedy algorithm to discover this optimization opportunity.

In principle, we should be able to inline enforce. Great. When is this going to happen? Well, we can inline all calls to enforce *today* if we simply remove the lazy attribute, and apply a lazy version in a small number of places. I think it's worth doing it, because the inlining fixes are not going to happen for a while. It's easy to switch it back once we can inline trivial lazy parameters. -Steve
Aug 13 2010
prev sibling parent "Lars T. Kyllingstad" <public kyllingen.NOSPAMnet> writes:
On Fri, 13 Aug 2010 09:21:00 -0400, Steven Schveighoffer wrote:

 On Thu, 12 Aug 2010 16:47:40 -0400, dsimcha <dsimcha yahoo.com> wrote:
 
 == Quote from Don (nospam nospam.com)'s article
 Steven Schveighoffer wrote:
 On Thu, 12 Aug 2010 02:00:00 -0400, Brad Roberts

 wrote:

 On 8/11/2010 6:19 AM, dsimcha wrote:
 An issue that's come up here several times before is that
 enforce() effectively disables inlining of the function it's used
 in.  From reading some
 disassemblies, the reason seems to be because of all the ASM code



 required for lazy parameters.  I wonder if either of the following



 feasible:

 1.  When a function takes a lazy parameter, the compiler



 generates two versions under the hood:  One that actually takes a
 non-lazy
 parameter and is used when the value is known at compile time and
 another that
 works like current lazy functions.  The only problem here is that
 this might
 create issues when using function pointers/delegates.

 2.  Allow overloading of lazy and non-lazy functions, with the
 rule that the
 lazy version gets called whenever the value must be computed at
 runtime and
 the non-lazy version gets called if the value is statically known



 thus
 there's no evaluation to speak of.

It's the throw that blocks inlining right now. Lazy might also disable inlining too, I haven't looked for that case. Either way, that's purely a quality of implementation issue. I don't think it'd be a good idea to bend the library all that much to work around that kind of limitation. It's something


 will get
 better as time permits.

Well, there's something to be said about preventing the bloated generation of code that accompanies lazy.

could inline very nicely. The delegate would disappear completely.

Just to clarify: enforce() calls a function called bailOut() if necessary that actually does the throwing. This of course is not inlined w.r.t. enforce(). However, the lazy parameter also prevents inlining of enforce() itself and generates a ton of code at the ASM level. Don is right, though. In principle, when a statically known delegate D (lazy is just syntactic sugar for delegates) that returns a statically known constant is passed to a statically known function F, D can be inlined w.r.t. F and F can be inlined w.r.t. its caller. The problem, though, is that F needs to be inlined first so that the compiler can optimize F w.r.t. the parameters passed to it from a specific caller. However, F won't look worth inlining until the compiler realizes that it can inline D w.r.t. F. Therefore, the compiler would need an inliner that uses something more sophisticated than a simple greedy algorithm to discover this optimization opportunity.

In principle, we should be able to inline enforce. Great. When is this going to happen? Well, we can inline all calls to enforce *today* if we simply remove the lazy attribute, and apply a lazy version in a small number of places. I think it's worth doing it, because the inlining fixes are not going to happen for a while. It's easy to switch it back once we can inline trivial lazy parameters. -Steve

I agree. A quick grep of the phobos sources shows that in the vast majority of cases, enforce() gets called either without a message or just with a string literal. -Lars
Aug 13 2010