Организация сбора данных и экономия памяти.
Как известно многим практикам, есть страшная и неотвратимая зависимость скорости работы и размера используемой памяти. Часто - это проблема нетривиальна, и однозначного рещения не имеет. Коротенький пример для тех, кто с проблемой не знаком, или знаком плохо.
Пример
Задача - принять данные с 256 портов и передать их соответственно в другие 256 портов.
Рещение по преобразованию портов приходит в голову просто. Пишется функция конвертации входных номеров в выходные и вперед. Но в режиме реального времени затраты на расчет (чаще всего он не такой простой как в примере) занимает время. Поэтому для ускорения мы будем тратить память в большем колличестве. Создадим тупо статичный массив. В нем будем по индексу хранить выходные порты. При этом индекс будет соответствовать номеру входного порта. Тогда при передаче данных определение выходного порта сведется тупо к прямому обращению к массиву по имеющемуся индексу. На лицо ускорение поведения, при увеличении затрачиваемой памяти. К слову, пример вполне реальный, единственное, что в реальности индексами были биты, а порты были не портами, а логическими номерами, что суть не важно.
Реально, в современных системах которые работают с железом приходится иметь дело, постоянно, с системами типа: чип (картис) PCI (PCIe,PCIx) + DMA в комплекте.
Для такой конфигурации задача передачи данных в железо сводится к тривиальной очереди на входе, с поддержкой мультитаскинга. А вот на выходе, чаще всего задача будет нетривальна, особенно если колличество результирующих буферов, как и их размер, не фиксировано.
Для того, чтобы полноценно принимать данные с железа в асинхронном режиме приходится постоянно в лоб резервировать огромные буфера данных, с предполагающимся максимальным размером, что приводит к разбазариванию памяти и ее тотальном дифиците, при этом не гарантирует прием всей приходящей информации в пиковые моменты.
Дабы такое на происходило, многие производители железа предлагают в конйигурации дескрипторов для DMA поля, которые позволяют цеплять в виде результирующих буферов - связные списки.
Каждый элемент такого списка некий стандартный результирующий буфер, однако система железа умеет при окончании размера результирующего буфера, перейти по указателю к следующему.
Это позволяет выделять память более аттрактивно.
Как вы понимаете, реальное решение в коде в данном случае вообще приводить некорректно, т.к. каждое такое решение будет специфичным для различного вида устройств.
Однако, я могу привести реально использованное решение в виде теоретического совета.
Итак, в вышеописаной системе был создан некий пул памяти, которые не удовлетворял условиям, когда суммарное колличество выделеной памяти равно максимальному уровню потребления ее для записи результатов системы.
Весь этот пул был поделен таким образом, чтобы результаты писались в связные списки памяти, передаваемые в устройство через форматированые заголовки дескрипторов.
Каждому пакету данных передаваемых в устройство выделялось максимальное суммарное колличество памяти в списке, достаточное для записи всех возможных результатов.
Естественный перерасход памяти компенсировался на выходе. Когда либо по приходу интерапта, либо по поллинговому запросу ( в данном случае не принципиально) происходила первичная обработка результирующих буферов, то программа просчитывала колличество пустых незаполненных буферов и высвобождала их в пул. Таким образом колличество задействованная память для результатов макисмально приближалась к реально необходимой.
Последующий процесс обрабатывал реальные данные, после чего освобождал и реально занятые буфера.
Дабы в пиковые моменты не попасть в ситуацию с невозможностью выделить память еще на входе, и не попасть в ошибочный кейс, была организована входная очередь, параметром постановки в которую являлось наличие того самого максимального колличества суммарной памяти в пуле маленьких буферов.
Ее размер, как пиаслось раньше, был предусмотрительно задан как максимально возможное колличество входных данных -1 (хотя бы единственный буфер должен был быть в обработке), что позволяло сказать с 100% уверенностью.
В данной системе не будет ошибочного состояния от нехватки памяти.
Кроме того, в связи с динамическим высвобождением буферов из списков, размер памяти уменьшался относительно реально необходимого в системе, если выделять цельные последовательные буфера для каждого пакета обрабатываемых данных.
Естественно, описываемый выше закон о потери скорости не отменялся никем, и система притормаживалась:
а) предварительными оценками пустых буферов из пула.
б) дополнительным процессом пред-ообработки, для оценки и овобождения излишков памяти связанных с данными не имеющими максимальных значений в колличестве результатов.
в) более медленным доступом к результатам, связанным с необходимостью прохода по связным спискам и расчетом индексов.
Однако, такое решение не сильно нагружало процессор, так как использованные моменты по сути сводились к минимальным использованием сложных конструкций ( только простые операторы условия и присваивание указателей). А экономия памяти в данном слуае была порядка 60%.
Осталось сказать, что пул буферов использовал структуру LIFO в своей основе, и таким образом помогал работать с повторяющимися адресами, что на полную катушку использовало возможности кеширования памяти и ускорения работы с копированием, где это необходимо.
Если у вас есть вопросы, то куски конкретного кода по решениям могу привести в ответы на коментарии.
