-
Notifications
You must be signed in to change notification settings - Fork 21
Conversation
-Specifically include tests for 2 arg map/map! -Reorganize module along 1 arg, 2 arg, n arg methods -Correct mistake (n args tests tested lifted map! twice, failed to test lifted map)
6478caa
to
3bc6fa2
Compare
Remove metaprogramming from previous regime (though introduce use of generated functions for n arg map) include compatibility for versions <= JuliaLang/julia@a2ce230
I wouldn't worry too much about 0.4 compatibility, keeping the existing code in a separate file is fine. Regarding lifting or not, that's a tough call. In practice, I guess most people will use the lifting behavior, but doing this by default would mean we break the Finally, I wonder about the difference between these two benchmarks: julia> @benchmark map(f, As...)
BenchmarkTools.Trial:
samples: 158
evals/sample: 1
time tolerance: 5.00%
memory tolerance: 1.00%
memory estimate: 7.63 mb
allocs estimate: 10
minimum time: 27.83 ms (0.00% GC)
median time: 30.86 ms (0.00% GC)
mean time: 31.65 ms (2.66% GC)
maximum time: 40.85 ms (6.13% GC)
julia> @benchmark map(f, Xs...; lift=true)
BenchmarkTools.Trial:
samples: 354
evals/sample: 1
time tolerance: 5.00%
memory tolerance: 1.00%
memory estimate: 8.58 mb
allocs estimate: 32
minimum time: 12.27 ms (0.00% GC)
median time: 13.62 ms (0.00% GC)
mean time: 14.18 ms (6.01% GC)
maximum time: 23.19 ms (10.96% GC) How it is that the |
w/r/t the allocations on non-lifted w/r/t beating Whatever the case, if you write using BenchmarkTools
n = 1_000_000
As = [ rand(n) for i in 1:5 ]
f(xs...) = prod(xs)
julia> @benchmark mymap(f, As...)
BenchmarkTools.Trial:
samples: 786
evals/sample: 1
time tolerance: 5.00%
memory tolerance: 1.00%
memory estimate: 7.63 mb
allocs estimate: 8
minimum time: 4.59 ms (0.00% GC)
median time: 5.35 ms (0.00% GC)
mean time: 6.36 ms (14.65% GC)
maximum time: 34.15 ms (0.00% GC)
julia> mymap(f, As...) == map(f, As...)
true This suggests that the current Base n-arg |
Not sure. But since it's an immutable, it should be possible to avoid the allocation at least in some scenarios. For example, by inlining the function which creates the As regards being faster than Base's |
1c53e04
to
3c9ae3b
Compare
fe04a2a
to
3a49e22
Compare
Removes metaprogramming-heavy 0.4 map regime, which seems to be responsible for strange intermittent Travis failures.
Current coverage is 80.24%@@ master #128 diff @@
==========================================
Files 13 14 +1
Lines 729 825 +96
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 595 662 +67
- Misses 134 163 +29
Partials 0 0
|
Specifications described in detail in #128
I'm going to merge this, since it passes on 0.4 and the |
There's a Slack group? |
A few of us chat on a private Slack channel. |
Ah, okay. Org owners only, I presume? |
This is an attempt to transition away from the metaprogramming-heavy approach to map we took for Julia v0.4. Admittedly, this does introduce the use of generated functions in n-arg
map
/map!
.Does anybody have any suggestions as to how best maintain compatibility with 0.4? What I have here seems kind of crude.There's a lot of code repetition due to the 1-arg, 2-arg and n-arg methods for
map
. The Basemap(f, A)
regime callscollect_similar(A, Generator(f, A))
, but I haven't found a way tocollect
over aGenerator{NullableArray, F}
without creating a lot of unnecessaryNullable
objects -- so, hooking into that interface isn't really an option. I'm not sure the best way to reduce the code footprint here.Here are the significant points for this PR:
map
over emptyNullableArray
and (ii)map over non-empty but all-null
NullableArray`.For both cases (i) and (ii) above, the call to
map
can be lifted or not. Ifmap
is not lifted, it falls back on the implementation in Base, which differs between 0.4 and 0.5 (and also between the 1-arg, 2-arg and n-arg implementations). On 0.5, we have the following four combinations of behaviors:map(f, X; lift=false)
, whereX
is empty. On 0.5, now that 0.5-style comprehensions JuliaLang/julia#16622 has been merged, this uses type inference (Base.return_types
) to select the type parameter for the returned emptyNullableArray{T}
. Note that, sincef
is not lifted,return_types
is called onf, (eltype(X),)
. If somewhere down the line a method specialized onNullable
arguments is missing, this will return an emptyNullableArray{Union{}}
:map(f, X; lift=true)
, whereX
is empty. This uses type inference (Base.return_types
) to select the type parameter for the returned emptyNullableArray
. Sincef
is lifted,return_types
is called onf, (inner_eltype(X),)
, whereinner_eltype(X::NullableArray{T}) = T
:map(f, X; lift=false)
whereX
is non-empty but all null. The returnedNullableArray{T}
does not depend on type inference --- it depends entirely on the behavior off
applied to emptyNullable{T}
objects.map(f, X; lift=true)
whereX
is non-empty but all null. This does use type inference to determine the return type of the returnedNullableArray
.Brief performance summary (on 0.5) (NOTE: revised as of 7/12/16):
It seems pretty clear that, as far as performance goes, we ought to transition towards lifting by default. I think this seems reasonable for most statistics/data science-related use cases. To do so, would we need to deprecate the current behavior for a release cycle?[7/12/16]: Lifting will probably yield best performance, but clearly something is sub-optimal with the (n-arg especially) non-liftedmap
implementation.@nalimilan @johnmyleswhite thoughts?