Строковые буферы в подпрограммах обработки строк - PullRequest
0 голосов
/ 18 октября 2019

В настоящее время я создаю некоторые подпрограммы обработки строк с использованием языка ассемблера mips и столкнулся с проблемой. Каждая подпрограмма принимает выходной адрес в качестве аргумента, который служит ячейкой памяти, в которую будет записана вновь обработанная строка. Сначала у меня были подпрограммы, записывающие байты непосредственно в выходной адрес, но вскоре понял, что вместо этого я должен записать во временный строковый буфер, обработать его, а затем скопировать содержимое буфера в выходной адрес. Вопрос в том, как мне это сделать? Объявление глобального статического буфера строк в main.asm с помощью .space было одним из решений, но может быть проблематичным, когда одна подпрограмма обработки строки вызывается другой и начинает перезаписывать строковые данные вызывающей стороны. Использование стека времени выполнения для хранения строковых данных было другим решением, но имело свой собственный набор проблем.

Это общая проблема? Какова наилучшая практика для обработки ситуаций такого типа?

Любой совет будет принят с благодарностью. Спасибо.

1 Ответ

0 голосов
/ 18 октября 2019

Копирование медленное, избегайте его всякий раз, когда это возможно.

У вас есть 2 хороших варианта:

  • копирование и вычисление: например, обращение строкифункция, которая запускается в конце буфера вывода, записывая в обратном направлении при чтении вперед. Или наоборот, потому что конец ввода может быть уже горячим в кеше. И вы хотите оставить начало вывода горячим в кеше, поэтому запись его в последнюю очередь подходит для средних и больших строк. Вы можете легко создавать целые слова (или векторы SIMD) за раз, если у вас есть инструкция подстановки байтов, если и на входе, и на выходе есть необходимое выравнивание.
  • на месте: например, обращение строки, начиная суказатели на начало и конец, и они ведут их навстречу друг другу, пока они не пересекутся посередине. Переход по более чем 1 байту за раз требует проверки, чтобы избежать дублирования при их пересечении. (Хотя, если вы ограничиваете себя загрузками / хранилищами с выравниванием по словам, вам уже придется иметь дело с этим, и в зависимости от выравнивания + длины это может быть нелегко сделать быстро.)

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

Вы могли бы даже реализоватьи копии, и версии на месте некоторых операций в разных функциях, чтобы дать вашему абоненту возможность выбора, в зависимости от того, нужно ли им сохранять оригинал или нет. Если вам нужна производительность, достаточная для написания от руки в asm, спроектируйте свой API, чтобы избежать ненужного копирования.

(Для некоторых алгоритмов при копировании и на месте можно использовать один и тот же код.)

Обычно вы можете просто обрабатывать в буфере вывода.

Вы можете указать (в комментариях / документации / сигнатуру функции C с char *restrict outbuf arg) что ваша функция записывает свои выходные данные перед чтением всех своих входных данных, поэтому перекрытие входных и выходных данных не обязательно будет работать.

Если поддерживается точно на месте (например, для функции, которая вводит прописные буквы для каждого буквенного ввода)символ, где out[i] зависит только от in[i]), то вы также можете задокументировать это, даже если частичное перекрытие не будет. (например, при написании out[i] уничтожает in[i+something])

Функция, подобная этой, будет работать точно так же, как на месте, или в виде непересекающегося копирования и верхнего регистра.


Если вам нужен буфер tmp по некоторым причинам:

  • Если размер буфера tmp небольшой и фиксированного размера (без масштабирования с длиной входной строки), выделитеэто в callstack.

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

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

Конечно, динамическое распределение + свободное возможно в качестве наихудшего варианта для больших строк. На игрушечной ОС, такой как MARS или SPIM, я думаю, что для этого есть syscall. На реальном MIPS обычно есть какая-то библиотечная функция malloc, которую вы можете использовать с оптимизированным распределителем, который поддерживает свободный список. Вы хотите избежать фактического системного вызова, чтобы получить и освободить небольшой буфер.

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

...