Потоковая передача во время сериализации с помощью Cap'n'Proto

Рассмотрим схему Cap'n'Proto следующим образом:

struct Document {
  header @0 : Header;
  records @1 :List(Record); // usually large number of records.
  footer @2 :Footer;
}
struct Header { numberOfRecords : UInt32; /* some fields */ };
struct Footer { /* some fields */ };
struct Record {
   type : UInt32;
   desc : Text;
   /* some more fields, relatively large in total */
}

Теперь я хочу сериализовать (т.е. построить) экземпляр документа и передать его в удаленное место назначения.

Поскольку документ обычно очень большой, я не хочу полностью строить его в памяти перед отправкой. Вместо этого я ищу конструктор, который напрямую отправляет структуру за структурой по сети. Таким образом, дополнительный необходимый буфер памяти является постоянным (т.е. O(max(sizeof(Header), sizeof(Record), sizeof(Footer))).

Глядя на учебный материал, я не нашел такого строителя. Кажется, что MallocMessageBuilder сначала создает все в памяти (затем вы вызываете на нем writeMessageToFd).

Поддерживает ли Cap'n'Proto API такой вариант использования?

Или Cap'n'Proto больше предназначен для сообщений, которые помещаются в память перед отправкой?

В этом примере структура документа может быть опущена, а затем можно просто отправить последовательность из одного сообщения заголовка, n сообщений записи и одного нижнего колонтитула. Поскольку сообщение Cap'n'Proto является саморазграничивающим, это должно работать. Но вы теряете корень документа - возможно, иногда это не вариант.


person maxschlepzig    schedule 16.01.2016    source источник


Ответы (1)


Обозначенное вами решение - отправка частей документа в виде отдельных сообщений - вероятно, лучше всего подходит для вашего случая использования. По сути, Cap'n Proto не предназначен для потоковой передачи фрагментов одного сообщения, поскольку это не соответствует его свойствам произвольного доступа (например, что происходит, когда вы пытаетесь следовать указателю, указывающему на фрагмент, который вы не получили). пока что?). Вместо этого, если вам нужна потоковая передача, вам следует разделить большое сообщение на серию сообщений меньшего размера.

Тем не менее, в отличие от других подобных систем (например, Protobuf), Cap'n Proto не требует строгого размещения сообщений в памяти. В частности, вы можете проделать некоторые трюки, используя mmap(2). Если данные вашего документа поступают из файла на диске, вы можете mmap() сохранить этот файл в памяти, а затем включить его в свое сообщение. При использовании mmap() операционная система фактически не считывает данные с диска, пока вы не попытаетесь получить доступ к памяти, и ОС также может очистить страницы из памяти после обращения к ним, поскольку она знает, что у нее все еще есть копия на диске. Это часто позволяет вам писать гораздо более простой код, поскольку вам больше не нужно думать об управлении памятью.

Чтобы включить фрагмент mmap()ed в сообщение Cap'n Proto, вам нужно использовать capnp::Orphanage::referenceExternalData(). Например, учитывая:

struct MyDocument {
  body @0 :Data;
  # (other fields)
}

Вы можете написать:

// Map file into memory.
void* ptr = (kj::byte*)mmap(
    nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (ptr == MAP_FAILED) {
  KJ_FAIL_SYSCALL("mmap", errno);
}
auto data = capnp::Data::Reader((kj::byte*)ptr, size);

// Incorporate it into a message.
capnp::MallocMessageBuilder message;
auto root = message.getRoot<MyDocument>();
root.adoptDocumentBody(
    message.getOrphanage().referenceExternalData(data));

Поскольку Cap'n Proto является нулевым копированием, он в конечном итоге будет записывать память mmap()ed напрямую в сокет, даже не обращаясь к ней. Затем ОС должна прочитать содержимое с диска и вывести его в сокет по мере необходимости.

Конечно, у вас все еще есть проблема на принимающей стороне. Вы обнаружите, что гораздо сложнее спроектировать принимающую сторону для чтения в память mmap()ed. Одна из стратегий может состоять в том, чтобы сначала сбросить весь поток непосредственно в файл (без использования библиотеки Cap'n Proto), затем mmap() этот файл и использовать capnp::FlatArrayMessageReader для чтения mmap()ed данных на месте.

Я описываю все это, потому что это изящная вещь, которая возможна с Cap'n Proto, но не с большинством других фреймворков сериализации (например, вы не можете сделать это с Protobuf). Иногда очень полезно пошутить с mmap() — я успешно использовал это в нескольких местах в Sandstorm, родителя Cap'n Proto. проект. Однако я подозреваю, что для вашего варианта использования разбиение документа на серию сообщений, вероятно, имеет больше смысла.

person Kenton Varda    schedule 16.01.2016
comment
Примечание для других: в библиотеке Java по-прежнему отсутствует Orphans API, поэтому описанное выше там не работает. - person Alex Moore-Niemi; 05.11.2020