про сравнение строк
От: niXman Ниоткуда https://github.com/niXman
Дата: 22.11.16 13:45
Оценка: :)
занудамод.

этот фрагмент кода:
static Level convertFromString(const char* levelStr) {
    if ((strcmp(levelStr, "GLOBAL") == 0) || (strcmp(levelStr, "global") == 0))
        return Level::Global;
    if ((strcmp(levelStr, "DEBUG") == 0) || (strcmp(levelStr, "debug") == 0))
        return Level::Debug;
    if ((strcmp(levelStr, "INFO") == 0) || (strcmp(levelStr, "info") == 0))
        return Level::Info;
    if ((strcmp(levelStr, "WARNING") == 0) || (strcmp(levelStr, "warning") == 0))
        return Level::Warning;
    if ((strcmp(levelStr, "ERROR") == 0) || (strcmp(levelStr, "error") == 0))
        return Level::Error;
    if ((strcmp(levelStr, "FATAL") == 0) || (strcmp(levelStr, "fatal") == 0))
        return Level::Fatal;
    if ((strcmp(levelStr, "VERBOSE") == 0) || (strcmp(levelStr, "verbose") == 0))
        return Level::Verbose;
    if ((strcmp(levelStr, "TRACE") == 0) || (strcmp(levelStr, "trace") == 0))
        return Level::Trace;
    return Level::Unknown;
}

транслируется в этот ассембелрный код(примеры для GCC и Intel).
Intel в данном случае поступил разуменее.
но, как мы видим, strcmp() никто не зовет.

теперь давайте изменим этот код так, чтоб вместо strcmp() использовалась memcmp():
#define mymemcmp(l, r) memcmp(l, r, sizeof(r)-1)

Level convertFromString(const char* levelStr) {
  if ((mymemcmp(levelStr, "GLOBAL") == 0) || (mymemcmp(levelStr, "global") == 0))
    return Level::Global;
  if ((mymemcmp(levelStr, "DEBUG") == 0) || (mymemcmp(levelStr, "debug") == 0))
    return Level::Debug;
  if ((mymemcmp(levelStr, "INFO") == 0) || (mymemcmp(levelStr, "info") == 0))
    return Level::Info;
  if ((mymemcmp(levelStr, "WARNING") == 0) || (mymemcmp(levelStr, "warning") == 0))
    return Level::Warning;
  if ((mymemcmp(levelStr, "ERROR") == 0) || (mymemcmp(levelStr, "error") == 0))
    return Level::Error;
  if ((mymemcmp(levelStr, "FATAL") == 0) || (mymemcmp(levelStr, "fatal") == 0))
    return Level::Fatal;
  if ((mymemcmp(levelStr, "VERBOSE") == 0) || (mymemcmp(levelStr, "verbose") == 0))
    return Level::Verbose;
  if ((mymemcmp(levelStr, "TRACE") == 0) || (mymemcmp(levelStr, "trace") == 0))
    return Level::Trace;
  return Level::Unknown;
}

результат.
GCC сейчас уже сравнивает строки так, буд-то бы это интегральные типы. т.е. в псевдокоде типа этого: int v0 = *(int64*)"GLOBAL", v1 = *(int64*)levelStr; if (v0==v1) ...

Intel же снова умничает.
в некоторых if`ах он использует memcmp(), а в некоторых поступает как GCC. похоже, зависит от длины константной строки...

корректность использования memcmp() для константных длин — отдельный вопрос, но можем сделать и так:
#define mymemcmp(l, s, r) s == sizeof(r)-1 && 0 == memcmp(l, r, s)

Level convertFromString(const char* levelStr) {
  std::size_t s = strlen(levelStr);
  if ((mymemcmp(levelStr, s, "GLOBAL")) || (mymemcmp(levelStr, s, "global")))
    return Level::Global;
  if ((mymemcmp(levelStr, s, "DEBUG")) || (mymemcmp(levelStr, s, "debug")))
    return Level::Debug;
  if ((mymemcmp(levelStr, s, "INFO")) || (mymemcmp(levelStr, s, "info")))
    return Level::Info;
  if ((mymemcmp(levelStr, s, "WARNING")) || (mymemcmp(levelStr, s, "warning")))
    return Level::Warning;
  if ((mymemcmp(levelStr, s, "ERROR")) || (mymemcmp(levelStr, s, "error")))
    return Level::Error;
  if ((mymemcmp(levelStr, s, "FATAL")) || (mymemcmp(levelStr, s, "fatal")))
    return Level::Fatal;
  if ((mymemcmp(levelStr, s, "VERBOSE")) || (mymemcmp(levelStr, s, "verbose")))
    return Level::Verbose;
  if ((mymemcmp(levelStr, s, "TRACE")) || (mymemcmp(levelStr, s, "trace")))
    return Level::Trace;
  return Level::Unknown;
}

результат.


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


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


зы
я бы решил это как-то так:
Level convertFromString(const char* levelStr) {
  if ( !levelStr ) return Level::Unknown;
  switch (*levelStr) {
    case 'G': case 'g': return Level::Global;
    case 'D': case 'd': return Level::Debug;
    case 'I': case 'i': return Level::Info;
    case 'W': case 'w': return Level::Warning;
    case 'E': case 'e': return Level::Error;
    case 'F': case 'f': return Level::Fatal;
    case 'V': case 'v': return Level::Verbose;
    case 'T': case 't': return Level::Trace;
    default:            return Level::Unknown;
  }
}
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: про сравнение срок
От: VTT http://vtt.to
Дата: 22.11.16 14:06
Оценка: +1
Код с memcmp приводит в выходу за границу массива когда levelStr короткая.
Код с strcmp тоже сломан, так как gLoBAl уже не прокатит.
Если хочется оптимизации, то не стоит сразу нырять в ассемблер.
Можно проверить длину levelStr, если она укладывается в диапазон длин проверяемых названий, то содержимое levelStr можно копировать в верхнем регистре в буффер и смело сравнивать по одному разу с каждым названием в том же регистре при помощи memcmp. Если названий много и они длинные, то тогда может иметь смысл вместо перебора сделать маp в коком-то виде.
А стоит ли этот кусок вообще оптимизировать? Может он всего один раз за все время жизни приложения вызывается.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re[2]: про сравнение срок
От: niXman Ниоткуда https://github.com/niXman
Дата: 22.11.16 14:15
Оценка:
Здравствуйте, VTT, Вы писали:

VTT>Код с memcmp приводит в выходу за границу массива когда levelStr короткая.

я же об этом написал и привел поправленный вариант.

VTT>Код с strcmp тоже сломан, так как gLoBAl уже не прокатит.

а должно? в исходной задаче этого нет.

VTT>Можно проверить длину levelStr, если она укладывается в диапазон длин проверяемых названий

в предпоследнем фрагменте кода я почти так и сделал...почти...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: про сравнение срок
От: VTT http://vtt.to
Дата: 22.11.16 14:30
Оценка:
Здравствуйте, niXman, Вы писали:

X>Здравствуйте, VTT, Вы писали:


VTT>>Код с memcmp приводит в выходу за границу массива когда levelStr короткая.

X>я же об этом написал и привел поправленный вариант.

А когда я смотрел пост в первый раз он был намного короче...

VTT>>Код с strcmp тоже сломан, так как gLoBAl уже не прокатит.

X>а должно? в исходной задаче этого нет.

Так там и условия задачи нет, и тестов для проверки тоже вроде нет.
Но раз проверяются оба регистра, то наверное эта проверка может считаться не чувствительной к регистру.
Говорить дальше не было нужды. Как и все космонавты, капитан Нортон не испытывал особого доверия к явлениям, внешне слишком заманчивым.
Re: про сравнение срок
От: watchmaker  
Дата: 22.11.16 14:31
Оценка:
Здравствуйте, niXman, Вы писали:

X>я бы решил это как-то так:

Но ты же понимаешь, что твой код выдаёт другие ответы?
Например, в твоём варианте convertFromString("WTF") == Level::Warning, но Level::Unknown в исходном.


X>вопрос эффективности я рассматривать не буду

А в чём тогда вопрос-то? Если на эффективность плевать, то и исходный вариант сойдёт. Если же эффективность важна, то просто используют PHF. Например, в виде готовой утилиты gperf, которая по списку строк сама сгенерирует код похожий на твой, но с куда меньшим количеством багов (то есть, который будет корректно обрабатывать случаи когда строка ни с чем не совпадает вообще).
Re[4]: про сравнение срок
От: niXman Ниоткуда https://github.com/niXman
Дата: 22.11.16 14:39
Оценка:
Здравствуйте, VTT, Вы писали:

VTT>А когда я смотрел пост в первый раз он был намного короче...

пост не редактировался, иначе было бы видно.

VTT>Так там и условия задачи нет, и тестов для проверки тоже вроде нет.

VTT>Но раз проверяются оба регистра, то наверное эта проверка может считаться не чувствительной к регистру.
ну хз. я не любитель гадать, сделал в соответствии с исходным кодом.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[2]: про сравнение срок
От: niXman Ниоткуда https://github.com/niXman
Дата: 22.11.16 14:42
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Но ты же понимаешь, что твой код выдаёт другие ответы?

W>Например, в твоём варианте convertFromString("WTF") == Level::Warning, но Level::Unknown в исходном.
ну...теоритически да. на практике же, эта функция парсит лог, который сама и сгенерила.

X>>вопрос эффективности я рассматривать не буду

W>А в чём тогда вопрос-то?
я об эфектиности ассемблерных кодов.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: про сравнение срок
От: uzhas Ниоткуда  
Дата: 22.11.16 14:48
Оценка: +2
Здравствуйте, niXman, Вы писали:

X>занудамод.

....
X>меня всегда удивляли подобные конструкции, ветвления на основе сравнения строк, иногда множество лишних сравнений пока не найдется нужная ветка.

вот тут присоединюсь. всегда вгоняет в депрессию подобный код
лично я предпочитаю в таких случаях делать ассоциативные контейнеры + поиск
мета-код
map/flat_map/unordered_map<string, Level> levelsIndex = {
  {"global", Level::Global},
  {"info", Level::Info},
  {"error", Level::Error}
};

Level detectLevel(const string& level)
{
  auto lowercaseLevel = toLower(level);
  auto it = levelsIndex.find(lowerCaseLevel);
  return  (it == levelsIndex.end()) ? Level::Unknown : it->second;
}

лишь один if — это гораздо проще, нежели 8 if. декларативно и компактно записывается маппинг.
причем масштабируется легко с увеличением вариантов (все равно 1 if будет)

то есть мои предпочтения по порядку:
1) use switch
2) use map
3) use many if-s
Re: про сравнение срок
От: uzhas Ниоткуда  
Дата: 22.11.16 15:15
Оценка:
Здравствуйте, niXman, Вы писали:

X>про сравнение срок


хорошо, что лишь в одной букве опечатался
Re[2]: про сравнение срок
От: smeeld  
Дата: 22.11.16 15:57
Оценка:
Здравствуйте, uzhas, Вы писали:

U>лично я предпочитаю в таких случаях делать ассоциативные контейнеры + поиск


Уверены, что string в качестве ключа для map/unordered_map есть правильное решение?
Re: про сравнение строк
От: smeeld  
Дата: 22.11.16 16:03
Оценка:
Здравствуйте, niXman, Вы писали:

X>к этим тестам я пришел после прочтения кода этого проекта. меня всегда удивляли подобные конструкции, ветвления на основе сравнения строк, иногда множество лишних сравнений пока не найдется нужная ветка.


Большинство строк в прогах, особено в парсерах, по длине не превосходят размеры SSE2/AVX регистров, эти строки без проблем целиком загружаются в эти регистры и сравниваются одной ассемблерной инструкцией, нужно только написать свою реализацию strcmp/memcmp специально для сравнения небольших строк.
Re: про сравнение строк
От: antropolog  
Дата: 22.11.16 16:07
Оценка:
Здравствуйте, niXman, Вы писали:

Сравнение строк всё равно не эффективно. Но лично я бы использовал (и почти всегда использую) тернарный оператор для подобного "паттерн-матчинга":

static Level convertFromString(const char* levelStr) {
  return levelStr == nullptr ? Level::Unknwonwn 
    : strcasecmp(levelStr, "global"  ) == 0 ? Level::Global
    : strcasecmp(levelStr, "debug"   ) == 0 ? Level::Debug
    : strcasecmp(levelStr, "info"    ) == 0 ? Level::Info
    : strcasecmp(levelStr, "warning" ) == 0 ? Level::Warning
    : strcasecmp(levelStr, "error"   ) == 0 ? Level::Error
    : strcasecmp(levelStr, "fatal"   ) == 0 ? Level::Fatal
    : strcasecmp(levelStr, "verbose" ) == 0 ? Level::Verbose
    : strcasecmp(levelStr, "trace"   ) == 0 ? Level::Trace
    : Level::Unknown;
}
Re: про сравнение строк
От: smeeld  
Дата: 22.11.16 16:11
Оценка:
Здравствуйте, niXman, Вы писали:

Кстати, кто в теме, есть ли в boost какое-либо решение, специально для ускорения сравнения строк?
Re[2]: про сравнение строк
От: _NN_  
Дата: 22.11.16 17:35
Оценка:
Здравствуйте, antropolog, Вы писали:

A>Здравствуйте, niXman, Вы писали:


A>Сравнение строк всё равно не эффективно. Но лично я бы использовал (и почти всегда использую) тернарный оператор для подобного "паттерн-матчинга":


A>
A>static Level convertFromString(const char* levelStr) {
A>  return levelStr == nullptr ? Level::Unknwonwn 
A>    : strcasecmp(levelStr, "global"  ) == 0 ? Level::Global
A>    : strcasecmp(levelStr, "debug"   ) == 0 ? Level::Debug
A>    : strcasecmp(levelStr, "info"    ) == 0 ? Level::Info
A>    : strcasecmp(levelStr, "warning" ) == 0 ? Level::Warning
A>    : strcasecmp(levelStr, "error"   ) == 0 ? Level::Error
A>    : strcasecmp(levelStr, "fatal"   ) == 0 ? Level::Fatal
A>    : strcasecmp(levelStr, "verbose" ) == 0 ? Level::Verbose
A>    : strcasecmp(levelStr, "trace"   ) == 0 ? Level::Trace
A>    : Level::Unknown;
A>}
A>


Этот код не эквивалентен оригиналу в начале.
Тут "Global" сработает правильно , а в оригинальном нет.
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[2]: про сравнение строк
От: niXman Ниоткуда https://github.com/niXman
Дата: 22.11.16 18:56
Оценка:
Здравствуйте, smeeld, Вы писали:

S>Кстати, кто в теме, есть ли в boost какое-либо решение, специально для ускорения сравнения строк?

ничего такого не видел, но видел быстрое сравнение для boost::uuid:
https://github.com/boostorg/uuid/blob/develop/include/boost/uuid/detail/uuid_x86.hpp
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: про сравнение строк
От: kov_serg Россия  
Дата: 22.11.16 22:12
Оценка:
Здравствуйте, niXman, Вы писали:

X>занудамод.

...
X>зы
X>я бы решил это как-то так:
X>
X>Level convertFromString(const char* levelStr) {
X>  if ( !levelStr ) return Level::Unknown;
X>  switch (*levelStr) {
X>    case 'G': case 'g': return Level::Global;
X>    case 'D': case 'd': return Level::Debug;
X>    case 'I': case 'i': return Level::Info;
X>    case 'W': case 'w': return Level::Warning;
X>    case 'E': case 'e': return Level::Error;
X>    case 'F': case 'f': return Level::Fatal;
X>    case 'V': case 'v': return Level::Verbose;
X>    case 'T': case 't': return Level::Trace;
X>    default:            return Level::Unknown;
X>  }
X>}
X>


Есть готовый компилятор для подобных забав
http://www.colm.net/open-source/ragel/
Re[2]: про сравнение строк
От: niXman Ниоткуда https://github.com/niXman
Дата: 23.11.16 09:13
Оценка:
Здравствуйте, smeeld, Вы писали:

S>Кстати, кто в теме, есть ли в boost какое-либо решение, специально для ускорения сравнения строк?

вот: https://github.com/WojciechMula/simd-string
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.