Очень низкая производительность: lexical_cast

Windows XP SP3. Core 2 Duo 2.0 ГГц. Я считаю, что производительность boost: lexical_cast очень медленная. Хотел выяснить способы ускорения кода. Использование /O 2 оптимизации на Visual С++ 2008 и сравнение с java 1.6 и python 2.6.2 Я вижу следующие результаты.

Целочисленное кастинг:

c++: 
std::string s ;
for(int i = 0; i < 10000000; ++i)
{
    s = boost::lexical_cast<string>(i);
}

java:
String s = new String();
for(int i = 0; i < 10000000; ++i)
{
    s = new Integer(i).toString();
}

python:
for i in xrange(1,10000000):
    s = str(i)

Время, которое я вижу,

С++: 6700 миллисекунд

java: 1178 миллисекунд

python: 6702 миллисекунды

С++ медленнее, чем python, и в 6 раз медленнее, чем java.

Двойное кастинг:

c++:
std::string s ;
for(int i = 0; i < 10000000; ++i)
{
    s = boost::lexical_cast<string>(d);
}

java:
String s = new String();
for(int i = 0; i < 10000000; ++i)
{
    double d = i*1.0;
    s = new Double(d).toString();
}

python:
for i in xrange(1,10000000):
    d = i*1.0
    s = str(d)

Время, которое я вижу,

С++: 56129 миллисекунд

java: 2852 миллисекунды

python: 30780 миллисекунд

Итак, для удвоений С++ на самом деле половина скорости python и в 20 раз медленнее, чем java-решение!!. Любые идеи по улучшению производительности boost: lexical_cast? Оказывается ли это из-за плохой строковой реализации, или мы можем ожидать, что общее снижение производительности на 10 раз связано с использованием библиотек повышения.

Ответ 1

Редактировать 2012-04-11

rve вполне справедливо прокомментировал производительность lexical_cast, указав ссылку:

http://www.boost.org/doc/libs/1_49_0/doc/html/boost_lexical_cast/performance.html

У меня нет доступа прямо сейчас, чтобы увеличить 1.49, но я помню, как сделать мой код быстрее на более старой версии. Поэтому я предполагаю:

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

Оригинальный ответ

Просто чтобы добавить информацию о Барри и Мотти, отличные ответы:

Некоторый фон

Помните, что Boost написана лучшими разработчиками С++ на этой планете и рассмотрены теми же самыми лучшими разработчиками. Если бы lexical_cast был настолько ошибочным, кто-то мог бы взломать библиотеку либо с критикой, либо с кодом.

Я предполагаю, что вы пропустили реальную ценность lexical_cast...

Сравнение яблок и апельсинов.

В Java вы бросаете целое число в строку Java. Вы заметите, что я не говорю о массиве символов или определенной пользователем строке. Вы также заметите, что я не говорю о вашем определяемом пользователем целом. Я говорю о строгом Java Integer и строгой строке Java.

В Python вы более или менее выполняете то же самое.

Как говорится в других сообщениях, вы, по сути, используете эквиваленты Java и Python sprintf (или менее стандартный itoa).

В С++ вы используете очень мощный трансляции. Не мощный в смысле скорости скорости (если вы хотите скорость, возможно, sprintf будет лучше подходит), но мощный в смысле расширяемости.

Сравнение яблок.

Если вы хотите сравнить метод Java Integer.toString, вы должны сравнить его с возможностями C sprintf или С++ ostream.

Решение потока С++ было бы в 6 раз быстрее (на моем g++), чем lexical_cast, и не менее растяжимое:

inline void toString(const int value, std::string & output)
{
   // The largest 32-bit integer is 4294967295, that is 10 chars
   // On the safe side, add 1 for sign, and 1 for trailing zero
   char buffer[12] ;
   sprintf(buffer, "%i", value) ;
   output = buffer ;
}

Решение C sprintf будет в 8 раз быстрее (на моем g++), чем lexical_cast, но гораздо менее безопасным:

inline void toString(const int value, char * output)
{
   sprintf(output, "%i", value) ;
}

Оба решения работают так же быстро или быстрее, чем ваше решение Java (согласно вашим данным).

Сравнение апельсинов.

Если вы хотите сравнить С++ lexical_cast, то вы должны сравнить его с этим псевдокодом Java:

Source s ;
Target t = Target.fromString(Source(s).toString()) ;

Источник и Целевой объект любого типа, который вы хотите, включая встроенные типы, такие как boolean или int, что возможно в С++ из-за шаблонов.

расширяемость? Это грязное слово?

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

В текущем случае, в наивной точке зрения, lexical_cast будет использовать средства потока для преобразования из типа A в поток строк, а затем из этого потока строк в тип B.

Это означает, что до тех пор, пока ваш объект может быть выведен в поток и вводится из потока, вы сможете использовать lexical_cast на нем, не касаясь какой-либо отдельной строки кода.

Итак, каковы используются lexical_cast?

Основные применения лексического литья:

  • Простота использования (эй, С++-трансляция, которая работает для того, чтобы все было ценным!)
  • Объединяя его с тяжелым кодом шаблона, где ваши типы параметризированы, и поэтому вы не хотите иметь дело со спецификой, и вы не хотите знать типы.
  • Все еще потенциально относительно эффективно, если у вас есть базовые знания шаблона, как я покажу ниже.

Здесь очень важна точка 2, потому что это означает, что у нас есть один и только один интерфейс/функция, чтобы передать значение типа в равное или аналогичное значение другого типа.

Это реальная точка, которую вы пропустили, и это то, что стоит в условиях производительности.

Но это так slooooooowwww!

Если вам нужна необработанная скорость, помните, что вы имеете дело с С++, и у вас есть много возможностей эффективно обрабатывать преобразование и, тем не менее, сохранять lexical_cast простоту использования.

Мне потребовалось несколько минут, чтобы посмотреть на источник lexical_cast и прийти с жизнеспособным решением. Добавьте в код С++ следующий код:

#ifdef SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT

namespace boost
{
   template<>
   std::string lexical_cast<std::string, int>(const int &arg)
   {
      // The largest 32-bit integer is 4294967295, that is 10 chars
      // On the safe side, add 1 for sign, and 1 for trailing zero
      char buffer[12] ;
      sprintf(buffer, "%i", arg) ;
      return buffer ;
   }
}

#endif

Включив эту специализацию lexical_cast для строк и ints (определяя макрос SPECIALIZE_BOOST_LEXICAL_CAST_FOR_STRING_AND_INT), мой код быстрее на 5 раз быстрее работал на моем компиляторе g++, а это значит, что ваши данные должны быть похожими на Java.

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

Ответ 2

Вы можете специализировать lexical_cast для типов int и double. Используйте strtod и strtol в своих специализациях.

namespace boost {
template<>
inline int lexical_cast(const std::string& arg)
{
    char* stop;
    int res = strtol( arg.c_str(), &stop, 10 );
    if ( *stop != 0 ) throw_exception(bad_lexical_cast(typeid(int), typeid(std::string)));
    return res;
}
template<>
inline std::string lexical_cast(const int& arg)
{
    char buffer[65]; // large enough for arg < 2^200
    ltoa( arg, buffer, 10 );
    return std::string( buffer ); // RVO will take place here
}
}//namespace boost

int main(int argc, char* argv[])
{
    std::string str = "22"; // SOME STRING
    int int_str = boost::lexical_cast<int>( str );
    std::string str2 = boost::lexical_cast<std::string>( str_int );

    return 0;
}

Этот вариант будет быстрее, чем использование реализации по умолчанию, потому что в реализации по умолчанию есть конструкция объектов с тяжелыми потоками. И это должно быть немного быстрее, чем printf, потому что printf должен анализировать строку формата.

Ответ 3

lexical_cast является более общим, чем конкретный код, который вы используете в Java и Python. Неудивительно, что общий подход, который работает во многих сценариях (лексическое приведение - это немного больше, чем потоковая передача, а затем обратно в и из временного потока) заканчивается медленнее, чем определенные процедуры.

(Кстати, вы можете получить лучшую производительность из Java, используя статическую версию, Integer.toString(int). [1])

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

[1] Комментарий "stepancheg" усомнился в моем намеке на то, что статическая версия может дать лучшую производительность. Здесь источник, который я использовал:

public class Test
{
    static int instanceCall(int i)
    {
        String s = new Integer(i).toString();
        return s == null ? 0 : 1;
    }

    static int staticCall(int i)
    {
        String s = Integer.toString(i);
        return s == null ? 0 : 1;
    }

    public static void main(String[] args)
    {
        // count used to avoid dead code elimination
        int count = 0;

        // *** instance

        // Warmup calls
        for (int i = 0; i < 100; ++i)
            count += instanceCall(i);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; ++i)
            count += instanceCall(i);
        long finish = System.currentTimeMillis();
        System.out.printf("10MM Time taken: %d ms\n", finish - start);


        // *** static

        // Warmup calls
        for (int i = 0; i < 100; ++i)
            count += staticCall(i);

        start = System.currentTimeMillis();
        for (int i = 0; i < 10000000; ++i)
            count += staticCall(i);
        finish = System.currentTimeMillis();
        System.out.printf("10MM Time taken: %d ms\n", finish - start);
        if (count == 42)
            System.out.println("bad result"); // prevent elimination of count
    }
}

Время выполнения, используя JDK 1.6.0-14, VM ​​сервера:

10MM Time taken: 688 ms
10MM Time taken: 547 ms

И в клиентской VM:

10MM Time taken: 687 ms
10MM Time taken: 610 ms

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

Ответ 4

То, что делает лексический актер в вашем коде, можно упростить:

string Cast( int i ) {
    ostringstream os;
    os << i;
    return os.str();
}

К сожалению, каждый раз, когда вы вызываете Cast(): [/p >

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

Thn в вашем собственном коде:

 s = Cast( i );

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

string s = Cast( i );

вместо.

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

Подводя итог, lexical_cast - удобная и полезная функция, но такое удобство приходит (как всегда) с компромиссами в других областях.

Ответ 5

К сожалению, мне еще не хватает репутации для комментариев...

lexical_cast не является прежде всего медленным, потому что он является общим (поиск шаблона происходит во время компиляции, поэтому вызовы виртуальных функций или другие поисковые запросы/разборки не нужны). lexical_cast, на мой взгляд, медленный, потому что он построен на С++ iostreams, которые в первую очередь предназначены для потоковых операций, а не для отдельных преобразований, а потому, что lexical_cast должен проверять и преобразовывать сигналы ошибки iostream. Таким образом:

  • объект потока должен быть создан и уничтожен
  • в приведенном выше примере вывода строки, обратите внимание, что компиляторам С++ трудно избежать копий буфера (альтернативой является форматирование непосредственно в выходной буфер, например, sprintf, хотя sprintf не будет безопасно обрабатывать переполнения буфера )
  • lexical_cast должен проверять ошибки stringstream (ss.fail()), чтобы генерировать исключения при ошибках преобразования

lexical_cast хорош, потому что (IMO) исключения позволяют захватывать все ошибки без дополнительных усилий и потому, что у них есть единый прототип. Я лично не вижу, почему любой из этих свойств требует медленной работы (когда ошибок преобразования не происходит), хотя я не знаю о таких функциях С++, которые бывают быстрыми (возможно, Spirit или boost:: xpressive?).

Изменить: я только что нашел сообщение об использовании BOOST_LEXICAL_CAST_ASSUME_C_LOCALE, чтобы включить оптимизацию "itoa": http://old.nabble.com/lexical_cast-optimization-td20817583.html. Там также есть ссылка

Ответ 6

lexical_cast может быть или не быть таким медленным по отношению к Java и Python, поскольку ваши тесты показывают, что ваши эталонные измерения могут иметь тонкую проблему. Любые распределения/освобождения рабочей области, выполняемые лексическим методом, или используемые им методы iostream, измеряются вашими эталонами, потому что С++ не откладывает эти операции. Однако в случае Java и Python соответствующие дезадаптации фактически могут быть отложены до будущего цикла сбора мусора и пропущены с помощью эталонных измерений. (Если только цикл GC случайно возникает во время проведения эталонного теста, и в этом случае вы слишком много измеряете). Таким образом, трудно точно знать, не изучая специфику реализаций Java и Python, сколько "затрат" следует отнести на отложенную нагрузку GC, которая может (или не быть) в конечном итоге навязываться.

Этот вид проблемы, очевидно, может применяться ко многим другим языковым ориентирам на языке С++ vs garbage.

Ответ 7

Как сказал Барри, lexical_cast очень общий, вы должны использовать более конкретную альтернативу, например, проверить itoa ( int->string) и atoi (string -> int).

Ответ 8

если скорость вызывает беспокойство, или вас просто интересует, насколько быстро такие отбрасывания могут быть с С++, есть заинтересованная thread относительно нее,

Boost.Spirit 2.1 (который должен быть выпущен с Boost 1.40), кажется, очень быстро, даже быстрее, чем C-эквиваленты (strtol(), atoi() и т.д.).

Ответ 9

Я использую это очень быстрое решение для типов POD...

namespace DATATYPES {

    typedef std::string   TString;
    typedef char*         TCString;
    typedef double        TDouble;
    typedef long          THuge;
    typedef unsigned long TUHuge;
};

namespace boost {

template<typename TYPE>
inline const DATATYPES::TString lexical_castNumericToString(

                                const TYPE& arg, 
                                const DATATYPES::TCString fmt) {

    enum { MAX_SIZE = ( std::numeric_limits<TYPE>::digits10 + 1 )  // sign
                                                            + 1 }; // null
    char buffer[MAX_SIZE] = { 0 };

    if (sprintf(buffer, fmt, arg) < 0) {
        throw_exception(bad_lexical_cast(typeid(TYPE),
                                         typeid(DATATYPES::TString)));
    }
    return ( DATATYPES::TString(buffer) );
}

template<typename TYPE>
inline const TYPE lexical_castStringToNumeric(const DATATYPES::TString& arg) {

    DATATYPES::TCString end = 0;
    DATATYPES::TDouble result = std::strtod(arg.c_str(), &end);

    if (not end or *end not_eq 0) {
        throw_exception(bad_lexical_cast(typeid(DATATYPES::TString),
                                         typeid(TYPE)));
    }
    return TYPE(result);
}

template<>
inline DATATYPES::THuge lexical_cast(const DATATYPES::TString& arg) {
    return (lexical_castStringToNumeric<DATATYPES::THuge>(arg));
}

template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::THuge& arg) {
    return (lexical_castNumericToString<DATATYPES::THuge>(arg,"%li"));
}

template<>
inline DATATYPES::TUHuge lexical_cast(const DATATYPES::TString& arg) {
    return (lexical_castStringToNumeric<DATATYPES::TUHuge>(arg));
}

template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::TUHuge& arg) {
    return (lexical_castNumericToString<DATATYPES::TUHuge>(arg,"%lu"));
}

template<>
inline DATATYPES::TDouble lexical_cast(const DATATYPES::TString& arg) {
    return (lexical_castStringToNumeric<DATATYPES::TDouble>(arg));
}

template<>
inline DATATYPES::TString lexical_cast(const DATATYPES::TDouble& arg) {
    return (lexical_castNumericToString<DATATYPES::TDouble>(arg,"%f"));
}

} // end namespace boost