Кеширование OVERLAPPED структур при использовании IOCP в Windows

Я использую порты завершения ввода-вывода в Windows, у меня есть объект под названием «Stream», который напоминает и абстрагирует РУЧКУ (так что это может быть сокет, файл и т. Д.).

Когда я вызываю Stream :: read () или Stream :: write () (так, ReadFile () / WriteFile () в случае файлов и WSARecv () / WSASend () в случае сокетов), я выделяю новая структура OVERLAPPED, чтобы сделать ожидающий запрос ввода-вывода, который будет завершен в цикле IOCP каким-либо другим потоком.

Затем, когда структура OVERLAPPED будет завершена циклом IOCP, она будет там уничтожена. Если это так, Stream :: read () или Stream :: write () вызываются снова из цикла IOCP, они будут создавать экземпляры новых структур OVERLAPPED, и это будет длиться вечно.

Это прекрасно работает. Но теперь я хочу улучшить это, добавив кеширование объектов OVERLAPPED: когда мой объект Stream выполняет много операций чтения или записи, абсолютно имеет смысл кэшировать структуры OVERLAPPED.

Но теперь возникает проблема: когда я освобождаю объект Stream, я должен освободить кешированные структуры OVERLAPPED, но как я могу узнать, завершены ли они или все еще ожидают, и один из циклов IOCP завершит это в последнее время? Итак, здесь требуется атомарный счетчик ссылок, но теперь проблема в том, что если я использую счетчик атомарных ссылок, мне придется увеличивать этот счетчик ссылок для каждой операции чтения или записи и уменьшать для каждого IOCP. завершение цикла структуры OVERLAPPED или удаление потока, которые на сервере представляют собой много операций, поэтому я закончу увеличением / уменьшением большого количества атомарных счетчиков < em> много раз.

Будет ли это очень негативно влиять на параллелизм нескольких потоков? Это моя единственная забота, которая не дает мне установить этот атомарный счетчик ссылок для каждой структуры OVERLAPPED.

Беспочвенны ли мои опасения?

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


person Marco Pagliaricci    schedule 19.12.2013    source источник
comment
Вы беспокоитесь о наносекундах, когда операция занимает миллисекунды. Это полностью соответствует корню всех злых мантр.   -  person Hans Passant    schedule 19.12.2013
comment
«Затем, когда структура OVERLAPPED будет завершена циклом IOCP, она будет там уничтожена». - зачем ты его разрушаешь? Он должен быть членом класса потока или какого-либо другого класса «IOCPbuffer», который кэшируется и используется в вызовах WSA и чьи данные применяются к его потоку после завершения IOCP перед возвратом в его пул.   -  person Martin James    schedule 19.12.2013
comment
Мартин, да, это именно то, чего я хочу достичь, и я хочу знать, можно ли использовать атомарный счетчик ссылок для этих структур IOCPbuffer. Ганс, проблема не в наносекундах или миллисекундах, а в блокировке, связка заблокированных инструкций может повлиять на параллелизм потоков, поскольку, когда область памяти (например, атомарный счетчик) атомарно считывается / записывается, другие ядра должны ждать завершения атомарная транзакция.   -  person Marco Pagliaricci    schedule 19.12.2013
comment
AFAIK, вам не нужен атомный счетчик. Если вы кешируете / объединяете эти экземпляры, вы никогда не уничтожаете их, а просто повторно объединяете их. Для пула iself потребуется блокировка, но время, потраченное на обработку блокировки, наверняка будет потрачено на предотвращение постоянного создания / уничтожения экземпляров класса.   -  person Martin James    schedule 19.12.2013
comment
Ой ... подождите, вы отправляете один и тот же буфер нескольким клиентам? Вот почему вам нужен refCount?   -  person Martin James    schedule 19.12.2013
comment
Нет, если я выделяю новый пакет ввода-вывода, то есть структуру IOCPbuffer, я сохраню указатель на него внутри объекта Stream, затем я вызову ReadFile или WSARecv и получу ERROR_IO_PENDING: пакет ожидает обработки. Теперь: если я вызову Stream :: close (), который, в свою очередь, вызовет CloseHandle (handle); другой поток завершит отложенное чтение, но вопрос в том, где я должен освободить пакет? в цикле IOCP или в dtor потока, поскольку объект Stream имеет указатель на этот пакет, поэтому разделяет его владение? Атомный счетчик решит эту проблему без проблем.   -  person Marco Pagliaricci    schedule 19.12.2013


Ответы (1)


Предполагая, что вы связываете буфер данных со структурой OVERLAPPED как объект данных «для каждой операции», а затем объедините их, чтобы избежать чрезмерного выделения / освобождения и фрагментации кучи, является хорошей идеей.

Если вы когда-либо используете этот объект только для операций ввода-вывода, тогда нет необходимости в счетчике ссылок, просто вытащите его из пула, выполните с ним свой WSASend / WSARecv, а затем отпустите его в пул, как только вы закончите с ним в обработчик завершения IOCP.

Если, однако, вы хотите немного усложнить задачу и разрешить передачу этих буферов другому коду, вы можете рассмотреть возможность их подсчета ссылок, если это упростит задачу. Я делаю это в своей текущей структуре, и это позволяет мне иметь общий код для сетевой стороны вещей, а затем передавать буферы данных из завершений чтения в код клиента, и они могут делать с ними все, что хотят, и когда они готовы, они выпускают их обратно в бассейн. В настоящее время здесь используется счетчик ссылок, но я отхожу от него в качестве незначительной настройки производительности. Счетчик ссылок все еще существует, но в большинстве случаев он изменяется только от 0 до 1, а затем снова до 0, вместо того, чтобы манипулировать на разных уровнях в моей структуре (это делается путем передачи права собственности на буфер пользователю код с использованием умного указателя).

В большинстве ситуаций я ожидаю, что подсчет ссылок вряд ли будет вашей самой дорогой операцией (даже на оборудовании NUMA в ситуациях, когда ваши буферы используются с нескольких узлов). Более вероятно, что блокировка, связанная с помещением этих вещей обратно в пул, будет вашим узким местом; Я решил эту проблему, поэтому перехожу к следующему более высокому фрукту;)

Вы также говорите о своем объекте «для каждого соединения» и локальном кэшировании ваших данных «для каждой операции» (это то, что я делаю, прежде чем отправлять их обратно в распределитель), в то время как счетчик ссылок не является строго обязательным для данных «на операцию». , для данных «на соединение» необходимо, по крайней мере, атомарно изменяемое количество выполняемых операций, чтобы вы могли определить, когда можно освободить ИТ. Опять же, из-за моей конструкции фреймворка это стало обычным счетчиком ссылок, для которого код клиента может содержать ссылки, а также активные операции ввода-вывода. Мне еще предстоит найти способ обойти необходимость этого счетчика в структуре общего назначения.

person Len Holgate    schedule 19.12.2013
comment
Спасибо, Лен. Что ж, у меня здесь есть фреймворк общего назначения, поэтому мне не нужно открывать пользователю пакеты io-запросов, вместо этого я хочу их скрыть. При нормальном дизайне (A) при использовании io req. пакет из пула и (B) вернуть его в пул из потока обработчика iocp действительно приведет к новому узкому месту: блокировке пула. Моя цель здесь - сохранить тот же io req. пакет, пока не истечет время жизни соединения, то есть пока не истечет срок жизни объекта Stream. Теперь единственное, что меня беспокоит, - это когда мне нужно освободить io req. пакет, внутри dtor потока или внутри обработчика iocp? - person Marco Pagliaricci; 19.12.2013
comment
Если пакет ожидает обработки, я сделаю это в обработчике iocp, если он больше не ожидает обработки и больше не используется, я должен освободить его от dtor потока, иначе произойдет утечка пакета. Теперь проблема в том, что я не знаю, нужен ли мне атомарный счетчик (или даже логическое значение), который просто сообщает мне атомарно, что пакет больше не ожидает обработки, и у других потоков нет никаких право собственности на него, поэтому я могу удалить его из дтора Stream. Кто-то говорит, что мне здесь атомарность не нужна, но так ли это? Я здесь немного сомневаюсь. - person Marco Pagliaricci; 19.12.2013
comment
Предполагая, что вы никогда не освобождаете свой поток до тех пор, пока все операции с ним не будут завершены, вам не нужен счетчик в данных для каждой операции, но вам, вероятно, понадобится счетчик в вашем потоке для отслеживания выполняемых операций. - person Len Holgate; 19.12.2013
comment
Я действительно никогда не освобождаю поток, пока не будут завершены все ожидающие операции. Поскольку я выполняю только 1 операцию каждый раз, у меня есть 2 флага: ожидающее чтение и ожидающая запись, поэтому, когда у меня 00 == нет ожидающих операций, 11 == ожидающих записи и чтения, 01 == ожидающего чтения и т. Д. . Я установил флаг перед вызовом ReadFile () и установил его в обработчике iocp. Здесь мне не нужны ни атомарные флаги, ни атомные счетчики, потому что я выполняю только одну операцию, верно? - person Marco Pagliaricci; 22.12.2013