py-sharedmemory 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 товіаѕ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.1
2
+ Name: py-sharedmemory
3
+ Version: 1.0.0
4
+ Summary: Shared Memory Pipe for Fast Multiprocessing Data Sharing of Large Objects (>1MB)
5
+ Author: Tobias Würth
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 товіаѕ
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/tobiaswuerth/python_shared_memory_queue
29
+ Project-URL: Bug Tracker, https://github.com/tobiaswuerth/python_shared_memory_queue/issues
30
+ Keywords: python,python3,multiprocessing,sharedmemory,queue,pipe
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Requires-Python: >=3.9
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: numpy>=1.26
38
+
39
+ # Shared Memory Pipe for Fast Multiprocessing Data Sharing of Large Objects (>1MB)
40
+
41
+ Since [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) [Queue](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue)s pickles the data, there is a certain performance decrease for larger data objects and huge amounts of I/O.
42
+
43
+ This is the reason why I created a substitute which instead converts data objects into bytes (if needed), puts the bytes into shared memory and only transfers meta-data about the data structure via Queues to the receiving end.
44
+
45
+ The following data types are currently supported:
46
+ ```
47
+ - bytes
48
+ - int
49
+ - float
50
+ - bool
51
+ - str
52
+ - np.ndarray
53
+ - np.dtype
54
+ ```
55
+
56
+ As well as any nested structures for the given types:
57
+ ```
58
+ - tuple/NamedTuple
59
+ - list
60
+ - dict
61
+ - set
62
+ ```
63
+
64
+ # Considerations
65
+ There is a certain overhead to the conversion process which is especially noticable for smaller objects and more complex data structures.
66
+ Use the following approach depending on the size of the data you're handling:
67
+
68
+ ||10B|100B|1KB|10KB|100KB|1MB|10MB|100MB|1GB|10GB|
69
+ |---|---|---|---|---|---|---|---|---|---|---|
70
+ |``mp.Queue()``|✅|✅|✅|✅|✅|❌|❌|❌|❌|❌|
71
+ |``SharedMemory``|❌|❌|❌|❌|❌|✅|✅|✅|✅|✅|
72
+
73
+ Actual performance heavily relies on your memory speed.
74
+
75
+ # Usage example
76
+ ```python
77
+ import multiprocessing as mp
78
+
79
+ from memory import create_shared_memory_pair, SharedMemorySender, SharedMemoryReceiver
80
+
81
+ def producer_sm(sender:SharedMemorySender):
82
+ your_data = "your data"
83
+
84
+ # ...
85
+ sender.put(your_data) # blocks until there is space
86
+
87
+ # or
88
+ if sender.has_space():
89
+ sender.put(your_data)
90
+ else:
91
+ # your else case
92
+
93
+ # ...
94
+ sender.wait_for_all_ack() # wait for all data to be received before closing the sender
95
+ # exit
96
+
97
+ def consumer_sm(receiver:SharedMemoryReceiver, loops, size):
98
+ # ...
99
+ data = receiver.get() # blocks
100
+
101
+ # or
102
+ data = receiver.get(timeout=3) # raises queue.Empty exception after 3s
103
+
104
+ # or
105
+ data = receiver.get_nowait()
106
+ if data is None:
107
+ # handle empty case
108
+
109
+ # ...
110
+
111
+ if __name__ == '__main__':
112
+ sender, receiver = create_shared_memory_pair(capacity=5)
113
+
114
+ mp.Process(target=producer_sm, args=(sender,)).start()
115
+ mp.Process(target=consumer_sm, args=(receiver,)).start()
116
+
117
+ ```
118
+
119
+ # Performance Testing
120
+ Note that in this testing example Producer and Consumer where dependent upon each other (due to Queue capacity), that's why they take a similar amount of time. Your actual performance may vary depending on the data type, structure and overall system performance.
121
+
122
+ Since there is no inter-process data transfer, I/O drops accordingly. E.g. in my case -2.4GB/s
123
+
124
+ ```
125
+ --------------------
126
+ Bytes: 100 (100 B), loops: 100000, queue capacity: 1000
127
+ Producer done in 0.78797s @ 12.10 MB/s
128
+ Consumer done in 0.78698s @ 12.12 MB/s
129
+ Producer done in SM 5.37207s @ 1.78 MB/s
130
+ Consumer done in SM 5.37207s @ 1.78 MB/s ❌
131
+ --------------------
132
+ Bytes: 1000 (1000 B), loops: 100000, queue capacity: 1000
133
+ Producer done in 0.92900s @ 102.66 MB/s
134
+ Consumer done in 0.91599s @ 104.11 MB/s
135
+ Producer done in SM 5.45683s @ 17.48 MB/s
136
+ Consumer done in SM 5.45582s @ 17.48 MB/s ❌
137
+ --------------------
138
+ Bytes: 10000 (9.77 KB), loops: 100000, queue capacity: 1000
139
+ Producer done in 2.16847s @ 439.79 MB/s
140
+ Consumer done in 2.16147s @ 441.22 MB/s
141
+ Producer done in SM 5.64625s @ 168.90 MB/s
142
+ Consumer done in SM 5.64625s @ 168.90 MB/s ❌
143
+ --------------------
144
+ Bytes: 100000 (97.66 KB), loops: 100000, queue capacity: 1000
145
+ Producer done in 2.80213s @ 3.32 GB/s
146
+ Consumer done in 2.80013s @ 3.33 GB/s
147
+ Producer done in SM 8.24400s @ 1.13 GB/s
148
+ Consumer done in SM 8.24200s @ 1.13 GB/s ❌
149
+ --------------------
150
+ Bytes: 1000000 (976.56 KB), loops: 10000, queue capacity: 1000
151
+ Producer done in 4.87300s @ 1.91 GB/s
152
+ Consumer done in 4.87300s @ 1.91 GB/s
153
+ Producer done in SM 4.01900s @ 2.32 GB/s
154
+ Consumer done in SM 4.01800s @ 2.32 GB/s ✅
155
+ --------------------
156
+ Bytes: 10000000 (9.54 MB), loops: 1000, queue capacity: 1000
157
+ Producer done in 6.25722s @ 1.49 GB/s
158
+ Consumer done in 6.28221s @ 1.48 GB/s
159
+ Producer done in SM 3.79851s @ 2.45 GB/s
160
+ Consumer done in SM 3.80359s @ 2.45 GB/s ✅
161
+ --------------------
162
+ Bytes: 100000000 (95.37 MB), loops: 1000, queue capacity: 100
163
+ Producer done in 64.91876s @ 1.43 GB/s
164
+ Consumer done in 65.20476s @ 1.43 GB/s
165
+ Producer done in SM 38.08093s @ 2.45 GB/s
166
+ Consumer done in SM 38.17893s @ 2.44 GB/s ✅
167
+ --------------------
168
+ Bytes: 1000000000 (953.67 MB), loops: 100, queue capacity: 10
169
+ Producer done in 63.22359s @ 1.47 GB/s
170
+ Consumer done in 66.07801s @ 1.41 GB/s
171
+ Producer done in SM 36.39488s @ 2.56 GB/s
172
+ Consumer done in SM 37.61108s @ 2.48 GB/s ✅
173
+ --------------------
174
+ Bytes: 10000000000 (9.31 GB), loops: 10, queue capacity: 10
175
+ Producer done in CRASHED
176
+ Consumer done in CRASHED
177
+ Producer done in SM 28.21499s @ 3.30 GB/s
178
+ Consumer done in SM 34.32684s @ 2.71 GB/s ✅✅
179
+ ```
@@ -0,0 +1,141 @@
1
+ # Shared Memory Pipe for Fast Multiprocessing Data Sharing of Large Objects (>1MB)
2
+
3
+ Since [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) [Queue](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue)s pickles the data, there is a certain performance decrease for larger data objects and huge amounts of I/O.
4
+
5
+ This is the reason why I created a substitute which instead converts data objects into bytes (if needed), puts the bytes into shared memory and only transfers meta-data about the data structure via Queues to the receiving end.
6
+
7
+ The following data types are currently supported:
8
+ ```
9
+ - bytes
10
+ - int
11
+ - float
12
+ - bool
13
+ - str
14
+ - np.ndarray
15
+ - np.dtype
16
+ ```
17
+
18
+ As well as any nested structures for the given types:
19
+ ```
20
+ - tuple/NamedTuple
21
+ - list
22
+ - dict
23
+ - set
24
+ ```
25
+
26
+ # Considerations
27
+ There is a certain overhead to the conversion process which is especially noticable for smaller objects and more complex data structures.
28
+ Use the following approach depending on the size of the data you're handling:
29
+
30
+ ||10B|100B|1KB|10KB|100KB|1MB|10MB|100MB|1GB|10GB|
31
+ |---|---|---|---|---|---|---|---|---|---|---|
32
+ |``mp.Queue()``|✅|✅|✅|✅|✅|❌|❌|❌|❌|❌|
33
+ |``SharedMemory``|❌|❌|❌|❌|❌|✅|✅|✅|✅|✅|
34
+
35
+ Actual performance heavily relies on your memory speed.
36
+
37
+ # Usage example
38
+ ```python
39
+ import multiprocessing as mp
40
+
41
+ from memory import create_shared_memory_pair, SharedMemorySender, SharedMemoryReceiver
42
+
43
+ def producer_sm(sender:SharedMemorySender):
44
+ your_data = "your data"
45
+
46
+ # ...
47
+ sender.put(your_data) # blocks until there is space
48
+
49
+ # or
50
+ if sender.has_space():
51
+ sender.put(your_data)
52
+ else:
53
+ # your else case
54
+
55
+ # ...
56
+ sender.wait_for_all_ack() # wait for all data to be received before closing the sender
57
+ # exit
58
+
59
+ def consumer_sm(receiver:SharedMemoryReceiver, loops, size):
60
+ # ...
61
+ data = receiver.get() # blocks
62
+
63
+ # or
64
+ data = receiver.get(timeout=3) # raises queue.Empty exception after 3s
65
+
66
+ # or
67
+ data = receiver.get_nowait()
68
+ if data is None:
69
+ # handle empty case
70
+
71
+ # ...
72
+
73
+ if __name__ == '__main__':
74
+ sender, receiver = create_shared_memory_pair(capacity=5)
75
+
76
+ mp.Process(target=producer_sm, args=(sender,)).start()
77
+ mp.Process(target=consumer_sm, args=(receiver,)).start()
78
+
79
+ ```
80
+
81
+ # Performance Testing
82
+ Note that in this testing example Producer and Consumer where dependent upon each other (due to Queue capacity), that's why they take a similar amount of time. Your actual performance may vary depending on the data type, structure and overall system performance.
83
+
84
+ Since there is no inter-process data transfer, I/O drops accordingly. E.g. in my case -2.4GB/s
85
+
86
+ ```
87
+ --------------------
88
+ Bytes: 100 (100 B), loops: 100000, queue capacity: 1000
89
+ Producer done in 0.78797s @ 12.10 MB/s
90
+ Consumer done in 0.78698s @ 12.12 MB/s
91
+ Producer done in SM 5.37207s @ 1.78 MB/s
92
+ Consumer done in SM 5.37207s @ 1.78 MB/s ❌
93
+ --------------------
94
+ Bytes: 1000 (1000 B), loops: 100000, queue capacity: 1000
95
+ Producer done in 0.92900s @ 102.66 MB/s
96
+ Consumer done in 0.91599s @ 104.11 MB/s
97
+ Producer done in SM 5.45683s @ 17.48 MB/s
98
+ Consumer done in SM 5.45582s @ 17.48 MB/s ❌
99
+ --------------------
100
+ Bytes: 10000 (9.77 KB), loops: 100000, queue capacity: 1000
101
+ Producer done in 2.16847s @ 439.79 MB/s
102
+ Consumer done in 2.16147s @ 441.22 MB/s
103
+ Producer done in SM 5.64625s @ 168.90 MB/s
104
+ Consumer done in SM 5.64625s @ 168.90 MB/s ❌
105
+ --------------------
106
+ Bytes: 100000 (97.66 KB), loops: 100000, queue capacity: 1000
107
+ Producer done in 2.80213s @ 3.32 GB/s
108
+ Consumer done in 2.80013s @ 3.33 GB/s
109
+ Producer done in SM 8.24400s @ 1.13 GB/s
110
+ Consumer done in SM 8.24200s @ 1.13 GB/s ❌
111
+ --------------------
112
+ Bytes: 1000000 (976.56 KB), loops: 10000, queue capacity: 1000
113
+ Producer done in 4.87300s @ 1.91 GB/s
114
+ Consumer done in 4.87300s @ 1.91 GB/s
115
+ Producer done in SM 4.01900s @ 2.32 GB/s
116
+ Consumer done in SM 4.01800s @ 2.32 GB/s ✅
117
+ --------------------
118
+ Bytes: 10000000 (9.54 MB), loops: 1000, queue capacity: 1000
119
+ Producer done in 6.25722s @ 1.49 GB/s
120
+ Consumer done in 6.28221s @ 1.48 GB/s
121
+ Producer done in SM 3.79851s @ 2.45 GB/s
122
+ Consumer done in SM 3.80359s @ 2.45 GB/s ✅
123
+ --------------------
124
+ Bytes: 100000000 (95.37 MB), loops: 1000, queue capacity: 100
125
+ Producer done in 64.91876s @ 1.43 GB/s
126
+ Consumer done in 65.20476s @ 1.43 GB/s
127
+ Producer done in SM 38.08093s @ 2.45 GB/s
128
+ Consumer done in SM 38.17893s @ 2.44 GB/s ✅
129
+ --------------------
130
+ Bytes: 1000000000 (953.67 MB), loops: 100, queue capacity: 10
131
+ Producer done in 63.22359s @ 1.47 GB/s
132
+ Consumer done in 66.07801s @ 1.41 GB/s
133
+ Producer done in SM 36.39488s @ 2.56 GB/s
134
+ Consumer done in SM 37.61108s @ 2.48 GB/s ✅
135
+ --------------------
136
+ Bytes: 10000000000 (9.31 GB), loops: 10, queue capacity: 10
137
+ Producer done in CRASHED
138
+ Consumer done in CRASHED
139
+ Producer done in SM 28.21499s @ 3.30 GB/s
140
+ Consumer done in SM 34.32684s @ 2.71 GB/s ✅✅
141
+ ```
@@ -0,0 +1 @@
1
+ from .factory import create_shared_memory_pair, SharedMemorySender, SharedMemoryReceiver
@@ -0,0 +1,106 @@
1
+ import numpy as np
2
+
3
+ def _encode_int(item:int):
4
+ size = item.bit_length() // 8 + 1
5
+ signed = item < 0
6
+ bytes_data = item.to_bytes(size, 'little', signed=signed)
7
+ return ('i', size, signed), memoryview(bytes_data)
8
+
9
+ def _decode_int(byte_data:memoryview, info:tuple):
10
+ return int.from_bytes(byte_data, 'little', signed=info[2])
11
+
12
+ def _encode_float(item:float):
13
+ byte_data = np.float64(item).tobytes()
14
+ return ('f', 8), memoryview(byte_data)
15
+
16
+ def _decode_float(byte_data:memoryview, info:tuple):
17
+ return np.frombuffer(byte_data, dtype=np.float64)[0]
18
+
19
+ def _encode_bool(item:bool):
20
+ byte_data = item.to_bytes(1, 'little')
21
+ return ('b', 1), memoryview(byte_data)
22
+
23
+ def _decode_bool(byte_data:memoryview, info:tuple):
24
+ return bool.from_bytes(byte_data, 'little')
25
+
26
+ def _encode_bytes(item:bytes):
27
+ return ('r', len(item)), memoryview(item)
28
+
29
+ def _decode_bytes(byte_data:memoryview, info:tuple):
30
+ if byte_data is None:
31
+ return b''
32
+ return bytes(byte_data)
33
+
34
+ def _encode_str(item:str):
35
+ bytes_data = str.encode(item)
36
+ return ('s', len(bytes_data)), memoryview(bytes_data)
37
+
38
+ def _decode_str(byte_data:memoryview, info:tuple):
39
+ if byte_data is None:
40
+ return ''
41
+ return bytes(byte_data).decode()
42
+
43
+ def _encode_ndarray(item:np.ndarray):
44
+ byte_data = item.tobytes()
45
+ return ('n', len(byte_data), item.dtype, item.shape), memoryview(byte_data)
46
+
47
+ def _decode_ndarray(byte_data:memoryview, info:tuple):
48
+ return np.frombuffer(byte_data, dtype=info[2]).copy().reshape(info[3])
49
+
50
+ def _encode_ndtype(item:np.dtype):
51
+ byte_data = str.encode(str(item))
52
+ return ('t', len(byte_data)), memoryview(byte_data)
53
+
54
+ def _decode_ndtype(byte_data:memoryview, info:tuple):
55
+ return np.dtype(bytes(byte_data).decode())
56
+
57
+ iterables = {
58
+ tuple: 'p',
59
+ list: 'l',
60
+ dict: 'd',
61
+ set: 'u',
62
+ }
63
+
64
+ encoder = {
65
+ bytes: _encode_bytes,
66
+ int: _encode_int,
67
+ float: _encode_float,
68
+ bool: _encode_bool,
69
+ str: _encode_str,
70
+ np.ndarray: _encode_ndarray,
71
+ }
72
+ decoder = {
73
+ 'r': _decode_bytes,
74
+ 'i': _decode_int,
75
+ 'f': _decode_float,
76
+ 'b': _decode_bool,
77
+ 's': _decode_str,
78
+ 'n': _decode_ndarray,
79
+ }
80
+
81
+ def encode(item):
82
+ if item is None:
83
+ return (None, 0), b''
84
+
85
+ datatype = type(item)
86
+ enc = encoder.get(datatype, None)
87
+ if enc is not None:
88
+ return enc(item)
89
+
90
+ if isinstance(item, np.dtype):
91
+ return _encode_ndtype(item)
92
+
93
+ raise ValueError(f"Unsupported data type: {type(item)}")
94
+
95
+ def decode(bytes_data:memoryview, info:tuple):
96
+ if info[0] is None:
97
+ return None
98
+
99
+ dec = decoder.get(info[0], None)
100
+ if dec is not None:
101
+ return dec(bytes_data, info)
102
+
103
+ if info[0] == 't':
104
+ return _decode_ndtype(bytes_data, info)
105
+
106
+ raise ValueError(f"Unsupported data type: {info[0]}")
@@ -0,0 +1,12 @@
1
+ import multiprocessing as mp
2
+
3
+ from .receiver import SharedMemoryReceiver
4
+ from .sender import SharedMemorySender
5
+
6
+
7
+ def create_shared_memory_pair(capacity):
8
+ q_data = mp.Queue(capacity)
9
+ q_ack = mp.Queue()
10
+ sender = SharedMemorySender(capacity, q_data, q_ack)
11
+ receiver = SharedMemoryReceiver(q_data, q_ack)
12
+ return sender, receiver
@@ -0,0 +1,75 @@
1
+ import multiprocessing as mp
2
+ import queue
3
+ from multiprocessing.shared_memory import SharedMemory
4
+
5
+ from .converter import decode, iterables
6
+
7
+ iterable_keys = set(iterables.values())
8
+ non_dict_iterables = set(iterables.keys()) - {dict}
9
+
10
+ class SharedMemoryReceiver:
11
+ def __init__(self, queue_data_in:mp.Queue, queue_ack_out:mp.Queue):
12
+ self.queue_data_in = queue_data_in
13
+ self.queue_ack_out = queue_ack_out
14
+
15
+ def _recover_data_recursive(self, shm:SharedMemory, info:tuple, byte_idx=0):
16
+ if info[0] not in iterable_keys:
17
+ if info[1] == 0: # size
18
+ return decode(None, info)
19
+ return decode(shm.buf[byte_idx:byte_idx+info[1]], info)
20
+
21
+ if info[0] == 'd': # dict
22
+ data = {}
23
+ for info_child, key in zip(info[3], info[4]):
24
+ item = self._recover_data_recursive(shm, info_child, byte_idx)
25
+ byte_idx += info_child[1]
26
+ data[key] = item
27
+ return data
28
+
29
+ # tuple, list, set
30
+ data = []
31
+ for info_child in info[3]:
32
+ item = self._recover_data_recursive(shm, info_child, byte_idx)
33
+ byte_idx += info_child[1]
34
+ data.append(item)
35
+
36
+ if info[2] in non_dict_iterables:
37
+ return info[2](data)
38
+
39
+ # NamedTuple needs to be handled separately
40
+ return info[2](*data)
41
+
42
+ def _recover_data(self, info:tuple):
43
+ if info[2] == 0:
44
+ return self._recover_data_recursive(None, info[1:])
45
+
46
+ name = info[0]
47
+ info = info[1:]
48
+ shm = SharedMemory(name=name, size=info[1])
49
+ data = self._recover_data_recursive(shm, info)
50
+ shm.close()
51
+ return data
52
+
53
+ def get_nowait(self):
54
+ if self.queue_data_in.empty():
55
+ return None
56
+
57
+ try:
58
+ info = self.queue_data_in.get_nowait()
59
+ except queue.Empty:
60
+ return None
61
+
62
+ if info is None:
63
+ return None
64
+
65
+ item = self._recover_data(info)
66
+ self.queue_ack_out.put_nowait(info[0])
67
+ del info
68
+ return item
69
+
70
+ def get(self, timeout=None):
71
+ info = self.queue_data_in.get(timeout=timeout)
72
+ item = self._recover_data(info)
73
+ self.queue_ack_out.put_nowait(info[0])
74
+ del info
75
+ return item
@@ -0,0 +1,149 @@
1
+ import multiprocessing as mp
2
+ import queue
3
+ from multiprocessing.shared_memory import SharedMemory
4
+
5
+ from .converter import encode, iterables
6
+
7
+ class _Separator:
8
+ def __init__(self, obj):
9
+ self.obj = obj
10
+
11
+ def _iterable_handler_list(stack, parents, entry):
12
+ stack.append(_Separator((type(entry), entry.__class__,)))
13
+ parents.append(([], []))
14
+ for field in reversed(entry): # reverse to maintain order in final binary
15
+ stack.append(field)
16
+
17
+ def _iterable_handler_set(stack, parents, entry):
18
+ stack.append(_Separator((set,entry.__class__,)))
19
+ parents.append(([], []))
20
+ for field in entry:
21
+ stack.append(field)
22
+
23
+ def _iterable_handler_dict(stack, parents, entry):
24
+ keys = list(entry.keys())
25
+ stack.append(_Separator((dict,entry.__class__,keys)))
26
+ parents.append(([], []))
27
+ for key in reversed(keys): # reverse to maintain order in final binary
28
+ stack.append(entry[key])
29
+
30
+ iterable_handlers = {
31
+ list: _iterable_handler_list,
32
+ tuple: _iterable_handler_list,
33
+ set: _iterable_handler_set,
34
+ dict: _iterable_handler_dict,
35
+ }
36
+
37
+ tuple_key = iterables[tuple]
38
+
39
+ class SharedMemorySender:
40
+ def __init__(self, capacity, queue_data_out:mp.Queue, queue_ack_in:mp.Queue):
41
+ self.open_handles = {}
42
+ self.queue_data_out = queue_data_out
43
+ self.queue_ack_in = queue_ack_in
44
+ self.capacity = capacity
45
+
46
+ def _prepare_binary(self, data):
47
+ parents:list[tuple[list[memoryview], list[tuple]]] = []
48
+ stack = [data]
49
+ while stack:
50
+ entry = stack.pop()
51
+
52
+ if isinstance(entry, _Separator):
53
+ # children processed, gather the results
54
+ bytes_data, child_infos = parents.pop()
55
+ entry_type = entry.obj[0]
56
+ key = iterables.get(entry_type, tuple_key)
57
+ size = sum(info[1] for info in child_infos)
58
+ clazz = entry.obj[1]
59
+ if entry_type == dict:
60
+ info = (key, size, clazz, child_infos, entry.obj[2])
61
+ else:
62
+ info = (key, size, clazz, child_infos)
63
+
64
+ if not parents:
65
+ # root entry was tuple, return the result
66
+ del parents, stack
67
+ return info, bytes_data
68
+
69
+ # append to parent binary
70
+ parents[-1][0].extend(bytes_data)
71
+ parents[-1][1].append(info)
72
+ continue
73
+
74
+ entry_type = type(entry)
75
+ if entry_type in iterables:
76
+ iterable_handlers[entry_type](stack, parents, entry)
77
+ continue
78
+ if isinstance(entry, tuple):
79
+ # NamedTuple
80
+ _iterable_handler_list(stack, parents, entry)
81
+ continue
82
+
83
+ info, bytes_data = encode(entry)
84
+ if not parents:
85
+ # root entry was single data, return the result
86
+ del parents, stack
87
+ return info, [bytes_data]
88
+
89
+ parents[-1][0].append(bytes_data)
90
+ parents[-1][1].append(info)
91
+
92
+ def put(self, data):
93
+ self.process_ack()
94
+ while len(self.open_handles) >= self.capacity:
95
+ self.wait_for_ack()
96
+
97
+ info, bytes_datas = self._prepare_binary(data)
98
+ size = info[1]
99
+ if size == 0:
100
+ info = (None, *info)
101
+ self.queue_data_out.put(info)
102
+ return
103
+
104
+ shm = SharedMemory(create=True, size=size)
105
+ info = (shm.name, *info)
106
+
107
+ offset = 0
108
+ for item in bytes_datas:
109
+ size = len(item)
110
+ shm.buf[offset:offset + size] = item
111
+ offset += size
112
+
113
+ self.queue_data_out.put(info)
114
+ self.open_handles[shm.name] = shm
115
+
116
+ def close_handle(self, shm_name:str):
117
+ shm = self.open_handles[shm_name]
118
+ shm.close()
119
+ shm.unlink()
120
+ del self.open_handles[shm_name]
121
+
122
+ def has_space(self):
123
+ self.process_ack()
124
+ return len(self.open_handles) < self.capacity
125
+
126
+ def process_ack(self):
127
+ try:
128
+ while True:
129
+ retour = self.queue_ack_in.get_nowait()
130
+ if retour is None:
131
+ return
132
+ self.close_handle(retour)
133
+ except queue.Empty:
134
+ pass
135
+
136
+ def close_all(self):
137
+ for shm_name in list(self.open_handles.keys()):
138
+ self.close_handle(shm_name)
139
+
140
+ def wait_for_ack(self):
141
+ shm_name = self.queue_ack_in.get()
142
+ self.close_handle(shm_name)
143
+
144
+ def wait_for_all_ack(self):
145
+ while len(self.open_handles) > 0:
146
+ self.wait_for_ack()
147
+
148
+ def __del__(self):
149
+ self.close_all()
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.1
2
+ Name: py-sharedmemory
3
+ Version: 1.0.0
4
+ Summary: Shared Memory Pipe for Fast Multiprocessing Data Sharing of Large Objects (>1MB)
5
+ Author: Tobias Würth
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 товіаѕ
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/tobiaswuerth/python_shared_memory_queue
29
+ Project-URL: Bug Tracker, https://github.com/tobiaswuerth/python_shared_memory_queue/issues
30
+ Keywords: python,python3,multiprocessing,sharedmemory,queue,pipe
31
+ Classifier: Programming Language :: Python :: 3
32
+ Classifier: License :: OSI Approved :: MIT License
33
+ Classifier: Operating System :: OS Independent
34
+ Requires-Python: >=3.9
35
+ Description-Content-Type: text/markdown
36
+ License-File: LICENSE
37
+ Requires-Dist: numpy>=1.26
38
+
39
+ # Shared Memory Pipe for Fast Multiprocessing Data Sharing of Large Objects (>1MB)
40
+
41
+ Since [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) [Queue](https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue)s pickles the data, there is a certain performance decrease for larger data objects and huge amounts of I/O.
42
+
43
+ This is the reason why I created a substitute which instead converts data objects into bytes (if needed), puts the bytes into shared memory and only transfers meta-data about the data structure via Queues to the receiving end.
44
+
45
+ The following data types are currently supported:
46
+ ```
47
+ - bytes
48
+ - int
49
+ - float
50
+ - bool
51
+ - str
52
+ - np.ndarray
53
+ - np.dtype
54
+ ```
55
+
56
+ As well as any nested structures for the given types:
57
+ ```
58
+ - tuple/NamedTuple
59
+ - list
60
+ - dict
61
+ - set
62
+ ```
63
+
64
+ # Considerations
65
+ There is a certain overhead to the conversion process which is especially noticable for smaller objects and more complex data structures.
66
+ Use the following approach depending on the size of the data you're handling:
67
+
68
+ ||10B|100B|1KB|10KB|100KB|1MB|10MB|100MB|1GB|10GB|
69
+ |---|---|---|---|---|---|---|---|---|---|---|
70
+ |``mp.Queue()``|✅|✅|✅|✅|✅|❌|❌|❌|❌|❌|
71
+ |``SharedMemory``|❌|❌|❌|❌|❌|✅|✅|✅|✅|✅|
72
+
73
+ Actual performance heavily relies on your memory speed.
74
+
75
+ # Usage example
76
+ ```python
77
+ import multiprocessing as mp
78
+
79
+ from memory import create_shared_memory_pair, SharedMemorySender, SharedMemoryReceiver
80
+
81
+ def producer_sm(sender:SharedMemorySender):
82
+ your_data = "your data"
83
+
84
+ # ...
85
+ sender.put(your_data) # blocks until there is space
86
+
87
+ # or
88
+ if sender.has_space():
89
+ sender.put(your_data)
90
+ else:
91
+ # your else case
92
+
93
+ # ...
94
+ sender.wait_for_all_ack() # wait for all data to be received before closing the sender
95
+ # exit
96
+
97
+ def consumer_sm(receiver:SharedMemoryReceiver, loops, size):
98
+ # ...
99
+ data = receiver.get() # blocks
100
+
101
+ # or
102
+ data = receiver.get(timeout=3) # raises queue.Empty exception after 3s
103
+
104
+ # or
105
+ data = receiver.get_nowait()
106
+ if data is None:
107
+ # handle empty case
108
+
109
+ # ...
110
+
111
+ if __name__ == '__main__':
112
+ sender, receiver = create_shared_memory_pair(capacity=5)
113
+
114
+ mp.Process(target=producer_sm, args=(sender,)).start()
115
+ mp.Process(target=consumer_sm, args=(receiver,)).start()
116
+
117
+ ```
118
+
119
+ # Performance Testing
120
+ Note that in this testing example Producer and Consumer where dependent upon each other (due to Queue capacity), that's why they take a similar amount of time. Your actual performance may vary depending on the data type, structure and overall system performance.
121
+
122
+ Since there is no inter-process data transfer, I/O drops accordingly. E.g. in my case -2.4GB/s
123
+
124
+ ```
125
+ --------------------
126
+ Bytes: 100 (100 B), loops: 100000, queue capacity: 1000
127
+ Producer done in 0.78797s @ 12.10 MB/s
128
+ Consumer done in 0.78698s @ 12.12 MB/s
129
+ Producer done in SM 5.37207s @ 1.78 MB/s
130
+ Consumer done in SM 5.37207s @ 1.78 MB/s ❌
131
+ --------------------
132
+ Bytes: 1000 (1000 B), loops: 100000, queue capacity: 1000
133
+ Producer done in 0.92900s @ 102.66 MB/s
134
+ Consumer done in 0.91599s @ 104.11 MB/s
135
+ Producer done in SM 5.45683s @ 17.48 MB/s
136
+ Consumer done in SM 5.45582s @ 17.48 MB/s ❌
137
+ --------------------
138
+ Bytes: 10000 (9.77 KB), loops: 100000, queue capacity: 1000
139
+ Producer done in 2.16847s @ 439.79 MB/s
140
+ Consumer done in 2.16147s @ 441.22 MB/s
141
+ Producer done in SM 5.64625s @ 168.90 MB/s
142
+ Consumer done in SM 5.64625s @ 168.90 MB/s ❌
143
+ --------------------
144
+ Bytes: 100000 (97.66 KB), loops: 100000, queue capacity: 1000
145
+ Producer done in 2.80213s @ 3.32 GB/s
146
+ Consumer done in 2.80013s @ 3.33 GB/s
147
+ Producer done in SM 8.24400s @ 1.13 GB/s
148
+ Consumer done in SM 8.24200s @ 1.13 GB/s ❌
149
+ --------------------
150
+ Bytes: 1000000 (976.56 KB), loops: 10000, queue capacity: 1000
151
+ Producer done in 4.87300s @ 1.91 GB/s
152
+ Consumer done in 4.87300s @ 1.91 GB/s
153
+ Producer done in SM 4.01900s @ 2.32 GB/s
154
+ Consumer done in SM 4.01800s @ 2.32 GB/s ✅
155
+ --------------------
156
+ Bytes: 10000000 (9.54 MB), loops: 1000, queue capacity: 1000
157
+ Producer done in 6.25722s @ 1.49 GB/s
158
+ Consumer done in 6.28221s @ 1.48 GB/s
159
+ Producer done in SM 3.79851s @ 2.45 GB/s
160
+ Consumer done in SM 3.80359s @ 2.45 GB/s ✅
161
+ --------------------
162
+ Bytes: 100000000 (95.37 MB), loops: 1000, queue capacity: 100
163
+ Producer done in 64.91876s @ 1.43 GB/s
164
+ Consumer done in 65.20476s @ 1.43 GB/s
165
+ Producer done in SM 38.08093s @ 2.45 GB/s
166
+ Consumer done in SM 38.17893s @ 2.44 GB/s ✅
167
+ --------------------
168
+ Bytes: 1000000000 (953.67 MB), loops: 100, queue capacity: 10
169
+ Producer done in 63.22359s @ 1.47 GB/s
170
+ Consumer done in 66.07801s @ 1.41 GB/s
171
+ Producer done in SM 36.39488s @ 2.56 GB/s
172
+ Consumer done in SM 37.61108s @ 2.48 GB/s ✅
173
+ --------------------
174
+ Bytes: 10000000000 (9.31 GB), loops: 10, queue capacity: 10
175
+ Producer done in CRASHED
176
+ Consumer done in CRASHED
177
+ Producer done in SM 28.21499s @ 3.30 GB/s
178
+ Consumer done in SM 34.32684s @ 2.71 GB/s ✅✅
179
+ ```
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ memory/__init__.py
5
+ memory/converter.py
6
+ memory/factory.py
7
+ memory/receiver.py
8
+ memory/sender.py
9
+ py_sharedmemory.egg-info/PKG-INFO
10
+ py_sharedmemory.egg-info/SOURCES.txt
11
+ py_sharedmemory.egg-info/dependency_links.txt
12
+ py_sharedmemory.egg-info/requires.txt
13
+ py_sharedmemory.egg-info/top_level.txt
14
+ tests/test_shared_memory.py
@@ -0,0 +1 @@
1
+ numpy>=1.26
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ['setuptools>=61.0', 'numpy>=1.26']
3
+ build-backend = 'setuptools.build_meta'
4
+
5
+ [tool.setuptools]
6
+ packages = ['memory']
7
+
8
+ [project]
9
+ name = "py-sharedmemory"
10
+ authors = [{name = "Tobias Würth"}]
11
+ version = "1.0.0"
12
+ requires-python = ">=3.9"
13
+ dependencies = [
14
+ "numpy>=1.26"
15
+ ]
16
+
17
+ description = "Shared Memory Pipe for Fast Multiprocessing Data Sharing of Large Objects (>1MB)"
18
+ keywords = ["python", "python3", "multiprocessing", "sharedmemory", "queue", "pipe"]
19
+ readme = {file = "README.md", content-type = "text/markdown"}
20
+ license = {file = "LICENSE"}
21
+
22
+ classifiers = [
23
+ "Programming Language :: Python :: 3",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Operating System :: OS Independent",
26
+ ]
27
+
28
+ [project.urls]
29
+ Repository = "https://github.com/tobiaswuerth/python_shared_memory_queue"
30
+ "Bug Tracker" = "https://github.com/tobiaswuerth/python_shared_memory_queue/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,339 @@
1
+ import unittest
2
+ import numpy as np
3
+
4
+ import multiprocessing as mp
5
+ mp.log_to_stderr()
6
+
7
+ from typing import NamedTuple
8
+
9
+ from memory import create_shared_memory_pair
10
+
11
+ class MyTuple(NamedTuple):
12
+ a:int
13
+ b:float
14
+ c:str
15
+
16
+ class TestSharedMemory(unittest.TestCase):
17
+ def test_None(self):
18
+ sender, receiver = create_shared_memory_pair(capacity=1)
19
+ data = None
20
+ sender.put(data)
21
+ item = receiver.get(timeout=2)
22
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
23
+ def test_None_tuple(self):
24
+ sender, receiver = create_shared_memory_pair(capacity=1)
25
+ data = (None,None)
26
+ sender.put(data)
27
+ item = receiver.get(timeout=2)
28
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
29
+ def test_None_list(self):
30
+ sender, receiver = create_shared_memory_pair(capacity=1)
31
+ data = [None,None]
32
+ sender.put(data)
33
+ item = receiver.get(timeout=2)
34
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
35
+ def test_None_dict(self):
36
+ sender, receiver = create_shared_memory_pair(capacity=1)
37
+ data = {None:None}
38
+ sender.put(data)
39
+ item = receiver.get(timeout=2)
40
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
41
+ def test_None_set(self):
42
+ sender, receiver = create_shared_memory_pair(capacity=1)
43
+ data = {None}
44
+ sender.put(data)
45
+ item = receiver.get(timeout=2)
46
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
47
+
48
+ def test_int_positive_small(self):
49
+ sender, receiver = create_shared_memory_pair(capacity=1)
50
+ data = 37
51
+ sender.put(data)
52
+ item = receiver.get(timeout=2)
53
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
54
+ def test_int_negative_small(self):
55
+ sender, receiver = create_shared_memory_pair(capacity=1)
56
+ data = -37
57
+ sender.put(data)
58
+ item = receiver.get(timeout=2)
59
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
60
+ def test_int_positive_big(self):
61
+ sender, receiver = create_shared_memory_pair(capacity=1)
62
+ data = 2147483640
63
+ sender.put(data)
64
+ item = receiver.get(timeout=2)
65
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
66
+ def test_int_negative_big(self):
67
+ sender, receiver = create_shared_memory_pair(capacity=1)
68
+ data = -2147483640
69
+ sender.put(data)
70
+ item = receiver.get(timeout=2)
71
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
72
+
73
+ def test_float1(self):
74
+ sender, receiver = create_shared_memory_pair(capacity=1)
75
+ data = 21474.83640
76
+ sender.put(data)
77
+ item = receiver.get(timeout=2)
78
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
79
+ def test_float2(self):
80
+ sender, receiver = create_shared_memory_pair(capacity=1)
81
+ data = -21474.83640
82
+ sender.put(data)
83
+ item = receiver.get(timeout=2)
84
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
85
+
86
+ def test_bool1(self):
87
+ sender, receiver = create_shared_memory_pair(capacity=1)
88
+ data = True
89
+ sender.put(data)
90
+ item = receiver.get(timeout=2)
91
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
92
+ def test_bool2(self):
93
+ sender, receiver = create_shared_memory_pair(capacity=1)
94
+ data = True
95
+ sender.put(data)
96
+ item = receiver.get(timeout=2)
97
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
98
+
99
+ def test_bytes(self):
100
+ sender, receiver = create_shared_memory_pair(capacity=1)
101
+ data = b"Hello World!"
102
+ sender.put(data)
103
+ item = receiver.get(timeout=2)
104
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
105
+ def test_bytes_empty(self):
106
+ sender, receiver = create_shared_memory_pair(capacity=1)
107
+ data = b''
108
+ sender.put(data)
109
+ item = receiver.get(timeout=2)
110
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
111
+
112
+ def test_str(self):
113
+ sender, receiver = create_shared_memory_pair(capacity=1)
114
+ data = "Hello World!"
115
+ sender.put(data)
116
+ item = receiver.get(timeout=2)
117
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
118
+ def test_str_empty(self):
119
+ sender, receiver = create_shared_memory_pair(capacity=1)
120
+ data = ""
121
+ sender.put(data)
122
+ item = receiver.get(timeout=2)
123
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
124
+ def test_str_long(self):
125
+ sender, receiver = create_shared_memory_pair(capacity=1)
126
+ data = """Zarathustra went down the mountain alone, no one meeting him. When he entered the forest, however, there suddenly stood before him an old man, who had left his holy cot to seek roots. And thus spake the old man to Zarathustra:
127
+ “No stranger to me is this wanderer: many years ago passed he by. Zarathustra he was called; but he hath altered.
128
+ Then thou carriedst thine ashes into the mountains: wilt thou now carry thy fire into the valleys? Fearest thou not the incendiary’s doom?
129
+ Yea, I recognise Zarathustra. Pure is his eye, and no loathing lurketh about his mouth. Goeth he not along like a dancer?
130
+ Altered is Zarathustra; a child hath Zarathustra become; an awakened one is Zarathustra: what wilt thou do in the land of the sleepers?
131
+ As in the sea hast thou lived in solitude, and it hath borne thee up. Alas, wilt thou now go ashore? Alas, wilt thou again drag thy body thyself?”
132
+ Zarathustra answered: “I love mankind.”
133
+ “Why,” said the saint, “did I go into the forest and the desert? Was it not because I loved men far too well?
134
+ Now I love God: men, I do not love. Man is a thing too imperfect for me. Love to man would be fatal to me.”
135
+ Zarathustra answered: “What spake I of love! I am bringing gifts unto men.”
136
+ “Give them nothing,” said the saint. “Take rather part of their load, and carry it along with them—that will be most agreeable unto them: if only it be agreeable unto thee!
137
+ If, however, thou wilt give unto them, give them no more than an alms, and let them also beg for it!”
138
+ “No,” replied Zarathustra, “I give no alms. I am not poor enough for that.”
139
+ The saint laughed at Zarathustra, and spake thus: “Then see to it that they accept thy treasures! They are distrustful of anchorites, and do not believe that we come with gifts.
140
+ The fall of our footsteps ringeth too hollow through their streets. And just as at night, when they are in bed and hear a man abroad long before sunrise, so they ask themselves concerning us: Where goeth the thief?
141
+ Go not to men, but stay in the forest! Go rather to the animals! Why not be like me—a bear amongst bears, a bird amongst birds?”
142
+ “And what doeth the saint in the forest?” asked Zarathustra.
143
+ The saint answered: “I make hymns and sing them; and in making hymns I laugh and weep and mumble: thus do I praise God.
144
+ With singing, weeping, laughing, and mumbling do I praise the God who is my God. But what dost thou bring us as a gift?”
145
+ When Zarathustra had heard these words, he bowed to the saint and said: “What should I have to give thee! Let me rather hurry hence lest I take aught away from thee!”—And thus they parted from one another, the old man and Zarathustra, laughing like schoolboys.
146
+ When Zarathustra was alone, however, he said to his heart: “Could it be possible! This old saint in the forest hath not yet heard of it, that God is dead!”"""
147
+ sender.put(data)
148
+ item = receiver.get(timeout=2)
149
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
150
+
151
+ def test_ndarray1(self):
152
+ sender, receiver = create_shared_memory_pair(capacity=1)
153
+ data = np.array([1,2,3,4,5], dtype=np.uint32)
154
+ sender.put(data)
155
+ item = receiver.get(timeout=2)
156
+ np.testing.assert_array_equal(item, data, f"Expected {data}, got {item}")
157
+ def test_ndarray2(self):
158
+ sender, receiver = create_shared_memory_pair(capacity=1)
159
+ data = np.array([[1.2,2.3,3.4],[-53.3,-3535,1]], dtype=np.float32)
160
+ sender.put(data)
161
+ item = receiver.get(timeout=2)
162
+ np.testing.assert_array_equal(item, data, f"Expected {data}, got {item}")
163
+ def test_ndarray_big(self):
164
+ sender, receiver = create_shared_memory_pair(capacity=1)
165
+ data = np.random.rand(64, 30, 6, 84, 84).astype(np.float32)
166
+ sender.put(data)
167
+ item = receiver.get(timeout=2)
168
+ np.testing.assert_array_equal(item, data, f"Expected {data}, got {item}")
169
+
170
+ def test_ndtype1(self):
171
+ sender, receiver = create_shared_memory_pair(capacity=1)
172
+ data = np.dtype(np.float32)
173
+ sender.put(data)
174
+ item = receiver.get(timeout=2)
175
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
176
+ def test_ndtype2(self):
177
+ sender, receiver = create_shared_memory_pair(capacity=1)
178
+ data = np.dtype(np.bool_)
179
+ sender.put(data)
180
+ item = receiver.get(timeout=2)
181
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
182
+
183
+ def test_list(self):
184
+ sender, receiver = create_shared_memory_pair(capacity=1)
185
+ data = [1,2,3,4,5]
186
+ sender.put(data)
187
+ item = receiver.get(timeout=2)
188
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
189
+ def test_list_nested(self):
190
+ sender, receiver = create_shared_memory_pair(capacity=1)
191
+ data = [[1,2,3],[4,5,6]]
192
+ sender.put(data)
193
+ item = receiver.get(timeout=2)
194
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
195
+ def test_list_mixed_nested(self):
196
+ sender, receiver = create_shared_memory_pair(capacity=1)
197
+ data = [1,2,[3,4],5]
198
+ sender.put(data)
199
+ item = receiver.get(timeout=2)
200
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
201
+ def test_list_nested_tuple(self):
202
+ sender, receiver = create_shared_memory_pair(capacity=1)
203
+ data = [1,2,(3,4),5]
204
+ sender.put(data)
205
+ item = receiver.get(timeout=2)
206
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
207
+ def test_list_mixed(self):
208
+ sender, receiver = create_shared_memory_pair(capacity=1)
209
+ data = [1,'a',3.4,5, None, b'Hello World!']
210
+ sender.put(data)
211
+ item = receiver.get(timeout=2)
212
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
213
+ def test_list_empty(self):
214
+ sender, receiver = create_shared_memory_pair(capacity=1)
215
+ data = []
216
+ sender.put(data)
217
+ item = receiver.get(timeout=2)
218
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
219
+ def test_list_empty_nested(self):
220
+ sender, receiver = create_shared_memory_pair(capacity=1)
221
+ data = [[],[]]
222
+ sender.put(data)
223
+ item = receiver.get(timeout=2)
224
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
225
+ def test_list_empty_nested_tuple(self):
226
+ sender, receiver = create_shared_memory_pair(capacity=1)
227
+ data = [(),()]
228
+ sender.put(data)
229
+ item = receiver.get(timeout=2)
230
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
231
+
232
+ def test_tuple(self):
233
+ sender, receiver = create_shared_memory_pair(capacity=1)
234
+ data = (1,2,3,4,5)
235
+ sender.put(data)
236
+ item = receiver.get(timeout=2)
237
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
238
+ def test_tuple_nested(self):
239
+ sender, receiver = create_shared_memory_pair(capacity=1)
240
+ data = ((1,2,3),(4,5,6))
241
+ sender.put(data)
242
+ item = receiver.get(timeout=2)
243
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
244
+ def test_tuple_mixed_nested(self):
245
+ sender, receiver = create_shared_memory_pair(capacity=1)
246
+ data = (1,2,[3,4],5)
247
+ sender.put(data)
248
+ item = receiver.get(timeout=2)
249
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
250
+ def test_tuple_nested_list(self):
251
+ sender, receiver = create_shared_memory_pair(capacity=1)
252
+ data = (1,2,[3,4],5)
253
+ sender.put(data)
254
+ item = receiver.get(timeout=2)
255
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
256
+ def test_tuple_mixed(self):
257
+ sender, receiver = create_shared_memory_pair(capacity=1)
258
+ data = (1,'a',3.4,5, None, b'Hello World!')
259
+ sender.put(data)
260
+ item = receiver.get(timeout=2)
261
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
262
+ def test_tuple_empty(self):
263
+ sender, receiver = create_shared_memory_pair(capacity=1)
264
+ data = ()
265
+ sender.put(data)
266
+ item = receiver.get(timeout=2)
267
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
268
+ def test_tuple_empty_nested(self):
269
+ sender, receiver = create_shared_memory_pair(capacity=1)
270
+ data = ((),())
271
+ sender.put(data)
272
+ item = receiver.get(timeout=2)
273
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
274
+
275
+ def test_namedtuple(self):
276
+ sender, receiver = create_shared_memory_pair(capacity=1)
277
+ data = MyTuple(1, 2.3, 'Hello World!')
278
+ sender.put(data)
279
+ item = receiver.get(timeout=2)
280
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
281
+
282
+ def test_dict(self):
283
+ sender, receiver = create_shared_memory_pair(capacity=1)
284
+ data = {1: 'a', 'b': 2, 3: 4}
285
+ sender.put(data)
286
+ item = receiver.get(timeout=2)
287
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
288
+ def test_dict_nested(self):
289
+ sender, receiver = create_shared_memory_pair(capacity=1)
290
+ data = {1: {2: 3, 4: 5}, 'a': {'b': 'c', 'd': 'e'}}
291
+ sender.put(data)
292
+ item = receiver.get(timeout=2)
293
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
294
+ def test_dict_mixed_nested(self):
295
+ sender, receiver = create_shared_memory_pair(capacity=1)
296
+ data = {1: {2: 3, 4: 5}, 'a': ['b', 'c', 'd']}
297
+ sender.put(data)
298
+ item = receiver.get(timeout=2)
299
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
300
+ def test_dict_mixed(self):
301
+ sender, receiver = create_shared_memory_pair(capacity=1)
302
+ data = {1: 'a', 'b': 2, 3: 4, 'c': None, 'd': b'Hello World!'}
303
+ sender.put(data)
304
+ item = receiver.get(timeout=2)
305
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
306
+ def test_dict_empty(self):
307
+ sender, receiver = create_shared_memory_pair(capacity=1)
308
+ data = {}
309
+ sender.put(data)
310
+ item = receiver.get(timeout=2)
311
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
312
+ def test_dict_empty_nested(self):
313
+ sender, receiver = create_shared_memory_pair(capacity=1)
314
+ data = {1: {}, 'a': {}}
315
+ sender.put(data)
316
+ item = receiver.get(timeout=2)
317
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
318
+
319
+ def test_set(self):
320
+ sender, receiver = create_shared_memory_pair(capacity=1)
321
+ data = {1,2,3,4,5}
322
+ sender.put(data)
323
+ item = receiver.get(timeout=2)
324
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
325
+ def test_set_mixed(self):
326
+ sender, receiver = create_shared_memory_pair(capacity=1)
327
+ data = {1,2,'a',None,4}
328
+ sender.put(data)
329
+ item = receiver.get(timeout=2)
330
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
331
+ def test_set_empty(self):
332
+ sender, receiver = create_shared_memory_pair(capacity=1)
333
+ data = set()
334
+ sender.put(data)
335
+ item = receiver.get(timeout=2)
336
+ self.assertEqual(item, data, f"Expected {data}, got {item}")
337
+
338
+ if __name__ == '__main__':
339
+ unittest.main()