Шаг 216.
Python: сборник рецептов.
Сети и веб-программирование. Отсылка и получение больших массивов

    На этом шаге мы рассмотрим особенности организации этой операции.

Задача

    Вы хотите отправлять и получать большие массивы смежных данных по сетевому соединению, делая настолько мало копий, насколько это возможно.

Решение

    Следующие функции используют представления памяти (memoryviews) для отправки и получения больших массивов:

def send_from(arr, dest):
    view = memoryview(arr).cast('B')
    while len(view):
        nsent = dest.send(view)
        view = view[nsent:]
    
def recv_into(arr, source):
    view = memoryview(arr).cast('B')
    while len(view):
        nrecv = source.recv_into(view)
        view = view[nrecv:]

    Чтобы протестировать эту программу, сначала создайте сервер и клиентскую программу, соединенные через сокет. В сервере:

>>> from socket import *
>>> s = socket(AF_INET, SOCK_STREAM)
>>> s.bind(('', 25000))
>>> s.listen(1)
>>> c, a = s.accept()

    В клиенте (в отдельном интепретаторе):

>>> from socket import *
>>> c = socket(AF_INET, SOCK_STREAM)
>>> c.connect(('localhost', 25000))
>>>

    Вся идея этого рецепта в том, что вы можете отправить огромный массив данных через соединение. В этом случае массивы могут быть созданы с помощью модуля array или numpy. Например:

# Сервер
>>> import numpy
>>> a = numpy.arange(0.0, 50000000.0)
>>> send_from(a, c)
>>>
# Клиент
>>> import numpy
>>> a = numpy.zeros(shape=50000000, dtype=float)
>>> a[0:10]
array([ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
>>> recv_into(a, c)
>>> a[0:10]
array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
>>>


Обсуждение

    Распределенные вычисления с большими объемами данных и параллельное программирование часто требуют получения/отправки больших кусков данных. Однако чтобы это сделать, вам как-то нужно свернуть данные в сырые байты, которые можно использовать с низкоуровневыми сетевыми функциями. Вам также может потребоваться нарезать данные на куски, поскольку большинство связанных с сетью функций не может посылать огромные блоки данных целиком за раз.

    Существует такой подход: как-то сериализовать данные - возможно, путем преобразования в байтовую строку. Однако это обычно заканчивается созданием копии данных. Даже если вы делаете это кусочек за кусочком, ваш код все равно будет создавать кучу маленьких копий.

    Данный рецепт обходит проблему путем хитрого фокуса с представлениями памяти (memoryviews). По сути, представление памяти - это наложение (overlay) существующего массива. Различные типы также могут быть преобразованы в представления памяти, чтобы позволить интерпретировать данные другим способом. В этом смысл следующего объявления:

  view = memoryview(arr).cast('B')

    Оно принимает массив arr и превращает в представление памяти беззнаковых байтов.

    В этой форме представление (view) может быть передано связанным с сокетами функциям, таким как sock.send() или send.recv_into(). "Под капотом" эти методы могут работать напрямую с областью памяти. Например, sock.send() посылает данные напрямую из памяти, без копирования. send.recv_into() использует представление памяти как входной буфер для операции получения.

    Оставшееся осложнение состоит в том, что функции сокетов могут работать только с разделенными на части данными. В общем случае потребуется много различных вызовов send() и recv_into(), чтобы передать весь массив. Не волнуйтесь. После каждой операции представление (view) разрезается в соответствии с количеством отправленных или полученных байтов для создания нового представления. Новое представление также будет наложением на память (memory overlay). Так что никаких копий создаваться не будет.

    Есть одна тонкость: получатель должен знать заранее, сколько данных будет отправлено, чтобы он мог либо заранее выделить массив, либо проверить, что он может принять данные в существующий массив. Если это важно в вашем случае, то посылающий должен всегда сначала посылать размер отправляемых данных, а уже потом - данные массива.

    Со следующего шага мы начнем рассмотривать конкурентность.




Предыдущий шаг Содержание