Перейти к содержанию
    

sprintf() с динамически созданным списком печатаемых значений

Здравствуйте!

Дано : есть конфигурационный файл, задающий формат генерируемой строки с использованием разных 'ключей'. Во время вывода эти ключи подменяются конкретными величинами. Количество ключей в строке может быть разное.
Например:

конфигурация 

String1 = "\dat \time, \SNum, V1=\Val1, V2=\Val2"

превращается в выводимую строку

"2021-04-01 00:22:15, 103567, V1=17.2, V2=0.4"

 

Сейчас это делается парсингом строки конфигурации онлайн во время каждой печати строки, символ-за символом, с заменой встреченных ключей на из значения. Но это реально долго (у меня десятки ключей). Хочу ускорить и не делать парсинг каждый раз. Идеально было бы однажды пропарсить конфигурационную строку и создать что-то, что можно просто вызывать каждый раз, когда эту строку нужно напечатать. Для данного случая, например это будет:

sprintf (txtOut, "%s_%s, %06d, V1=%.1f, V2=%.1f", date, time, SERNUM, Val1, Val2);

но как это сделать? Нужно решить задачи:

1) создать строку формата  (в примере это ["%s_%s, %06d, V1=%.1f, V2=%.1f"]) - это самое простое, это я понимаю.

2) создать строку аргументов (в примере это [date, time, SERNUM, Val1, Val2]) - не представляю как это сделать.  Ну, могу создать массив указателей на нужные величины, но как это использовать?

3) запустить это как sprintf() - подозреваю, что если будет ответ на предыдущий вопрос, то этот уже просто решается.

 

Вроде бы подходит vsprintf() и va_list ? 

Если я правильно понял, то для vsprintf() можно однажды создать список параметров va_list c указателями на расположение нужных мне величин в памяти, и дальше просто вызывать vsprintf() столько раз сколько нужно? Так?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

sprintf точно так же парсит строку формата. Переливать из одного в другое будет еще медленее.

"реально долго" и "десятки ключей" как-то не стоят рядом. Реальные измерения есть?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 hour ago, rkit said:

sprintf точно так же парсит строку формата. Переливать из одного в другое будет еще медленее.

"реально долго" и "десятки ключей" как-то не стоят рядом. Реальные измерения есть?

18.6 миллисекунды на строку. Но когда нужно сформировать тысячу-другую строк, набегает много.

 

Время зависит от многих параметров, в основном- какой длины строка парсится и сколько ключей возможны: получается, что встретив новый ключ в строке, я его сравниваю с базой, применяя strstr(). То есть если у меня база из сейчас 120 ключей, то для каждого встреченного ключа это от одного до 120 вызовов strstr() . Можно оптимизировать сам метод, но лучше уйти от бесконечного парсинга.

 

И меня не столько парсинг строки формата волнует во время sprintf(), сколько этот вот парсинг ключей для списка параметров, который и съедает все время.

А формат и строка параметров у меня постоянные до следующего выключения, то есть создав один раз списки, надеюсь что vsprintf с ними быстрее будет работать чем я с исходными текстовыми ключами.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Цитата

Время зависит от многих параметров, в основном- какой длины строка парсится и сколько ключей возможны: получается, что встретив новый ключ в строке, я его сравниваю с базой, применяя strstr(). То есть если у меня база из сейчас 120 ключей, то для каждого встреченного ключа это от одного до 120 вызовов strstr() . Можно оптимизировать сам метод, но лучше уйти от бесконечного парсинга.

Можно для каждого ключа посчитать что-то вроде хеша (или CRC) и потом сравнивать не строки а хеш-суммы (CRC). И только при совпадении хеш (CRC) вызывать сравнение строк, чтобы избежать случайного совпадения.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, Edit2007 сказал:

Можно для каждого ключа посчитать что-то вроде хеша (или CRC) и потом сравнивать не строки а хеш-суммы (CRC). И только при совпадении хеш (CRC) вызывать сравнение строк, чтобы избежать случайного совпадения.

Не нужны все эти ухищрения. Тупой простейший цикл по N первым 32-битным словам - и всех делов.

Примерно так сделано у меня в аналогичном случае:

enum {JK_cfg, JK_now, JK_control, JK_file, ... JK_n};
#define apiKeyLen  12 //[байт]
#define apiKeysN   ((sizeof(apiKeys) - 1) / apiKeyLen)
#define __align4 __attribute__ ((aligned (4)))
typedef __packed u32 u32p8;
typedef __packed u64 u64p8;
char const apiKeys[] __align4 =
  "cfg         " //JK_cfg
  "now         " //JK_now
  "control     " //JK_control
  "file        " //JK_file
  ...
};  
int F(char const *str)
{  
  char const *ss = &apiKeys[sizeof(apiKeys) - 1];
  u64 q = ((u64p8 *)str)[0];
  u32 j = ((u32p8 *)s)[2];
  int n = apiKeysN - 1;
  do {
    ss -= apiKeyLen;
    if (q == ((u64p32 *)ss)[0]) if (j == ((u32 *)ss)[2]) break;
  } while (--n >= 0);
  return n;
}

Компилируется это в короткий оптимальный цикл.

Если длина некоторых имён превышает apiKeyLen (хвост вылазит за apiKeyLen), то после выхода из цикла добавляем проверку хвоста и, если он не совпадает, возвращаемся в цикл.

Ну или - в отдельный короткий список такие имена.

 

PS: Использование strstr() для такой цели - это конечно автор старался как можно сильнее затормозить работу. Только этим можно объяснить её использование.  :unknw:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

19 hours ago, Ruslan1 said:

получается, что встретив новый ключ в строке, я его сравниваю с базой, применяя strstr()

Ну так вот и нашли проблему. Сделай префиксное дерево или хеш-таблицу.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вопрос-то был про переменное количество аргументов в функции, а не про парсинг строк...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо всем, буду думать в рабочее время.

1. если сделать как однажды сделанную функцию с установленным динамически числом аргументов- то все уже придумано до меня, это массив, созданный с помощью va_list() для vsprintf().

2. Если упрощать "в лоб"- то отказаться от strstr(): переконвертировать строку, оставив признак ключа (у меня это '/') и после него один единственный байт, уникальный для каждого ключа. Метод работает для 253 ключей, что меня сейчас более чем устраивает. Тогда простым 'switch-case' решается.

 

Первый метод быстрее, но посмотрю получится ли так как я хочу, и сколько занимает в памяти этот сформированный va_list массив: у меня строк много разных, и может быть критично если массив сильно (в дестки-сотни раз) больше чем сама оригинальная строка.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

18 minutes ago, Ruslan1 said:

это массив, созданный с помощью va_list() для vsprintf().

Немного занудства - va_list это не массив, а некий псевдо указатель на параметр в вызове функции. Реализация его полностью зависит от архитектуры процессора и иногда от компилятора. Далеко не на всех архитектурах это реальный массив :(

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Имхо, наиболее производительоо Вам будет взять исходник принтера vprintf, и немного навернуть его, добавив в парсинг ваши параметры.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 минуты назад, AlexRayne сказал:

Имхо, наиболее производительоо Вам будет взять исходник принтера vprintf, и немного навернуть его, добавив в парсинг ваши параметры.

"Наворачивать исходник printf()" - нет необходимости. Если хочется получить максимально гибкий printf(), для этого есть его вариант с передачей ему в аргументах 2-х указателей: указателя на свою callback-функцию и указателя блок параметров для неё.

extern "C" int _Printf(void *(*)(void *, int), void *, const char *, va_list *);

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

45 минут назад, AlexRayne сказал:

Это где такой принтф окопался?

IAR. У других думаю есть аналогичные. По-крайней мере у CCS - точно есть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 4/18/2021 at 7:53 PM, xvr said:

Немного занудства - va_list это не массив, а некий псевдо указатель на параметр в вызове функции. Реализация его полностью зависит от архитектуры процессора и иногда от компилятора. Далеко не на всех архитектурах это реальный массив :(

 

Это очень важное занудство, спасибо за напоминание! :)  И живет этот "псевдоуказатель" только между va_start() и va_end(). Вот нашел пример тут, как организуют независимую область памяти и хранят там va_list. Они через va_copy() создают копию, и делают проход два раза: первый раз для определения нужного размера памяти и потом, после malloc(), засовывают все в эту память. Получается char* на область памяти, в которой храниться va_list.

char* vjoin(const char* delim, va_list ap) {
  va_list aq;
  va_copy(aq, ap);
  size_t dlen = strlen(delim);

  /* First pass. Use the copied va_list */
  size_t needed = 1; /* NUL terminator */
  const char* s = va_arg(aq, const char*);
  if (s) {
    needed += strlen(s);
    while ((s = va_arg(aq, const char*)))
      needed += dlen + strlen(s);
  }
  va_end(aq);

  /* Second pass. Use the original va_list */
  char* rv = malloc(needed);
  size_t offset = 0;
  *rv = 0;
  s = va_arg(ap, const char*);
  if (s) {
    strcpy(rv, s);
    offset = strlen(s);
    while ((s = va_arg(ap, const char*))) {
      strcpy(rv + offset, delim);
      strcpy(rv + offset + dlen, s);
      offset += dlen + strlen(s);
    }
  }
  return rv;
}

char* join(const char* delim, ...) {
  va_list ap;
  va_start(ap, delim);
  char* rv = vjoin(delim, ap);
  va_end(ap);
  return rv;
}

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ниасилил. Сложно у меня va_list идет, хакерство какое-то непереносимое (я его хотел сохранять для использования потом).

 

Сделаю "в лоб"

сотня функций, в каждую из которых передается от 0 до 100 переменных:

mystring_0arg(char* out, char* format, void* arg[]) {     sprintf (out, format);}

mystring_1arg(char* out, char* format, void* arg[]) {     sprintf (out, format, *arg[0]);}

mystring_2args(char* out, char* format, void* arg[]) {     sprintf (out, format, *arg[0], *arg[1]);}

mystring_3args(char* out, char* format, void* arg[]) {     sprintf (out, format, *arg[0], *arg[1], *arg[2]);}

 

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

 

То есть парсинг по-минимуму: один раз в начале работы при просмотре конфигурации. Тогда же и определяю какая из функций вызывается. во время работы получаю максимальное быстродействие.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...