суббота, 1 марта 2008 г.

Compare string performance using C#,C++(STL,boost) and C - part 1, C#

Compare string performance using C#,C++(STL,boost) and C - part 2, C\C++
Compare string performance using C#,C++(STL,boost) and C - part 3, results

This comparation was inspired by STL vector performance

Task -
you have array of strings "word1-word2", "word3-word4", "word5-word6", you need to transform it to string "word1:word2:word3:word4:word5:word6"


I was doing this on Windows so I use QueryPerformanceCounter to test performance to keep performance testing the same with C# and C++.

Main C# piece of code:

string r = ":".join(array1.SelectMany(x => x.Split('-')));


Whole C# code:

using System;

using System.Linq;

using System.Collections.Generic;

using System.Runtime.InteropServices;

 

namespace MainProgramm

{

    static class string_extentions

    {

        public static string join(this string a, IEnumerable<string> elems)

        {

            string result = "";

            foreach (string elem in elems) {

                if (result.Length != 0)

                    result += a;

                result += elem;

            }

            return result;

        }

    }

    internal class HiPerfTimer

    {

        [DllImport("Kernel32.dll")]

        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

 

        [DllImport("Kernel32.dll")]

        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

 

        private long startTime, stopTime;

        private long freq;

        public HiPerfTimer(){QueryPerformanceFrequency(out freq);}

        public void    start   (){QueryPerformanceCounter(out startTime);}

        public void    stop    (){QueryPerformanceCounter(out stopTime);}

        public double   duration{

            get{return (double)(stopTime - startTime) / (double)freq;}

        }

    }

 

    class Program

    {

        static void test_functional()

        {

            string[] array1 = { "word1-word2", "word3-word4", "word5-word6", "word7-word8", "word9-word0" };

            HiPerfTimer pt = new HiPerfTimer();

            pt.start();

            for (int i = 0; i < 10000; ++i) {

                string r = ":".join(array1.SelectMany(x => x.Split('-')));

            }

            pt.stop();

 

            Console.WriteLine("C# functional - {0} sec\n", pt.duration); // print the duration of the timed code

        }

        static void Main(string[] args){

            test_string_builder();

            test_functional();

        }

    }

}

пятница, 29 февраля 2008 г.

Сравнение производительность С#,C,C++ часть 3, результаты

В предыдущих сериях:
Разединяем строки...обьединяем строки.
Сравнение производительность С#,C,C++ часть 1, код на C#
Сравнение производительность С#,C,C++ часть 2, код на C++ и на С

Изначально идея была навеяна STL vector performance

Цели которые я перед собой ставил - сравнить производительность кода на чистом С с кодом на С++ с использованием STL/boost при работе со строками на более-менее похожей на реальную задаче. C# я добавил в тесты когда увидел огромную разницу в производительности.

Напомню задачу:
нужно из таких строк: "word1-word2", "word3-word4", "word5-word6"
получить вот такую строку:
word1:word2:word3:word4:word5:word6

Код на С.С++ я компилил в VS 2005, с оптимизацием по скорости в Release версии. Код на С# в VS 2008 Express Edition.

Сводная табличка результатов, отсортированная по скорости.







boost::split, boost::join0.596665 sec
boost::split, boost::join, no std:copy0.492545 sec
simple select_many0.100862 sec
boost::tokenizer0.086117 sec
plain C++0.053814 sec
C#0,041314 sec
plain C0.003925 sec


Выводы - я повременю с уходом в монастырь только потому что код на С все-таки оказался в 10 раз быстрее кода на С#. И в принципе его еще можно немного разогнать.
Хотя код на C# я не оптимизировал, даже скорее наоборот, написал максимально "красиво" с использованием лямбд/генериков не заботясь о скорости. Цели написать быстрый код на C# не было. Когда я переписал с испльзованием StringBuilder разница по скорости с кодом на С сократилась до 4х раз. Уверен его можно еще разогнать.

Что же касается кода на С++ с испльзованием STL+boost - либо я не знаком с каким-то особым способом его использования/оптимизации...либо все очень и очень плохо.

Особенно удручающе выглядит использование Boost String Algorithms Library.

Послесловие - претензии к коду на С++ принимаются только если будет предложено что-то лучшее :). Я старался писать в "идиоматичном" С++, по крайнем мере как я его понимаю. Если кто-то может написать более эффективную реализацию на С++ с использованием std::string, std::vector и прочего std::, а также желательно boost и при этом не скатываясь к варианту на С - буду только рад.
Претензии к реализации тоже не очень принимаются...я и сам вижу что результат будет не правильным например на такой строке "word1--word2" или на такой "word1-word2-" и т.д. Но я решал конкретную задачу а не писал обобщенный способ разбиения строк.
Также я прекрасно вижу что алгоритмы на С++ и C# отличаются от алгоритма на С. Но - и тут я старался написать на "идиоматичном" С, и получилось именно вот так.

Сравнение производительность С#,C,C++ часть 2, код на C++ и на С

Напомню задачу:
нужно из таких строк: "word1-word2", "word3-word4", "word5-word6"
получить вот такую строку:
word1:word2:word3:word4:word5:word6


Использовал 6 разных вариантов решения этой задачи.
1. C использованием boost/algorithm/string
2. C использованием boost/algorithm/string, но обрабатывал промежуточный результат
3. Использовал select_many из Разединяем строки...обьединяем строки.
4. Использовал boost/tokenizer
5. Использовал реализацию "в лоб" на С++.
6. Реализовал все это дело практически на чистом С для частного случая.

Код:

#include "stdafx.h"

 

#include <string>

#include <algorithm>

 

#include <boost/tokenizer.hpp>

#include <boost/algorithm/string.hpp>

#include <boost/bind.hpp>

 

 

class HiPerfTimer

{

public:

    void start()

    {

        QueryPerformanceFrequency(&freq);

        QueryPerformanceCounter(&begin);

    }

    void stop(){QueryPerformanceCounter(&end);}

 

    double duration(){return (double)(end.QuadPart-begin.QuadPart)/freq.QuadPart;}

protected:

    LARGE_INTEGER begin,end,freq;

};

 

 

template<class _container_type,class _Fn1> inline

_container_type select_many(const _container_type& container, _Fn1& _Func)

{   

    _container_type result;

    for(_container_type::const_iterator v = container.begin();v!=container.end();++v)

    {

        _container_type tmp = _Func(*v);

        for(_container_type::const_iterator v1 = tmp.begin();v1!=tmp.end();++v1)

            result.push_back(*v1);

    }

    return result;

}

 

std::vector<std::string>    split_string    (const std::string& str, const std::string& delimiters)

{

    std::vector<std::string> v;

    size_t offset = 0;

    while(true)

    {

        size_t token_start = str.find_first_not_of(delimiters, offset);

        if (token_start == std::string::npos)

        {

            v.push_back( str.substr(token_start,str.length()  - token_start));

            break;

        }

        size_t token_end = str.find_first_of(delimiters, token_start);

        if (token_end == std::string::npos)

        {

            v.push_back(str.substr(token_start));

            break;

        }

        v.push_back(str.substr(token_start, token_end - token_start));

        offset = token_end;

    }

 

    return v;

}

 

std::string    join_string        (const std::vector<std::string>& v,const std::string& delimiters)

{

    std::string s;

    size_t reserve = 0;

    for(std::vector<std::string>::const_iterator the_str = v.begin();the_str!=v.end();++the_str)

        reserve+=the_str->length();

    reserve+=delimiters.length()*v.size();

    s.reserve(reserve);

    for(std::vector<std::string>::const_iterator the_str = v.begin();the_str!=v.end();++the_str)   

    {

        if(!s.empty())

            s+=delimiters;

        s+=*the_str;

    }

    return s;

}

 

 

char* do_the_job_in_plain_c(char** array, size_t size, char c1,char c2)

{

    size_t desc_size=size;

 

    for(size_t i=0;i<size;++i)

        desc_size+=strlen(array[i]);

    char* res_str = new char[desc_size];

    char* res_str_t = res_str;

    for(size_t i=0;i<size;++i,*res_str_t++ = c2)

    {

        char* p = array[i];

        do

        {

            *res_str_t++ = (*p == c1)?c2:*p;

        }while(*(++p));

    }

    res_str[desc_size-1] = 0;

    return res_str;

}

 

#define NUM_SAMPLES 10000

void test_select_many(std::vector<std::string>& w){

    LARGE_INTEGER begin,end,freq;

    double duration;

    QueryPerformanceFrequency(&freq);

 

    QueryPerformanceCounter(&begin);

    for (int i = 0; i < NUM_SAMPLES; ++i)

        std::string res = join_string(select_many(w,bind(split_string,_1,("-"))),":");

    QueryPerformanceCounter(&end);

 

    duration = (double)(end.QuadPart-begin.QuadPart)/freq.QuadPart;

    printf("select_many- %f sec\n",duration);

}

 

void test_boost_string_algorithm(std::vector<std::string>& w){

    HiPerfTimer pt;

    pt.start();

 

    for (int i = 0; i < NUM_SAMPLES; ++i)

    {

        std::string result;

        typedef std::vector< std::string > split_vector_type;

        split_vector_type split_vec_res;

        for(std::vector<std::string>::const_iterator v = w.begin();v!=w.end();++v){

            split_vector_type split_vec;

            boost::split( split_vec, *v, boost::is_any_of("-") );

            std::copy(split_vec.begin(),split_vec.end(),std::back_inserter(split_vec_res));

        }

        std::string res = boost::join(split_vec_res,":");

    }

    pt.stop();

    printf("boost::split, boost::join- %f sec\n",pt.duration());

}

 

void test_boost_string_algorithm_opt(std::vector<std::string>& w)

{

    HiPerfTimer pt;

    pt.start();

 

    for (int i = 0; i < NUM_SAMPLES; ++i) {

        std::string result;

        typedef std::vector< std::string > split_vector_type;

        split_vector_type split_vec_res;

        for(std::vector<std::string>::const_iterator v = w.begin();v!=w.end();++v){

            split_vector_type split_vec;

            boost::split( split_vec, *v, boost::is_any_of("-") );

            if(!result.empty())

                result+=":";

            result += boost::join(split_vec_res,":");

 

        }

    }

    pt.stop();

 

    printf("boost::split, boost::join, no std:copy- %f sec\n",pt.duration());

}

 

 

void test_boost_tokenizer(std::vector<std::string>& w)

{

    HiPerfTimer pt;

    pt.start();

 

    typedef boost::tokenizer<boost::char_separator<char> > tokenizer;

    for (int i = 0; i < NUM_SAMPLES; ++i) {

        std::string result;

        for(std::vector<std::string>::const_iterator v = w.begin();v!=w.end();++v){

            boost::char_separator<char> sep("-");

            tokenizer tok(*v,sep);   

            for (tokenizer::iterator tok_iter = tok.begin();tok_iter != tok.end(); ++tok_iter){

                if(!result.empty())

                    result+=":";

                result+=*tok_iter;

            }

        }

    }

    pt.stop();   

    printf("boost::tokenizer - %f sec\n",pt.duration());

}

 

void test_plain_cpp(std::vector<std::string>& w)

{

    HiPerfTimer pt;

    pt.start();

 

    for (int i = 0; i < NUM_SAMPLES; ++i) {

        std::string result;

 

        size_t length = w.size()+1;

        for(std::vector<std::string>::const_iterator v = w.begin();v!=w.end();++v)

            length+=v->length();

        result.reserve(length);           

 

        for(std::vector<std::string>::const_iterator v = w.begin();v!=w.end();++v){

            const std::vector<std::string>& w1 = split_string(*v,"-");

            for(std::vector<std::string>::const_iterator v1 = w1.begin();v1!=w1.end();++v1){

                if(!result.empty())

                    result+=":";

                result+=*v1;

            }

        }

    }

    pt.stop();   

    printf("plain C++ - %f sec\n",pt.duration());

}

 

void test_plain_c(char** array,size_t size)

{

    HiPerfTimer pt;

    pt.start();

    for (int i = 0; i < NUM_SAMPLES; ++i)

        std::auto_ptr<char> res_str ( do_the_job_in_plain_c(array,size,'-',':') );

    pt.stop();   

    printf("plain C - %f sec\n",pt.duration());

}

int _tmain(int argc, _TCHAR* argv[])

{

    std::vector<std::string> w;

    w.push_back("word1-word2");w.push_back("word3-word4");w.push_back("word5-word6");

    w.push_back("word7-word8");w.push_back("word9-word0");

 

    char* w1[]={"word1-word2","word3-word4","word5-word6","word7-word8","word9-word0"};

 

    test_boost_string_algorithm(w);

    test_boost_string_algorithm_opt(w);

    test_select_many(w);

    test_boost_tokenizer(w);

    test_plain_cpp(w);

    test_plain_c(w1,5);

    return 0;

}

Сравнение производительность С#,C,C++ часть 1, код на C#

Задумал я страшное - сравнить производительность чистого С, С++ (с использованием STL и boost) и С# на основании задачи из заметки Разединяем строки...обьединяем строки.

Напомню задачу:
нужно из таких строк: "word1-word2", "word3-word4", "word5-word6"
получить вот такую строку:
word1:word2:word3:word4:word5:word6

Для сравнения производительности время выполнения замерял с помощью QueryPerformanceCounter, просто потому что этот способ одинаков для С# и С++.

Основной код - код, делающий всю работу:

string r = ":".join(array1.SelectMany(x => x.Split('-')));


Весь исходный код на C#

using System;

using System.Linq;

using System.Collections.Generic;

using System.Runtime.InteropServices;

 

namespace MainProgramm

{

    static class string_extentions

    {

        public static string join(this string a, IEnumerable<string> elems)

        {

            string result = "";

            foreach (string elem in elems) {

                if (result.Length != 0)

                    result += a;

                result += elem;

            }

            return result;

        }

    }

    internal class HiPerfTimer

    {

        [DllImport("Kernel32.dll")]

        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

 

        [DllImport("Kernel32.dll")]

        private static extern bool QueryPerformanceFrequency(out long lpFrequency);

 

        private long startTime, stopTime;

        private long freq;

        public HiPerfTimer(){QueryPerformanceFrequency(out freq);}

        public void    start   (){QueryPerformanceCounter(out startTime);}

        public void    stop    (){QueryPerformanceCounter(out stopTime);}

        public double   duration{

            get{return (double)(stopTime - startTime) / (double)freq;}

        }

    }

 

    class Program

    {

        static void Main(string[] args){

            string[] array1 = { "word1-word2", "word3-word4", "word5-word6", "word7-word8", "word9-word0" };

            HiPerfTimer pt = new HiPerfTimer();

            pt.start();

            for (int i = 0; i < 10000; ++i){

                string r = ":".join(array1.SelectMany(x => x.Split('-')));

            }

            pt.stop();

 

            Console.WriteLine("C# - {0} sec\n", pt.duration); // print the duration of the timed code

        }

    }

}

Сумбурные мысли о С++ , или С++ как функциональный язык

В сущности это продолжение записи "Разъединяем строки, объединяем строки..."

Функциональные возможности языка С++ очень ограничены и слабы. Но несмотря на это я раз за разом вижу как фанаты С++ отстаивают право С++ быть самым лучшем фукнциональным языком... Диалог обычно происходит следующим образом:
- Вот как это делается в Haskell
- Ха, в С++ можно сделать вот так, это практически то же самое.

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

Проблема С++ и его фанатов - славное прошлое языка С++. Было время когда С++ был безоговорочный "номер первый" в программировании. Причем не такой "новый первый" каким сейчас является..наверно java.Или VB, не в курсе. С++ применялся во всех областях и для всех задач. Были конечно и другие языки, которые где-то там как-то там применялось...но доминирование С++ было подавляющим. А остальные языки были как говорится никто и звали их никак.

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

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

Кстати. Java - тоже вполне себе функциональный язык!!! На мем можно писать функционально!!!

Например вот так (источник вдохновения):

public int calcPriceInternalIterator(List items)

{

    Iterator i = items.iterator();

    UnaryFunction getItemPrice = new UnaryFunction()

    {

        public Object evaluate (Object obj)

        {

            return new Double(((SETLItem)obj).getPrice());

        }

    };

    Constant usd100 = new Constant(new Double(100));

    BinaryPredicateUnaryPredicate moreThanUSD100 = new BinaryPredicateUnaryPredicate

        (new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(), getItemPrice, usd100));

    FilteredIterator fi = new FilteredIterator(i, moreThanUSD100);

    Summer addPrice = new Summer();

    Algorithms.foreach(fi, addPrice);

    return addPrice.getSum();

}


Обьясняю шутку...
Python "почти" эквиваленты:

a = [123,98,12,124,1007,2344]

 

sum1  = sum(filter(lambda x: x > 100,a))

sum2  = sum([x for x in a if x > 100])

sum3  = reduce( lambda x,y:x+y,[x for x in a if x > 100])

sum4  = reduce( lambda x,y:x+y,filter(lambda x: x > 100,a))


С# - фукнционально+LINQ

List<int> a = new List<int> { 123, 98, 12, 124, 1007, 2344 };

 

var sum1 = (from x in a where x > 100 select x).Sum();

var sum2 = a.filter(x => x > 100).reduce((x, y) => x + y);

четверг, 28 февраля 2008 г.

Разединяем строки...обьединяем строки.

Итак...задачка. Есть массив строк. Каждую строку в массиве нужно разделить по указанному разделителю, потом все полученные строки обьединить по еще одному указанному разделителю.

Если не очень понятно - нужно из таких строк: "word1-word2", "word3-word4", "word5-word6"
получить вот такую строку:
word1:word2:word3:word4:word5:word6

C#:

Увы, стандартная функция Join не работает с IEnumerable. Поэтому пришлось написать свою. Также нет стандартной функции reduce. Поэтому пришлось написать свою. Это не так уж и важно, поскольку к выводам не относится. Итак, код:

Способ1:

string[] a = { "word1-word2", "word3-word4", "word5-word6" };

Console.WriteLine(":".join(a.SelectMany(x=>x.Split('-'))));


Способ 2:

string[] a = { "word1-word2", "word3-word4", "word5-word6" };

Console.WriteLine(a.SelectMany(x => x.Split('-')).reduce((x, y) => x + ":" + y));



Python. Тут ничего дописывать не пришлось.

Способ 1:

a = ["word1-word2", "word3-word4", "word5-word6"]

print ":".join([x for word in a for x in word.split('-')])


Способ 2:

a = ["word1-word2", "word3-word4", "word5-word6"]

print reduce(lambda x,y:x+":"+y,[x for word in a for x in word.split('-')])



К чему это я...
С++, решение "в лоб"

    vector<string> w;

    w.push_back("word1-word2");

    w.push_back("word3-word4");

    w.push_back("word5-word6");

 

    string result;

 

    for(vector<string>::const_iterator v = w.begin();v!=w.end();++v)

    {

        const vector<string>& w1 = split_string(*v,"-");

        for(vector<string>::const_iterator v1 = w1.begin();v1!=w1.end();++v1)

        {

            if(!result.empty())

                result+=":";

            result+=*v1;

        }

    }

    cout << result << endl;


Попробуем функционально? "Без проблем":

    vector<string> w;

    w.push_back("word1-word2");

    w.push_back("word3-word4");

    w.push_back("word5-word6");

 

    cout << join_string(select_many(w,bind(split_string,_1,("-"))),":") << endl;

Почему "без проблем" в кавычках...?
Потому что сначала я попытался достигнуть результата используя только std::for_each и bind. Не получилось. Пришлось написать (ну помимо join_string и split_string функцию select_many.

template<class _Container,class _Fn1> inline

_Container select_many(const _Container& container, _Fn1& _Func)

{   

    _Container res;

    for(_Container::const_iterator v = container.begin();v!=container.end();++v)

    {

        _Container tmp = _Func(*v);

        for(_Container::const_iterator v1 = tmp.begin();v1!=tmp.end();++v1)

            res.push_back(*v1);

    }

    return res;

}



В принципе по своей сути она похожа на аналогичные в C# и Python, но увы - фактически до них ей ну очень далеко. Разница огромна, даже расписывать не буду.

К чему это я...

Если бы у меня была возможность выбрать какую возможность добавить в следующую версию С++ я бы пожалуй выбрал питоновские генераторы (они же шарповые IEnumerable).