LINUX.ORG.RU

memcmp вместо strncmp для оптимизации?

 , , memcmp, strncmp


0

2

Приветствую.

Построено у меня взаимодействие с тысячами железок через mqtt сервер, из всего многообразия «сообщений» приходящих через либу в типе char * msg есть пересылка снимков в jpeg, которые тоже приходят одномоментно тысячами штук на свой сервис, весь «парсинг заголовка сообщения» на этом сервисе выполнял обычным strncmp и для жпега это выглядит как

if (!strncmp(msg, "\xFF\xD8\xFF", 3)) { ... }
else if ...

как я понимаю memcmp быстрее strncmp, тем более мне нужно сравнить байты а не строки и понятно что так тоже как минимум компилируется

if (!memcmp(msg, "\xFF\xD8\xFF", 3)) { ... }

а можно БЕЗ объявления переменной выполнять сравнение набора байт любой длины?

мне такой в голову пришел перл, но что то сомнительна его производительность )

if (memcmp(msg, std::array<uint8_t,3>({0xFF, 0xD8, 0xFF}).data(), 3) == 0)
★★★

как я понимаю memcmp быстрее strncmp

для начала открой исходники используемой либы и сравни

а можно БЕЗ объявления переменной выполнять сравнение набора байт любой длины?

Я бы (поскольку количество заголовков конечно) на каждый заголовок создал static или не static, в зависимости от требуемой области видения ''' const uint8_t my_header_NN[] = {0xFF,0xD8,0xFF}; ''' обращался к ним где надо.

aiqu6Ait ★★★★
()
Ответ на: комментарий от wolverin

все равно валятся в одного слушателя

А почему нельзя посадить отдельньго? Как я понимаю разбор дестинейшена сервером всё равно происходит даже если ты вайлдкард на всё поставил. Или ты куда-то в кишки на сырые мессаги присел?

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от aiqu6Ait

для начала открой исходники используемой либы и сравни

Чтобы по исходникам этих функций оценить их скорость - надо быть экспертом по ассемблерной оптимизации под конкретный проц. Если им не быть то то что выше написали «memcmp не надо проверять на ноль» это лучшее что ты сможешь учесть.

Исходники там либо на асме, либо выглядят как что-то обфусцированное обычно.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

memcmp не надо проверять на ноль

там нюансы есть в зависимости от платформы, memcmp на 32-битных сравнивает за такт по 4 байта (правда если размер в 3 байта то такого не будет), а strcmp может посимвольно работать, короче я б исходники открыл.

aiqu6Ait ★★★★
()
Последнее исправление: aiqu6Ait (всего исправлений: 1)
Ответ на: комментарий от aiqu6Ait

Дело не в нюансах а в том что эти исходники даже просто понять уже не совсем элементарная задача, а оценивать их производительность - тем более. Так что если не эксперт - лучше ограничиться тем бытовым аргументом и глубже не пытаться лезть.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от aiqu6Ait

Открывал. В частности memcmp из glibc это простыня на больше 200 строк. Да, понять конечно можно, но явно не в виде «посмотрел и сразу всё ясно». И на первый взгляд вообще не верится что ЭТО может быть быстрее простого цикла.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

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

месаги все мои же.

wolverin ★★★
() автор топика
Ответ на: комментарий от firkax

Там же не тело цикла в 200 строк. Там основная логика что при большом количестве элементов выгоднее использовать регистры не байтные, а побольше, что бы за одну операцию сравнивать больше, в зависимости от размера выбирается метод работы. Ну и их несколько реализаций, потому что в последних процессорах Intel, они снова сделали быстрыми аппаратные memcmp, memcpy, strlen, поэтому самый эффективный способ в современных процессорах это одна строчка ассемблерного кода.

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)
Ответ на: комментарий от wolverin

Я переменную определяю как имя привязанное к данным, соответственно если нету имени, нету и переменной. Если определять как просто область в памяти для чтения/изменения, то создать без переменной массив для передачи в memcmp невозможно (особенно если это происходит в runtime).

Массив без определения переменной (с именем) это вот: (int[])({1,2,3})

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

эх так уже пробовал не компилится

.cpp: warning: ISO C++ forbids braced-groups within expressions [-Wpedantic]
  if (memcmp(msg, (int[])({1,2,3}), 3) == 0)
                         ^
.cpp:301:29: warning: left operand of comma operator has no effect [-Wunused-value]
  if (memcmp(msg, (int[])({1,2,3}), 3) == 0)
                             ^
.cpp:301:31: warning: right operand of comma operator has no effect [-Wunused-value]
  if (memcmp(msg, (int[])({1,2,3}), 3) == 0)
                               ^
.cpp:301:32: error: expected ‘;’ before ‘}’ token
  if (memcmp(msg, (int[])({1,2,3}), 3) == 0)
                                ^
                                ;
.cpp:301:33: error: ISO C++ forbids casting to an array type ‘int []’
  if (memcmp(msg, (int[])({1,2,3}), 3) == 0)

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

У тебя ошибка в синтаксисе (я тоже скопипастил выше)

$ cat main.cpp
#include <cstring>
#include <iostream>

int main() {
  std::cout
    << std::memcmp((int[]){1,2,3}, (int[]){1,2,3}, 3 * sizeof(int))
    << "\n";
}
$ g++-14 main.cpp
$ ./a.out 
0
А вместо -Wpedantic можно поставить -std=gnu++20 %)

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)

Не говоря уж о том, что

static inline bool is_jpeg(const char* msg) {
    static const char JPEG_HEADER[] = {0xFF, 0xB8, 0xFF};
    return !memcmp(msg, JPEG_HEADER, sizeof(JPEG_HEADER));
}

// ...

   if (is_jpeg(msg)) {

Прекрасно инлайнится для -Og (уровень оптимизации для Debug, ну не использовать же -O0 для плюсов). А начиная с -O2 к черту выкидывается и сам memcmp.

AlexVR ★★★★★
()

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

Shushundr ★★★★
()

Зачем? Компилятор при оптимизации тут всё равно сделает по своему. Для проверки трёх статичных значений тут простая проверка на три == после оптимизации будет, к гадалке не ходи.

vtVitus ★★★★★
()

Посмотри в godbolt. Все три конструкции делаются на регистрах без вызова функций (так как размер известен и мал). Но первый вариант выполняется чуть иначе. В случае с memcmp просто сравнение чисел.

Reset ★★★★★
()
Ответ на: комментарий от AlexVR

Разве в С++ ключевое слово inline отвечает за инлайнинг? Лучше тогда __attribute__((always_inline)). Я не могу вчитаться нормально в стандарт, вроде отвечает за множественные определения.

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от mittorn
// счастливой отладки
CompareBytes("\xFF\xD8\xFF\xFF", ReadBytes(fd, 8)); 

Правильнее будет так, не тестировал, синтаксис блоков-выражений не помню, ну или просто можно сделать Caps-название, это же макрос.

#define COMPARE_BYTES(A,B) ({    \
  typeof(A) a = A;               \
  typeof(B) b = B;               \
  memcmp((void*)a,b,sizeof(b)-1) \
})

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 7)
Ответ на: комментарий от MOPKOBKA

Можно на шаблон переписать чтобы избежать неправильного использования

template<size_t len>
int CompareBytes(char *data, const char (&str)[len])
{
return memcmp(data, str, len - 1);
}

mittorn ★★★★★
()
Последнее исправление: mittorn (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

Что в плюсах, что в Си inline - только рекомендация, плюс неважное в текущем вопросе.

__attribute__((always_inline)) и т.п. просто так расставлять не надо. Вначале нужен бенчмарк. Если код выглядит как-то так

if (foo) {
  ...
  my_inlined_function(...)
  ...
} else {
  ...
}

То принудительный inline не так и очевиден.

В данном случае, можно было бы вообще остановиться на static.

static inline - более чем достаточно для уровня оптимизации выше -O0, который использовать смысла нет (для плюсов с его шаблонами, inline, constexpr, … для Debug есть -Og). И можно читать как «Эта функция нужна только здесь и можно её заинлайнить, если это будет уместно».

Ну а если уж и упарываться, то искать что-то из:

static __attribute__((always_inline)) bool is_jpeg(const char* msg) {
    // TODO: Check endian
    return ((*(const uint32_t*)msg) & 0x00FFFFFFul) == 0x00FFB8FFul;
}
static __attribute__((always_inline)) bool is_jpeg(const char* msg) {
    return (msg[0] == '\xFF') && (msg[1] == '\xB8') && (msg[2] == '\xFF');
}
AlexVR ★★★★★
()
Ответ на: комментарий от AlexVR

static const uint8_t JPEG_HEADER[] = {0xFF, 0xD8, 0xFF};

тогда может лучше static const std::map (или даже std::unorderedmap ?) и сравнивать уже через switch по известному uint8_t id вместо кучи if по одной и той же месаге???

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 3)
Ответ на: комментарий от wolverin

тогда может лучше глобальный static const std::map (или даже std::unorderedmap ?)

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

annulen ★★★★★
()
Ответ на: комментарий от wolverin

Ну вот я через годболт прогнал для ARM:

#include <string.h>

int check(char *msg) {
    return memcmp(msg, "\xFF\xD8\xFF", 3) == 0;
}
check:
        ldrb    r3, [r0]        @ zero_extendqisi2
        cmp     r3, #255
        beq     .L5
.L2:
        movs    r0, #1
.L3:
        clz     r0, r0
        lsrs    r0, r0, #5
        bx      lr
.L5:
        ldrb    r3, [r0, #1]    @ zero_extendqisi2
        adds    r0, r0, #1
        cmp     r3, #216
        bne     .L2
        ldrb    r3, [r0, #1]    @ zero_extendqisi2
        cmp     r3, #255
        it      eq
        moveq   r0, #0
        bne     .L2
        b       .L3

Насколько могу судить, компилятор сравнивает три байта с указанными значениями, то бишь замысел кода он понял и функцию memcmp «раскрыл» на месте.

vbr ★★★★
()
Ответ на: комментарий от vbr

Бох с ним, ВСЕМ БОЛЬШОЕ СПАСИБО за ликбез, мемцмп сойдет, тем более теперь собираюсь вообще все сообщение если это не жпег целиком парсить на JSON и там буду просто уже std::string сравнивать

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

Если ты на C++ пишешь, там свои приколы, memcmp и прочее байтолюбие там не любят (правда как в данном случае надо - не знаю, наверное какого-нибудь boost-а навернуть с трёхэтажным шаблоном).

А на C лучший вариант тут: memcmp вместо strncmp для оптимизации? (комментарий)

vbr ★★★★
()

варианты с strncmp и memcmp, оба неверные by-design. JPEG не так устроен, чтобы сравнивать по 3 байта. 3-й байт это уже начало следующего тега.

// хотя-бы так, чтобы следовать спецификации
if (msg[0]==0xFF && msg[1]==JPEG_SOI) {
   // jpeg, разбираем остальные теги..
}
MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

)) в таком случае если докапываться, то надо еще и конец байт сравнивать на закрывающий тег, а не просто 2 байта, которые могут совпасть с чем то еще

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)
Ответ на: комментарий от wolverin

) в таком случае если докапываться, то надо еще и конец байт сравнивать на закрывающий тег, а не просто 2 байта, которые могут совпасть с чем то еще

«докопаться» это: не должно быть ситуации когда принимающая сторона вынуждена разбирать заголовки jpeg только чтобы определить jpeg-ли это или строка или ещё что :-) вообще такая ветка не должна была встречаться..

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

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

) выше уже отвечал на этот вопрос, все равно нужно все разбирать, почему бы и не разобрать так же жпег

… хотя была мысль а не посылать ли еще кусок mp4 ))

wolverin ★★★
() автор топика
Ответ на: комментарий от wolverin

месаги все мои же.

так разнеси по топикам или добавь в мессагу «первый байт = тип контента» сообщения.

хотя по хорошему в сообщениях mqtt и так обычно отдают валидный json и там по первому-же байту можно понять объект/массив/строка/число и с jpeg это никак не перепутать.

MKuznetsov ★★★★★
()
Ответ на: комментарий от ya-betmen

сервер самый стандартный москито

на клиента все топики и сообщения приходят в один вызов на один коннект, либа paho

wolverin ★★★
() автор топика
Последнее исправление: wolverin (всего исправлений: 1)
Ответ на: комментарий от MKuznetsov

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

ну причем тут разнос по топикам - т.е. топики сравнивать быстрее чем просто посмотреть о чем месага!?

wolverin ★★★
() автор топика