You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Pickle is significantly slower than a memory copy #7544
The answer is that ndarray.__reduce__ uses tostring() internally (making a copy) and then pickle.dumps makes an additional copy of any data is receives from __reduce__ (writing it into a io.BytesIO, of course).
Compare:
In [22]: %time _ = x.copy()
CPU times: user 286 ms, sys: 324 ms, total: 609 ms
Wall time: 609 ms
In [23]: %time _ = x.__reduce__()
CPU times: user 296 ms, sys: 320 ms, total: 616 ms
Wall time: 615 ms
In [24]: %time _ = pickle.dumps(x, protocol=-1)
CPU times: user 606 ms, sys: 682 ms, total: 1.29 s
Wall time: 1.29 s
It might be possible to do the pickling without an additional copy, but as far as I can tell based on the current design of pickle, that would require converting numpy arrays into bytes or another builtin Python type supported by pickle without a copy (you can't pickle memory views). Unfortunately, as @teoliphant explains, converting numpy arrays into strings without a copy isn't possible.
So I guess you could either try to get first class support for memoryview objects into pickle (maybe not a bad idea) or roll your own serialization format.
For those interested in this issue, would recommend taking a look at issue ( #11161 ). Basically there is a draft PEP that proposes leveraging the Python Buffer Protocol to perform zero-copy networking. There is also a backport package, pickle5, which implements this for recent Python 3 versions.
That is a memory bandwidth of 5GB/s (read + write)
Numpy's copy seems to be a bit faster clocking in at 5.8GB/s
In [1]: import numpy as np
In [2]: a = np.zeros(10*1024*1024*1024, 'byte')
In [3]: %timeit _ = a.copy()
3.44 s ± 9.64 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [4]: 10 / 3.44
Out[4]: 2.9069767441860463
This blows any single digit performance that we get with python out the water.
PS. I had to change the size of the benchmark to 6GB since my computer has 6 cores + hyperthreading. Maybe I could have done with 3GB to get that factor of 3 in the size of the benchmark.
note that your numpy benchmark probably spends most of its time pagefaulting new memory so it is not comparable with the other benchmark.
doing following with a preallocated destination will likely be much faster:
%timeit np.copyto(dst, src)
I reach 20GiB/s on my machine (using glibc 2.27 which uses avx nontemporal writes)
On which platform and C library are on? for copying numpy just uses that so the performance depends on the platform. On mine the threaded benchmark you linked reaches 27GiB max so numpy is pretty close.
unfortunately numpy does not cache large arrays (only small ones). gh-8783 is about using a special linux features to cache large arrays without using up all of the systems memory when its not needed. But it still needs some time spent on for review and refactoring.
I'm on an updated version of conda-forge, I think they use glibc 2.55, but I'm pretty sure they limit it to SSE2 (soon to be SSE3). Ubuntu 16.04 64 bit.
at first the 2.5GB/s (or 5GB/s) was an order of magnitude off. the 10GB/s or 20GB/s depending on how you count is much better already.
PS. if there a more appropriate place to follow up on this conversation, please let me know. separate issue / stack overflow. I don't know if this conversation derailed too much from the pickling question.
Activity
shoyer commentedon Apr 14, 2016
The answer is that
ndarray.__reduce__usestostring()internally (making a copy) and thenpickle.dumpsmakes an additional copy of any data is receives from__reduce__(writing it into aio.BytesIO, of course).Compare:
It might be possible to do the pickling without an additional copy, but as far as I can tell based on the current design of pickle, that would require converting numpy arrays into
bytesor another builtin Python type supported by pickle without a copy (you can't pickle memory views). Unfortunately, as @teoliphant explains, converting numpy arrays into strings without a copy isn't possible.So I guess you could either try to get first class support for memoryview objects into pickle (maybe not a bad idea) or roll your own serialization format.
jakirkham commentedon Aug 8, 2018
For those interested in this issue, would recommend taking a look at issue ( #11161 ). Basically there is a draft PEP that proposes leveraging the Python Buffer Protocol to perform zero-copy networking. There is also a backport package,
pickle5, which implements this for recent Python 3 versions.hmaarrfk commentedon Sep 5, 2018
Sorry to hijack.
As an other note, it would also be really cool to get more bandwidth in memory copy in python in general.
That is a memory bandwidth of 5GB/s (read + write)
Numpy's copy seems to be a bit faster clocking in at 5.8GB/s
Following what this article is saying:
http://codearcana.com/posts/2013/05/18/achieving-maximum-memory-bandwidth.html
and using their benchmark https://github.com/awreece/memory-bandwidth-demo
On my machine I was able to obtain the following numbers:
This blows any single digit performance that we get with python out the water.
PS. I had to change the size of the benchmark to 6GB since my computer has 6 cores + hyperthreading. Maybe I could have done with 3GB to get that factor of 3 in the size of the benchmark.
juliantaylor commentedon Sep 7, 2018
note that your numpy benchmark probably spends most of its time pagefaulting new memory so it is not comparable with the other benchmark.
doing following with a preallocated destination will likely be much faster:
I reach 20GiB/s on my machine (using glibc 2.27 which uses avx nontemporal writes)
hmaarrfk commentedon Sep 7, 2018
Thanks @juliantaylor!
I'm up to 10GB/s copy speed so 20GB/s bandwidth. Much more respectable! (Conda forge numpy, I'm not sure what version of glibc they use).
benchmarks make my head hurt :S.
When allocating and deallocating large arrays, is numpy/python smart enough to reuse memory so it doesn't keep page faulting?
juliantaylor commentedon Sep 7, 2018
On which platform and C library are on? for copying numpy just uses that so the performance depends on the platform. On mine the threaded benchmark you linked reaches 27GiB max so numpy is pretty close.
unfortunately numpy does not cache large arrays (only small ones). gh-8783 is about using a special linux features to cache large arrays without using up all of the systems memory when its not needed. But it still needs some time spent on for review and refactoring.
hmaarrfk commentedon Sep 7, 2018
I'm on an updated version of conda-forge, I think they use glibc 2.55, but I'm pretty sure they limit it to SSE2 (soon to be SSE3). Ubuntu 16.04 64 bit.
at first the 2.5GB/s (or 5GB/s) was an order of magnitude off. the 10GB/s or 20GB/s depending on how you count is much better already.
PS. if there a more appropriate place to follow up on this conversation, please let me know. separate issue / stack overflow. I don't know if this conversation derailed too much from the pickling question.
jakirkham commentedon Jan 16, 2019
Probably worth rechecking with protocol 5 now that pickle5 will be used if found on Python 3.6+ as of NumPy 1.16.0.
mattip commentedon May 7, 2019
Support for protocol 5 has been merged. Closing. If the copy benchmark analysis leads to another issue, please open a new one.