Skip to content

Commit 12d329b

Browse files
authored
do lazy reallocation after take!(iobuffer) (#48676)
closes #27741. closes #48651
1 parent 8e3e970 commit 12d329b

File tree

5 files changed

+58
-23
lines changed

5 files changed

+58
-23
lines changed

base/iobuffer.jl

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# Stateful string
66
mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO
77
data::T # T should support: getindex, setindex!, length, copyto!, and resize!
8+
reinit::Bool # if true, data needs to be re-allocated (after take!)
89
readable::Bool
910
writable::Bool
1011
seekable::Bool # if not seekable, implementation is free to destroy (compact) past read data
@@ -17,7 +18,7 @@ mutable struct GenericIOBuffer{T<:AbstractVector{UInt8}} <: IO
1718
function GenericIOBuffer{T}(data::T, readable::Bool, writable::Bool, seekable::Bool, append::Bool,
1819
maxsize::Integer) where T<:AbstractVector{UInt8}
1920
require_one_based_indexing(data)
20-
new(data,readable,writable,seekable,append,length(data),maxsize,1,-1)
21+
new(data,false,readable,writable,seekable,append,length(data),maxsize,1,-1)
2122
end
2223
end
2324
const IOBuffer = GenericIOBuffer{Vector{UInt8}}
@@ -137,8 +138,12 @@ PipeBuffer(data::Vector{UInt8}=UInt8[]; maxsize::Int = typemax(Int)) =
137138
GenericIOBuffer(data,true,true,false,true,maxsize)
138139
PipeBuffer(maxsize::Integer) = (x = PipeBuffer(StringVector(maxsize), maxsize = maxsize); x.size=0; x)
139140

141+
_similar_data(b::GenericIOBuffer, len::Int) = similar(b.data, len)
142+
_similar_data(b::IOBuffer, len::Int) = StringVector(len)
143+
140144
function copy(b::GenericIOBuffer)
141-
ret = typeof(b)(b.writable ? copy(b.data) : b.data,
145+
ret = typeof(b)(b.reinit ? _similar_data(b, 0) : b.writable ?
146+
copyto!(_similar_data(b, length(b.data)), b.data) : b.data,
142147
b.readable, b.writable, b.seekable, b.append, b.maxsize)
143148
ret.size = b.size
144149
ret.ptr = b.ptr
@@ -270,7 +275,10 @@ function truncate(io::GenericIOBuffer, n::Integer)
270275
io.seekable || throw(ArgumentError("truncate failed, IOBuffer is not seekable"))
271276
n < 0 && throw(ArgumentError("truncate failed, n bytes must be ≥ 0, got $n"))
272277
n > io.maxsize && throw(ArgumentError("truncate failed, $(n) bytes is exceeds IOBuffer maxsize $(io.maxsize)"))
273-
if n > length(io.data)
278+
if io.reinit
279+
io.data = _similar_data(io, n)
280+
io.reinit = false
281+
elseif n > length(io.data)
274282
resize!(io.data, n)
275283
end
276284
io.data[io.size+1:n] .= 0
@@ -325,9 +333,14 @@ end
325333
ensureroom_slowpath(io, nshort)
326334
end
327335
n = min((nshort % Int) + (io.append ? io.size : io.ptr-1), io.maxsize)
328-
l = length(io.data)
329-
if n > l
330-
_growend!(io.data, (n - l) % UInt)
336+
if io.reinit
337+
io.data = _similar_data(io, n)
338+
io.reinit = false
339+
else
340+
l = length(io.data)
341+
if n > l
342+
_growend!(io.data, (n - l) % UInt)
343+
end
331344
end
332345
return io
333346
end
@@ -390,18 +403,26 @@ end
390403
function take!(io::IOBuffer)
391404
ismarked(io) && unmark(io)
392405
if io.seekable
393-
data = io.data
394406
if io.writable
395-
maxsize = (io.maxsize == typemax(Int) ? 0 : min(length(io.data),io.maxsize))
396-
io.data = StringVector(maxsize)
407+
if io.reinit
408+
data = StringVector(0)
409+
else
410+
data = resize!(io.data, io.size)
411+
io.reinit = true
412+
end
397413
else
398-
data = copy(data)
414+
data = copyto!(StringVector(io.size), 1, io.data, 1, io.size)
399415
end
400-
resize!(data,io.size)
401416
else
402417
nbytes = bytesavailable(io)
403-
a = StringVector(nbytes)
404-
data = read!(io, a)
418+
if io.writable
419+
data = io.data
420+
io.reinit = true
421+
_deletebeg!(data, io.ptr-1)
422+
resize!(data, nbytes)
423+
else
424+
data = read!(io, StringVector(nbytes))
425+
end
405426
end
406427
if io.writable
407428
io.ptr = 1
@@ -410,6 +431,19 @@ function take!(io::IOBuffer)
410431
return data
411432
end
412433

434+
"""
435+
_unsafe_take!(io::IOBuffer)
436+
437+
This simply returns the raw resized `io.data`, with no checks to be
438+
sure that `io` is readable etcetera, and leaves `io` in an inconsistent
439+
state. This should only be used internally for performance-critical
440+
`String` routines that immediately discard `io` afterwards, and it
441+
*assumes* that `io` is writable and seekable.
442+
443+
It saves no allocations compared to `take!`, it just omits some checks.
444+
"""
445+
_unsafe_take!(io::IOBuffer) = resize!(io.data, io.size)
446+
413447
function write(to::IO, from::GenericIOBuffer)
414448
if to === from
415449
from.ptr = from.size + 1

base/strings/basic.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ function filter(f, s::AbstractString)
636636
for c in s
637637
f(c) && write(out, c)
638638
end
639-
String(take!(out))
639+
String(_unsafe_take!(out))
640640
end
641641

642642
## string first and last ##

base/strings/io.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function sprint(f::Function, args...; context=nothing, sizehint::Integer=0)
113113
else
114114
f(s, args...)
115115
end
116-
String(resize!(s.data, s.size))
116+
String(_unsafe_take!(s))
117117
end
118118

119119
function _str_sizehint(x)
@@ -147,7 +147,7 @@ function print_to_string(xs...)
147147
for x in xs
148148
print(s, x)
149149
end
150-
String(resize!(s.data, s.size))
150+
String(_unsafe_take!(s))
151151
end
152152

153153
function string_with_env(env, xs...)
@@ -164,7 +164,7 @@ function string_with_env(env, xs...)
164164
for x in xs
165165
print(env_io, x)
166166
end
167-
String(resize!(s.data, s.size))
167+
String(_unsafe_take!(s))
168168
end
169169

170170
"""

stdlib/REPL/src/LineEdit.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -761,10 +761,11 @@ function edit_splice!(s::BufferLike, r::Region=region(s), ins::String = ""; rigi
761761
elseif buf.mark >= B
762762
buf.mark += sizeof(ins) - B + A
763763
end
764+
ensureroom(buf, B) # handle !buf.reinit from take!
764765
ret = splice!(buf.data, A+1:B, codeunits(String(ins))) # position(), etc, are 0-indexed
765766
buf.size = buf.size + sizeof(ins) - B + A
766767
adjust_pos && seek(buf, position(buf) + sizeof(ins))
767-
return String(ret)
768+
return String(copy(ret))
768769
end
769770

770771
edit_splice!(s::MIState, ins::AbstractString) = edit_splice!(s, region(s), ins)
@@ -1281,7 +1282,7 @@ end
12811282
# compute the number of spaces from b till the next non-space on the right
12821283
# (which can also be "end of line" or "end of buffer")
12831284
function leadingspaces(buf::IOBuffer, b::Int)
1284-
ls = something(findnext(_notspace, buf.data, b+1), 0)-1
1285+
@views ls = something(findnext(_notspace, buf.data[1:buf.size], b+1), 0)-1
12851286
ls == -1 && (ls = buf.size)
12861287
ls -= b
12871288
return ls
@@ -2238,7 +2239,7 @@ end
22382239

22392240
function move_line_end(buf::IOBuffer)
22402241
eof(buf) && return
2241-
pos = findnext(isequal(UInt8('\n')), buf.data, position(buf)+1)
2242+
@views pos = findnext(isequal(UInt8('\n')), buf.data[1:buf.size], position(buf)+1)
22422243
if pos === nothing
22432244
move_input_end(buf)
22442245
return

stdlib/REPL/test/lineedit.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -306,21 +306,21 @@ seek(buf,0)
306306

307307
## edit_delete_prev_word ##
308308

309-
buf = IOBuffer("type X\n ")
309+
buf = IOBuffer(Vector{UInt8}("type X\n "), read=true, write=true)
310310
seekend(buf)
311311
@test !isempty(@inferred(LineEdit.edit_delete_prev_word(buf)))
312312
@test position(buf) == 5
313313
@test buf.size == 5
314314
@test content(buf) == "type "
315315

316-
buf = IOBuffer("4 +aaa+ x")
316+
buf = IOBuffer(Vector{UInt8}("4 +aaa+ x"), read=true, write=true)
317317
seek(buf,8)
318318
@test !isempty(LineEdit.edit_delete_prev_word(buf))
319319
@test position(buf) == 3
320320
@test buf.size == 4
321321
@test content(buf) == "4 +x"
322322

323-
buf = IOBuffer("x = func(arg1,arg2 , arg3)")
323+
buf = IOBuffer(Vector{UInt8}("x = func(arg1,arg2 , arg3)"), read=true, write=true)
324324
seekend(buf)
325325
LineEdit.char_move_word_left(buf)
326326
@test position(buf) == 21

0 commit comments

Comments
 (0)