On Wednesday, 8 October 2014 at 11:25:25 UTC, Johannes Pfau wrote:
Am Tue, 07 Oct 2014 15:57:58 +0000
schrieb "Dmitry Olshansky" <[email protected]>:
Instead we need to observe patterns and label it automatically
until the non-trivial subset remains. So everybody, please
take time and identify simple patterns and post back your
ideas on solution(s).
I just had a look at all closure allocations and identified
these
patterns:
Awesome! This is exactly the kind of help I wanted.
1) Fixable by manually stack-allocating closure
A delegate is passed to some function which stores this
delegate and
therefore correctly doesn't mark the parameter as scope.
However,
the lifetime of the stored delegate is still limited to the
current
function (e.g. it's stored in a struct instance, but on the
stack).
Can be fixed by creating a static struct{T... members; void
doSomething(){access members}} instance on stack and passing
&stackvar.doSomething as delegate.
Hm... Probably we can create a template for this.
2) Using delegates to add state to ranges
----
return iota(dim).
filter!(i => ptr[i])().
map!(i => BitsSet!size_t(ptr[i], i * bitsPerSizeT))().
joiner();
----
This code adds state to ranges without declaring a new type:
the ptr
variable is not accessible and needs to be move into a
closure.
Declaring a custom range type is a solution, but not
straightforward: If the ptr field is moved into the range a
closure
is not necessary. But if the range is copied, it's address
changes
and the delegate passed to map is now invalid.
Indeed, such code is fine in "user-space" but have no place in
the library.
3) Functions taking delegates as generic parameters
receiveTimeout,receive,formattedWrite accept different types,
including delegates. The delegates can all be scope to avoid
the
allocation but is void foo(T)(scope T) a good idea? The
alternative
is probably making an overload for delegates with scope
attribute.
(The result is that all functions calling receiveTimeout,...
with a
delegate allocate a closure)
4) Solvable with manual memory management
Some specific functions can't be easily fixed, but the
delegates
they create have a well defined lifetime (for example spawn
creates
a delegate which is only needed at the startup of a new
thread, it's
never used again). These could be malloc+freed.
I think this and (2) can be solved if we come up with solid
support for RC-closures.
5) Design issue
These functions generally create a delegate using variables
passed
in as parameters. There's no way to avoid closures here.
Although
manual allocation is an possible, the lifetime is undefined
and can
only be managed by the GC.
6) Other
Two cases can be fixed by moving a buffer into a struct or
moving a
function out of a member function into it's surrounding
class.
Yeah, there are always outliers ;)
Also notable: 17 out of 35 cases are in std.net.curl. This is
because
curl heavily uses delegates and wrapper delegates.
Interesting... it must be due to cURL callback-based API.
All in all, std.net.curl is a constant source of complaints, it
may need some work to fix other issues anyway.