Короткий ответ: Нет, переносимого способа сделать это не существует.
Подход sendfile()
специфичен для Linux, потому что в большинстве других ОС, реализующих его, источником должен быть файл или объект общей памяти. (Я даже не проверял, поддерживается ли/в каких версиях ядра Linux sendfile()
от дескриптора сокета до /dev/null
. Если честно, я бы очень подозрительно отнесся к коду, который это делает.)
Глядя на например. Исходники ядра Linux, и, учитывая, как мало ssize_t discard(fd, len)
отличается от стандартного ssize_t read(fd, buf, len)
, очевидно, можно добавить такую поддержку. Можно даже добавить его через ioctl (скажем, SIOCISKIP
) для легкого обнаружения поддержки.
Однако проблема в том, что вы разработали неэффективный подход, и вместо того, чтобы исправить подход на алгоритмическом уровне, вы ищете костыли, которые позволили бы вашему подходу работать лучше.
Видите ли, очень сложно показать случай, когда «лишняя копия» (из буферов ядра в буферы пользовательского пространства) является реальным узким местом в производительности. Иногда количество системных вызовов (переключение контекста между пространством пользователя и пространством ядра). Если вы отправили патч вверх по течению, реализующий, например. ioctl(socketfd, SIOCISKIP, bytes)
для сокетов потока домена TCP и/или Unix, они бы указали, что увеличение производительности, которого они надеются достичь, лучше достигается, если не пытаться получить данные, которые вам не нужны в первую очередь. (Другими словами, способ, которым вы пытаетесь что-то делать, по своей сути неэффективен, и вместо того, чтобы создавать костыли, чтобы заставить этот подход работать лучше, вы должны просто выбрать более эффективный подход.)
В вашем первом случае процесс, получающий структурированные данные, обрамленные идентификатором типа и длины, желающий пропустить ненужные кадры, лучше исправить, исправив протокол передачи. Например, принимающая сторона может информировать отправляющую сторону о том, какие кадры ее интересуют (т. е. базовый подход к фильтрации). Если вы застряли с глупым протоколом, который вы не можете заменить по внешним причинам, вы сами по себе. (Сообщество разработчиков FLOSS не несет и не должно нести ответственность за принятие глупых решений только потому, что кто-то о них плачет. Любой может это сделать, но им нужно будет сделать это таким образом, чтобы не требовать от других дополнительной работы. слишком.)
Во втором случае вы уже прочитали свои данные. Не делай этого. Вместо этого используйте буфер пользовательского пространства, достаточно большой, чтобы вместить два полноразмерных кадра. Всякий раз, когда вам нужно больше данных, но начало кадра уже находится за серединой буфера, memmove()
кадр должен начинаться в начале буфера.
Когда у вас есть частично прочитанный кадр, и у вас осталось N
непрочитанных байтов, которые вас не интересуют, прочитайте их в неиспользуемую часть буфера. Места всегда достаточно, потому что вы можете перезаписать часть, уже занятую текущим кадром, а его начало всегда находится в пределах первой половины буфера.
Если кадры маленькие, скажем, максимум 65536 байт, вы должны использовать настройку максимального размера буфера. На большинстве настольных и серверных машин с потоковыми сокетами с высокой пропускной способностью гораздо разумнее что-то вроде 2 МБ (2097152 байта или больше). Памяти тратится не так уж много, но вы редко делаете какие-либо копии памяти (а если и делаете, то они, как правило, короткие). (Можно даже оптимизировать перемещения памяти так, чтобы копировались и выравнивались только полные строки кеша, поскольку оставление почти одной строки кеша мусора в начале буфера не имеет значения.)
Я выполняю высокопроизводительные вычисления с большими наборами данных (включая молекулярные данные в текстовой форме, где записи разделяются символами новой строки, а специальные синтаксические анализаторы для преобразования десятичных целых чисел или значений с плавающей запятой используются для повышения производительности), и этот подход хорошо работает на практике. Проще говоря, пропуск данных, уже находящихся в вашем буфере, — это не то, что вам нужно оптимизировать; это незначительные накладные расходы по сравнению с простым нежеланием делать то, что вам не нужно.
Существует также вопрос о том, что вы хотите оптимизировать при этом: используемое время/ресурсы процессора или настенные часы, используемые в общей задаче. Это совершенно разные вещи.
Например, если вам нужно отсортировать большое количество текстовых строк из какого-то файла, вы используете наименьшее время процессора, если просто читаете весь набор данных в память, создаете массив указателей на каждую строку, сортируете указатели и, наконец, пишете каждую строку (используя внутреннюю буферизацию и/или POSIX writev()
, так что вам не нужно выполнять системный вызов write()
для каждой отдельной строки).
Однако, если вы хотите свести к минимуму используемое время настенных часов, вы можете использовать двоичную кучу или сбалансированное двоичное дерево вместо массива указателей и добавлять в кучу или вставлять по порядку каждую полностью прочитанную строку, чтобы, когда последняя строка наконец прочитано, у вас уже есть строки в правильном порядке. Это связано с тем, что ввод-вывод хранилища (для всех случаев, кроме патологических входных данных, что-то вроде односимвольных строк) занимает больше времени, чем их сортировка с использованием любого надежного алгоритма сортировки! Алгоритмы сортировки, которые работают в режиме реального времени (по мере поступления данных), обычно не так эффективны с точки зрения ЦП, как те, которые работают в автономном режиме (для полных наборов данных), поэтому в конечном итоге они потребляют несколько больше времени ЦП; но поскольку работа ЦП выполняется в то время, которое в противном случае тратится впустую на ожидание загрузки всего набора данных в память, она выполняется за меньшее время настенных часов!
Если есть необходимость и интерес, я могу привести практический пример для иллюстрации методов. Однако здесь нет абсолютно никакой магии, и любой программист на C должен быть в состоянии реализовать их (как схему буферизации, так и схему сортировки) самостоятельно. (Я рассматриваю возможность использования таких ресурсов, как справочные страницы Linux в Интернете, а также статьи Википедии и псевдокод, например, бинарные кучи делают это "самостоятельно". Если вы не просто копируете-вставляете существующий код, я считаю, что вы делаете это «самостоятельно», даже если кто-то или какой-то ресурс поможет вам найти хорошие, надежные способы сделать это.)
person
Nominal Animal
schedule
06.07.2018
read()
в качестве запасного варианта. Если вы знаете, сколько нужно пропустить, убедитесь, что вы используете многобайтовые вызовыread()
и что вызовыread()
возвращают столько, сколько вы ожидаете (зацикливайте, если они возвращают меньше байтов, пока не достигнете требуемого числа). - person Jonathan Leffler   schedule 07.07.2018