-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
append!(Int64[], [1.1, 0.1])
results in unwanted array growth
#15868
Comments
I believe this came up once before and this behavior was chosen because it's technically acceptable since the operation errored and this avoids and expensive try-catch, but I still think it would be better to at least try to make |
Ref #7671 |
In the Midori terminology this would be an unrecoverable error. As in #7671 I still don't think operations need to be atomic with respect to these. |
Could we at least reset the array to its original size before raising the error? I don't see any drawback in doing so, and it completely hides the bug. |
The previous discussion in #7642 and #7671 has This is a choice between safety and the speed of appending arrays of different types. Suggested solutions:
Off topic: Should probably consider #7798 (extending |
Right, except that we don't have unrecoverable errors. |
Doesn't @nalimilan's suggesting solve this issue without introducing any overhead? |
I don't see how to implement @nalimilan's suggestion without a |
Hmm. That's a fair point. If we could pass in custom error handlers, then it would be straightforward. But we can't do that currently. |
One question that still interests me is why Or imagine that |
Could this be handled by calling sizehint on the array first and then pushing individual elements? That way the array would only end up containing successfully converted and pushed elements. |
My bad. Unless we introduce a Pushing elements one-by-one is an interesting idea, but it probably incurs some performance hit too, and in the end the array is still modified (even if the result is much better). Only benchmarks will tell. |
Are there really that many situations where one might want to append an iterable that Of course, this would be a breaking change so would require waiting until 2.0. Edit: I guess the downside is that you lose the ability to |
As this is still open I'm curious to ask why the following doesn't solve the original problem: function append!(a, b)
oldlen = length(a)
resize!(a, length(a)+length(b)) #ensures enough space & contiguous memory
a, newb = split!(a, oldlen) #splits into two arrays
#a now is equivalent to the original a
newb::typeof(similar(b, eltype(a))) #-> newb is uninitialized
copyto!(newb, b) #errors on convert error
contiguousmerge!(a, newb) #fast path which atomically forgets newb and resizes a
return a
end Here we only need to guarantee that the gc won't delete Phrased differently: Why can't we allocate the targetarray in the right place in the first place? |
One option would be to do something like this: function append!(x, y)
if eltype(y) <: eltype(x)
unsafe_append!(x, y)
else
try_append!(x, y)
end
end where |
I like it! Would you make a PR? |
Yeah I can give it a go. |
First we add an optional API parameter for `sizehint!` that controls whether it is for `push!` (default) or `pushfirst!`. Secondly, we make the offset zero when using `sizehint!` to shrink an array from the end, or the trailing size zero when using it to shring from the beginning. Then we replace the prior implementations of `prepend!` and `append!` with ones that are safe even if the iterator changes length during the operation or if convert fails. The result of `prepend!` may be in an undefined order (because of the `reverse!` call) in the presence of concurrent modifications or errors, but at least all of the elements will be present and valid afterwards. Replaces and closes #49905 Replaces and closes #47391 Fixes #15868 Co-authored-by: Sukera <Seelengrab@users.noreply.github.com> Co-authored-by: MasonProtter <mason.protter@icloud.com>
First we add an optional API parameter for `sizehint!` that controls whether it is for `push!` (default) or `pushfirst!`. Secondly, we make the offset zero when using `sizehint!` to shrink an array from the end, or the trailing size zero when using it to shring from the beginning. Then we replace the prior implementations of `prepend!` and `append!` with ones that are safe even if the iterator changes length during the operation or if convert fails. The result of `prepend!` may be in an undefined order (because of the `reverse!` call) in the presence of concurrent modifications or errors, but at least all of the elements will be present and valid afterwards. Replaces and closes #49905 Replaces and closes #47391 Fixes #15868 Co-authored-by: Sukera <Seelengrab@users.noreply.github.com> Co-authored-by: MasonProtter <mason.protter@icloud.com>
First we add an optional API parameter for `sizehint!` that controls whether it is for `push!` (default) or `pushfirst!`. Secondly, we make the offset zero when using `sizehint!` to shrink an array from the end, or the trailing size zero when using it to shring from the beginning. Then we replace the prior implementations of `prepend!` and `append!` with ones that are more safe, even if the iterator changes length during the operation or if convert fails. The result of `prepend!` may be in an undefined order (because of the `reverse!` call) in the presence of concurrent modifications or errors, but at least all of the elements will be present and all entries will be valid afterwards. Replaces and closes #49905 Replaces and closes #47391 Fixes #15868 Co-authored-by: Sukera <Seelengrab@users.noreply.github.com> Co-authored-by: MasonProtter <mason.protter@icloud.com>
First we add an optional API parameter for `sizehint!` that controls whether it is for `push!` (default) or `pushfirst!`. Secondly, we make the offset zero when using `sizehint!` to shrink an array from the end, or the trailing size zero when using it to shring from the beginning. Then we replace the prior implementations of `prepend!` and `append!` with ones that are more safe, even if the iterator changes length during the operation or if convert fails. The result of `prepend!` may be in an undefined order (because of the `reverse!` call) in the presence of concurrent modifications or errors, but at least all of the elements will be present and all entries will be valid afterwards. Replaces and closes #49905 Replaces and closes #47391 Fixes #15868 Co-authored-by: Sukera <Seelengrab@users.noreply.github.com> Co-authored-by: MasonProtter <mason.protter@icloud.com>
First we add an optional API parameter for `sizehint!` that controls whether it is for `push!` (default) or `pushfirst!`. Secondly, we make the offset zero when using `sizehint!` to shrink an array from the end, or the trailing size zero when using it to shring from the beginning. Then we replace the prior implementations of `prepend!` and `append!` with ones that are safe even if the iterator changes length during the operation or if convert fails. The result of `prepend!` may be in an undefined order (because of the `reverse!` call) in the presence of concurrent modifications or errors, but at least all of the elements will be present and valid afterwards. Replaces and closes #49905 Replaces and closes #47391 Fixes #15868 Benchmarks show that repeated `push!` performance (with sizehint) is nearly equivalent to the old append performance: ``` julia> @benchmark append!(x, 1:1000) setup=x=Vector{Float64}(undef,0) BenchmarkTools.Trial: 10000 samples with 10 evaluations. Range (min … max): 1.027 μs … 72.871 μs ┊ GC (min … max): 0.00% … 94.57% Time (median): 1.465 μs ┊ GC (median): 0.00% Time (mean ± σ): 1.663 μs ± 1.832 μs ┊ GC (mean ± σ): 6.20% ± 5.67% ▂▃▅▆█▇▇▆▄▂▁ ▂▁▁▂▂▂▂▃▄▅▇█████████████▇▆▅▅▅▅▅▅▄▅▄▅▅▅▆▇███▆▅▄▃▃▂▂▂▂▂▂▂▂▂▂ ▄ 1.03 μs Histogram: frequency by time 2.31 μs < Memory estimate: 19.69 KiB, allocs estimate: 0. julia> @benchmark append!(x, 1:1000) setup=x=Vector{Int}(undef,0) BenchmarkTools.Trial: 10000 samples with 10 evaluations. Range (min … max): 851.900 ns … 76.757 μs ┊ GC (min … max): 0.00% … 91.59% Time (median): 1.181 μs ┊ GC (median): 0.00% Time (mean ± σ): 1.543 μs ± 1.972 μs ┊ GC (mean ± σ): 6.75% ± 5.75% ▆█▇▃ ▂▃██████▇▅▅▄▅▅▃▂▂▂▃▃▃▂▃▃▃▂▂▂▂▂▁▂▁▂▁▂▂▂▁▁▂▂▁▁▁▁▁▁▁▂▂▂▃▃▃▃▂▂▂▂ ▃ 852 ns Histogram: frequency by time 4.07 μs < Memory estimate: 19.69 KiB, allocs estimate: 0. ``` Co-authored-by: Sukera <Seelengrab@users.noreply.github.com> Co-authored-by: MasonProtter <mason.protter@icloud.com>
Consider the following code demonstrating the bug in
append!
Now, in this case the program terminates with an error, but if the append statement is within a try/catch block the growth of the array can go unnoticed.
I actually discovered this bug in testing code, where I was testing that some code involving an
append!
raised an appropriate error. As soon as I added this test, all other tests in my code started to fail miserably.The text was updated successfully, but these errors were encountered: