Skip to content

Commit 0c09984

Browse files
committed
streams: Don't use malloc for buffers for small writes
1 parent ca548bc commit 0c09984

File tree

2 files changed

+91
-38
lines changed

2 files changed

+91
-38
lines changed

tests/test_tcp.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,16 @@ async def run():
526526

527527
DATA = b'x' * 102400
528528

529+
# Test _StreamWriteContext with short sequences of writes
530+
w.write(DATA)
531+
await w.drain()
532+
for _ in range(3):
533+
w.write(DATA)
534+
await w.drain()
535+
for _ in range(10):
536+
w.write(DATA)
537+
await w.drain()
538+
529539
for _ in range(N):
530540
w.write(DATA)
531541

@@ -545,7 +555,7 @@ async def run():
545555
srv.close()
546556
await srv.wait_closed()
547557

548-
self.assertEqual(TOTAL, N * 2 * len(DATA))
558+
self.assertEqual(TOTAL, N * 2 * len(DATA) + 14 * len(DATA))
549559

550560
self.loop.run_until_complete(run())
551561

uvloop/handles/stream.pyx

Lines changed: 80 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
DEF __PREALLOCED_BUFS = 4
2+
13
@cython.no_gc_clear
24
@cython.freelist(DEFAULT_FREELIST_SIZE)
35
cdef class _StreamWriteContext:
@@ -8,6 +10,10 @@ cdef class _StreamWriteContext:
810

911
list buffers
1012

13+
uv.uv_buf_t uv_bufs_sml[__PREALLOCED_BUFS]
14+
Py_buffer py_bufs_sml[__PREALLOCED_BUFS]
15+
bint py_bufs_sml_inuse
16+
1117
uv.uv_buf_t* uv_bufs
1218
Py_buffer* py_bufs
1319
ssize_t py_bufs_len
@@ -22,6 +28,11 @@ cdef class _StreamWriteContext:
2228

2329
self.closed = 1
2430

31+
if self.py_bufs_sml_inuse:
32+
for i in range(self.py_bufs_len):
33+
PyBuffer_Release(&self.py_bufs_sml[i])
34+
self.py_bufs_sml_inuse = 0
35+
2536
if self.uv_bufs is not NULL:
2637
PyMem_Free(self.uv_bufs)
2738
self.uv_bufs = NULL
@@ -41,67 +52,92 @@ cdef class _StreamWriteContext:
4152
cdef _StreamWriteContext new(UVStream stream, list buffers):
4253
cdef:
4354
_StreamWriteContext ctx
44-
int py_bufs_idx = 0
4555
int uv_bufs_idx = 0
56+
int py_bufs_len = 0
57+
58+
Py_buffer* p_pybufs
59+
uv.uv_buf_t* p_uvbufs
4660

4761
ctx = _StreamWriteContext.__new__(_StreamWriteContext)
4862
ctx.stream = None
4963
ctx.closed = 1
5064
ctx.py_bufs_len = 0
65+
ctx.py_bufs_sml_inuse = 0
5166
ctx.uv_bufs = NULL
5267
ctx.py_bufs = NULL
5368
ctx.buffers = buffers
5469
ctx.stream = stream
5570

56-
for buf in buffers:
57-
if not PyBytes_CheckExact(buf) and \
58-
not PyByteArray_CheckExact(buf):
59-
ctx.py_bufs_len += 1
60-
61-
if ctx.py_bufs_len > 0:
62-
ctx.py_bufs = <Py_buffer*>PyMem_Malloc(
63-
ctx.py_bufs_len * sizeof(Py_buffer))
64-
if ctx.py_bufs is NULL:
71+
if len(buffers) <= __PREALLOCED_BUFS:
72+
# We've got a small number of buffers to write, don't
73+
# need to use malloc.
74+
ctx.py_bufs_sml_inuse = 1
75+
p_pybufs = <Py_buffer*>&ctx.py_bufs_sml
76+
p_uvbufs = <uv.uv_buf_t*>&ctx.uv_bufs_sml
77+
78+
else:
79+
for buf in buffers:
80+
if not PyBytes_CheckExact(buf) and \
81+
not PyByteArray_CheckExact(buf):
82+
py_bufs_len += 1
83+
84+
if py_bufs_len > 0:
85+
ctx.py_bufs = <Py_buffer*>PyMem_Malloc(
86+
py_bufs_len * sizeof(Py_buffer))
87+
if ctx.py_bufs is NULL:
88+
raise MemoryError()
89+
90+
ctx.uv_bufs = <uv.uv_buf_t*>PyMem_Malloc(
91+
len(buffers) * sizeof(uv.uv_buf_t))
92+
if ctx.uv_bufs is NULL:
6593
raise MemoryError()
6694

67-
ctx.uv_bufs = <uv.uv_buf_t*>PyMem_Malloc(
68-
len(buffers) * sizeof(uv.uv_buf_t))
69-
if ctx.uv_bufs is NULL:
70-
raise MemoryError()
95+
p_pybufs = ctx.py_bufs
96+
p_uvbufs = ctx.uv_bufs
7197

98+
py_bufs_len = 0
7299
for buf in buffers:
73100
if PyBytes_CheckExact(buf):
74-
ctx.uv_bufs[uv_bufs_idx].base = PyBytes_AS_STRING(buf)
75-
ctx.uv_bufs[uv_bufs_idx].len = Py_SIZE(buf)
101+
p_uvbufs[uv_bufs_idx].base = PyBytes_AS_STRING(buf)
102+
p_uvbufs[uv_bufs_idx].len = Py_SIZE(buf)
76103

77104
elif PyByteArray_CheckExact(buf):
78-
ctx.uv_bufs[uv_bufs_idx].base = PyByteArray_AS_STRING(buf)
79-
ctx.uv_bufs[uv_bufs_idx].len = Py_SIZE(buf)
105+
p_uvbufs[uv_bufs_idx].base = PyByteArray_AS_STRING(buf)
106+
p_uvbufs[uv_bufs_idx].len = Py_SIZE(buf)
80107

81108
else:
82109
try:
83110
PyObject_GetBuffer(
84-
buf, &ctx.py_bufs[py_bufs_idx], PyBUF_SIMPLE)
111+
buf, &p_pybufs[py_bufs_len], PyBUF_SIMPLE)
85112
except:
86-
PyMem_Free(ctx.uv_bufs)
87-
ctx.uv_bufs = NULL
88-
for i in range(py_bufs_idx):
89-
PyBuffer_Release(&ctx.py_bufs[i])
90-
PyMem_Free(ctx.py_bufs)
91-
ctx.py_bufs = NULL
92-
ctx.py_bufs_len = 0
93-
raise
113+
# This shouldn't ever happen, since UVStream._write
114+
# casts non-bytes and non-bytearrays to memoryviews.
94115

95-
ctx.uv_bufs[uv_bufs_idx].base = \
96-
<char*>ctx.py_bufs[py_bufs_idx].buf
116+
if ctx.py_bufs_sml_inuse:
117+
for i in range(py_bufs_len):
118+
PyBuffer_Release(&ctx.py_bufs_sml[i])
119+
ctx.py_bufs_sml_inuse = 0
97120

98-
ctx.uv_bufs[uv_bufs_idx].len = \
99-
ctx.py_bufs[py_bufs_idx].len
121+
if ctx.uv_bufs is not NULL:
122+
PyMem_Free(ctx.uv_bufs)
123+
ctx.uv_bufs = NULL
100124

101-
py_bufs_idx += 1
125+
if ctx.py_bufs is not NULL:
126+
for i in range(py_bufs_len):
127+
PyBuffer_Release(&ctx.py_bufs[i])
128+
PyMem_Free(ctx.py_bufs)
129+
ctx.py_bufs = NULL
130+
131+
raise
132+
133+
p_uvbufs[uv_bufs_idx].base = <char*>p_pybufs[py_bufs_len].buf
134+
p_uvbufs[uv_bufs_idx].len = p_pybufs[py_bufs_len].len
135+
136+
py_bufs_len += 1
102137

103138
uv_bufs_idx += 1
104139

140+
ctx.py_bufs_len = py_bufs_len
105141
ctx.req.data = <void*> ctx
106142

107143
IF DEBUG:
@@ -339,11 +375,18 @@ cdef class UVStream(UVBaseTransport):
339375

340376
ctx = _StreamWriteContext.new(self, self._buffer)
341377

342-
err = uv.uv_write(&ctx.req,
343-
<uv.uv_stream_t*>self._handle,
344-
ctx.uv_bufs,
345-
len(self._buffer),
346-
__uv_stream_on_write)
378+
if ctx.py_bufs_sml_inuse:
379+
err = uv.uv_write(&ctx.req,
380+
<uv.uv_stream_t*>self._handle,
381+
ctx.uv_bufs_sml,
382+
len(self._buffer),
383+
__uv_stream_on_write)
384+
else:
385+
err = uv.uv_write(&ctx.req,
386+
<uv.uv_stream_t*>self._handle,
387+
ctx.uv_bufs,
388+
len(self._buffer),
389+
__uv_stream_on_write)
347390

348391
self._buffer_size = 0
349392
self._buffer = []

0 commit comments

Comments
 (0)