<div dir="ltr"><div dir="ltr">Hi Phil, <div><br></div><div>We started testing on the updated snapshot from this morning, we're seeing improvements but need to alter our own benchmark code to properly capture the benefits, so I'll be sending an update in a few days hopefully.</div><div><br></div><div>One thing to note is that we did come across a major performance regression on accident (think 80% performance hit) with QPainter.drawPolyline(QPolygonF).  While pyqtgraph does not use this method, PythonQwt does make extensive use of it and that library would likely be significantly impacted by this penalty (which is why I added Pierra to this email).</div><div><br></div><div>An example of our use case</div><div><br></div><div># create data</div><div>x = np. arrange(100)</div><div>y = np.zeros(100)</div><div><br></div><div># create qpolygonf instance</div><div>size = x.size</div><div>polyline = QtGui.QPolygonF()</div><div>polyline.fill(QtCore.QPointF(), size)</div><div><br></div><div># get buffer and fill it with original data</div><div>nbytes = 2 * len(polyline) * 8</div><div>buffer = polyline.data()</div><div>buffer.setsize(nbytes)</div><div>memory = np.frombuffer(buffer, np.double).reshape((-1, 2))</div><div>memory[:, 0] = x</div><div>memory[:, 1] = y</div><div><br></div><div># draw it</div><div>painter.drawPolyline(polyline)</div><div><br></div><div> If you would like a more complete example you can run let me know.</div><div><br></div><div>Ogi</div></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Jun 2, 2022 at 8:54 AM Phil Thompson <<a href="mailto:phil@riverbankcomputing.com">phil@riverbankcomputing.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">Whoops - should be fixed in the next snapshot.<br>
<br>
I'm seeing about a 70% speedup.<br>
<br>
Thanks,<br>
Phil<br>
<br>
On 01/06/2022 04:50, Ognyan Moore wrote:<br>
> Hi Phil,<br>
> <br>
> In sip_core.c line 4883 there is a call to sip_array_can_convert()  <br>
> which<br>
> appears to return False regardless if we are passing sip.array objects <br>
> or<br>
> normal python lists:<br>
> <br>
> Here's the code we inserted:<br>
> <br>
> if (arg != NULL)<br>
> {<br>
>     printf("sip_array_can_convert %d\n", sip_array_can_convert(arg, <br>
> td));<br>
>     if (sip_array_can_convert(arg, td))<br>
>     {<br>
>         sip_array_convert(arg, array, nr_elem);<br>
>         *is_temp = FALSE;<br>
>     }<br>
>     else if (convertFromSequence(arg, td, array, nr_elem))<br>
> <br>
> Here is some example code that we used:<br>
> <br>
> from PyQt6 import QtCore, QtGui<br>
> import PyQt6.sip as sip<br>
> import numpy as np<br>
> <br>
> import itertools<br>
> from time import perf_counter<br>
> <br>
> class LineSegments:<br>
>     def __init__(self, use_sip_array):<br>
>         self.use_sip_array = hasattr(sip, 'array') and use_sip_array<br>
>         self.alloc(0)<br>
> <br>
>     def alloc(self, size):<br>
>         if self.use_sip_array:<br>
>             self.objs = sip.array(QtCore.QLineF, size)<br>
>             vp = sip.voidptr(self.objs, len(self.objs)*4*8)<br>
>             self.arr = np.frombuffer(vp, dtype=np.float64).reshape((-1, <br>
> 4))<br>
>         else:<br>
>             self.arr = np.empty((size, 4), dtype=np.float64)<br>
>             self.objs = list(map(sip.wrapinstance,<br>
>                 itertools.count(self.arr.ctypes.data, <br>
> self.arr.strides[0]),<br>
>                 itertools.repeat(QtCore.QLineF, self.arr.shape[0])))<br>
> <br>
>     def get(self, size):<br>
>         if size != self.arr.shape[0]:<br>
>             self.alloc(size)<br>
>         return self.objs, self.arr<br>
> <br>
> def generate_pattern(nsegs, size):<br>
>     rng = np.random.default_rng()<br>
>     x = rng.random(nsegs) * size<br>
>     y = rng.random(nsegs) * size<br>
>     arr = np.empty((nsegs, 4), dtype=np.float64)<br>
>     arr[:, 0] = x<br>
>     arr[:, 1] = y<br>
>     arr[:, 2] = x + 2<br>
>     arr[:, 3] = y + 2<br>
>     return arr<br>
> <br>
> nsegs = 10_000<br>
> size = 500<br>
> nframes = 100<br>
> pattern = generate_pattern(nsegs, size)<br>
> <br>
> def run(use_sip_array):<br>
>     # generate lines once<br>
>     segments = LineSegments(use_sip_array)<br>
>     lines, memory = segments.get(nsegs)<br>
>     memory[:] = pattern<br>
> <br>
>     # draw multiple frames using the same lines array<br>
>     t0 = perf_counter()<br>
>     for _ in range(nframes):<br>
>         qimg = QtGui.QImage(size, size, <br>
> QtGui.QImage.Format.Format_RGB32)<br>
>         qimg.fill(QtCore.Qt.GlobalColor.transparent)<br>
>         painter = QtGui.QPainter(qimg)<br>
>         painter.setPen(QtCore.Qt.GlobalColor.cyan)<br>
>         painter.drawLines(lines)<br>
>         painter.end()<br>
>     t1 = perf_counter()<br>
>     return t1 - t0<br>
> <br>
> for use_sip_array in [False, True]:<br>
>     duration = run(use_sip_array)<br>
>     fps = int(nframes / duration)<br>
>     print(f'{use_sip_array=} {duration=:.3f} {fps=}')<br>
> <br>
> On Sat, May 28, 2022 at 8:17 AM Ognyan Moore <<a href="mailto:ognyan.moore@gmail.com" target="_blank">ognyan.moore@gmail.com</a>> <br>
> wrote:<br>
> <br>
>> Right; will adjust our benchmarking and keep testing.  Thanks!<br>
>> <br>
>> On Sat, May 28, 2022 at 01:18 Phil Thompson <br>
>> <<a href="mailto:phil@riverbankcomputing.com" target="_blank">phil@riverbankcomputing.com</a>><br>
>> wrote:<br>
>> <br>
>>> On 28/05/2022 06:39, Ognyan Moore wrote:<br>
>>> > Hi Phil,<br>
>>> ><br>
>>> > We started doing some benchmarking using the following script:<br>
>>> ><br>
>>> > from PyQt6 import QtCore, QtGui<br>
>>> > import PyQt6.sip as sip<br>
>>> > import numpy as np<br>
>>> ><br>
>>> > import itertools<br>
>>> > from time import perf_counter<br>
>>> ><br>
>>> > class LineSegments:<br>
>>> >     def __init__(self, use_sip_array):<br>
>>> >         self.use_sip_array = hasattr(sip, 'array') and use_sip_array<br>
>>> >         self.alloc(0)<br>
>>> ><br>
>>> >     def alloc(self, size):<br>
>>> >         if self.use_sip_array:<br>
>>> >             self.objs = sip.array(QtCore.QLineF, size)<br>
>>> >             vp = sip.voidptr(self.objs, len(self.objs)*4*8)<br>
>>> >             self.arr = np.frombuffer(vp, dtype=np.float64).reshape((-1,<br>
>>> > 4))<br>
>>> >         else:<br>
>>> >             self.arr = np.empty((size, 4), dtype=np.float64)<br>
>>> >             self.objs = list(map(sip.wrapinstance,<br>
>>> >                 itertools.count(self.arr.ctypes.data,<br>
>>> > self.arr.strides[0]),<br>
>>> >                 itertools.repeat(QtCore.QLineF, self.arr.shape[0])))<br>
>>> ><br>
>>> >     def get(self, size):<br>
>>> >         if size != self.arr.shape[0]:<br>
>>> >             self.alloc(size)<br>
>>> >         return self.objs, self.arr<br>
>>> ><br>
>>> > def run(size, use_sip_array):<br>
>>> >     qimg = QtGui.QImage(640, 480, QtGui.QImage.Format.Format_RGB32)<br>
>>> >     qimg.fill(QtCore.Qt.GlobalColor.transparent)<br>
>>> >     segments = LineSegments(use_sip_array)<br>
>>> >     objs, arr = segments.get(size)<br>
>>> ><br>
>>> >     arr[:, 0] = 0<br>
>>> >     arr[:, 1] = 0<br>
>>> >     arr[:, 2] = qimg.width()<br>
>>> >     arr[:, 3] = qimg.height()<br>
>>> ><br>
>>> >     painter = QtGui.QPainter(qimg)<br>
>>> >     painter.setPen(QtCore.Qt.GlobalColor.cyan)<br>
>>> ><br>
>>> >     draw_t0 = perf_counter()<br>
>>> >     painter.drawLines(objs)<br>
>>> >     draw_t1 = perf_counter()<br>
>>> ><br>
>>> >     painter.end()<br>
>>> >     return draw_t1 - draw_t0<br>
>>> ><br>
>>> > size = int(1e6)<br>
>>> > for use_sip_array in [True, False]:<br>
>>> >     dt = run(size, use_sip_array)<br>
>>> >     print(f'{use_sip_array=} {dt:.3f}')<br>
>>> ><br>
>>> ><br>
>>> > Here we noticed that using sip.array was slightly faster than not on<br>
>>> > macOS,<br>
>>> > but on Windows it was slightly slower.  Then when we changed the<br>
>>> > endpoint<br>
>>> > of the line to (10, 10) instead of the width and height of the QImage<br>
>>> > as<br>
>>> > such:<br>
>>> ><br>
>>> > arr[:, 2] = 10<br>
>>> > arr[:, 3] = 10<br>
>>> ><br>
>>> > We started seeing significantly higher penalties for using sip.array<br>
>>> ><br>
>>> > On macOS:<br>
>>> ><br>
>>> > use_sip_array=True 0.513<br>
>>> > use_sip_array=False 0.171<br>
>>> ><br>
>>> ><br>
>>> > On Windows:<br>
>>> ><br>
>>> > use_sip_array=True 0.351<br>
>>> > use_sip_array=False 0.218<br>
>>> ><br>
>>> ><br>
>>> > Also for sanity sake, the output of pip list:<br>
>>> ><br>
>>> > Package    Version             Editable project location<br>
>>> > ---------- ------------------- ------------------------------<br>
>>> > numpy      1.22.4<br>
>>> > pip        22.1.1<br>
>>> > PyQt6      6.3.1.dev2205201737<br>
>>> > PyQt6-Qt6  6.3.0<br>
>>> > PyQt6-sip  13.4.0<br>
>>> > pyqtgraph  0.12.4.dev0         /Users/ogi/Developer/pyqtgraph<br>
>>> > setuptools 58.1.0<br>
>>> > wheel      0.37.1<br>
>>> ><br>
>>> > Let me know if there is another use case I should test.<br>
>>> <br>
>>> That's what I would expect with that use case. You are creating the<br>
>>> array each time you want to do a draw so there is no benefit. The<br>
>>> benefit is when you create the array once and draw many times. In a<br>
>>> previous email you said that approach was usable.<br>
>>> <br>
>>> Phil<br>
>>> <br>
>> <br>
</blockquote></div></div>