Подтвердить что ты не робот

С++ map <std::string> vs map <char *> производительность (я знаю, "снова?" )

Я использовал карту с ключом std::string, и пока все работало нормально, я не получал ожидаемого результата. Я искал места для оптимизации и улучшения всего лишь немного, и когда коллега сказал: "Этот строковый ключ будет медленным".

Я читал десятки вопросов, и они последовательно говорят:

"не использовать char * как ключ"
"std::string ключи никогда не являются вашим узким местом"
"разница в производительности между a char * и std::string - это миф."

Я неохотно попробовал ключ char *, и была разница, большая разница.

Я сварил проблему до простого примера:

#include <stdio.h>
#include <stdlib.h>
#include <map>

#ifdef USE_STRING

#include <string>
typedef std::map<std::string, int> Map;

#else

#include <string.h>
struct char_cmp { 
    bool operator () (const char *a,const char *b) const 
    {
        return strcmp(a,b)<0;
    } 
};
typedef std::map<const char *, int, char_cmp> Map;

#endif

Map m;

bool test(const char *s)
{
    Map::iterator it = m.find(s);
    return it != m.end();
}

int main(int argc, char *argv[])
{
    m.insert( Map::value_type("hello", 42) );

    const int lcount = atoi(argv[1]);
    for (int i=0 ; i<lcount ; i++) test("hello");
}

Сначала версия std::string:

$ g++ -O3 -o test test.cpp -DUSE_STRING
$ time ./test 20000000
real    0m1.893s

Далее версия 'char *':

g++ -O3 -o test test.cpp             
$ time ./test 20000000
real    0m0.465s

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

Использование клавиши char * - это боль, чтобы справиться с освобождением ключа и просто не чувствует себя хорошо. Эксперты С++, что мне не хватает? Любые мысли или предложения?

4b9b3361

Ответ 1

Вы используете const char * в качестве ключевого слова для find(). Для карты, содержащей const char*, это правильный тип, который ожидает find, и поиск может быть выполнен напрямую.

Карта, содержащая std::string, ожидает, что параметр find() будет std::string, поэтому в этом случае const char* сначала должен быть преобразован в std::string. Вероятно, это разница, которую вы видите.

Ответ 2

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

С другой стороны, функции из <algorithm> не страдают от этого, например lower_bound определяется как:

template< class ForwardIt, class T >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );

template< class ForwardIt, class T, class Compare >
ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );

Таким образом, альтернативой может быть:

std::vector< std::pair< std::string, int > >

И тогда вы могли бы сделать:

std::lower_bound(vec.begin(), vec.end(), std::make_pair("hello", 0), CompareFirst{})

Где CompareFirst определяется как:

struct CompareFirst {
     template <typename T, typename U>
     bool operator()(T const& t, U const& u) const { return t.first < u.first; }
};

Или даже создать полностью настраиваемый компаратор (но это немного сложнее).

A vector пары, как правило, более эффективна при нагрузках с высокой нагрузкой, поэтому действительно хранить конфигурацию, например.

Я рекомендую предоставить методы для переноса доступа. lower_bound довольно низкоуровневый.

Ответ 3

Если ваш в С++ 11, конструктор копирования не называется если строка не изменена. Поскольку std::string является конструкцией С++, для получения строковых данных требуется не менее 1 разыменования.

Я предполагаю, что время будет занято дополнительным разыменованием (что если сделано 10000 раз дорогостоящим), а std::string, скорее всего, проведет соответствующие проверки нулевого указателя, который снова ест циклы.

Ответ 4

После компиляции 2 "Hello" строковые литералы будут иметь одинаковый адрес памяти. В случае char * вы используете эти адреса памяти в качестве ключей.

В случае string каждый "Hello" будет преобразован в другой объект. Это небольшая часть (действительно очень маленькая) вашей разницы в производительности.

Большая часть может заключаться в том, что, поскольку все используемые вами "Hello" имеют одинаковый адрес памяти, strcmp всегда будет иметь 2 эквивалентных указателя char, и я уверен, что он проверяет этот случай на раннем этапе: ) Таким образом, он никогда не будет переименовывать все символы, но сравнение std::string будет.

Ответ 5

Сохраните std::string как указатель, а затем вы потеряете служебные данные конструктора копии.

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

Причина std::string медленная - это то, что она сама создает. Вызывает конструктор копирования, а затем в конце вызывает удаление. Если вы создаете строку в куче, вы теряете конструкцию копии.