Показаны сообщения с ярлыком C#. Показать все сообщения
Показаны сообщения с ярлыком C#. Показать все сообщения

вторник, 18 ноября 2008 г.

RAII паттерн на C#, Nemerle и C++

Многие программисты на С++ перешедшие в С# испытывают дискомфорт от невозможности использования деструкторов. Точнее деструкторы то есть...но вызываются не при выходе за область видимости а когда до неиспользуемого более обьекта доберется GC.
Это делает невозможным испльзование RAII паттерна. Паттерн очень удобный, в коде на С++ я его использую достаточно часто, а к хорошему быстро привыкаешь.

"Обычные" C# программисты используют try...finally, получают простыни кода и не видят проблемы. Чуть более продвинутые слышали про IDisposable и using. Но если класс не наследован от IDisposable - пишут try...finally, получают простыни кода и не видят проблемы.

В целом никакой особой проблемы нет...вот только хочется красоты и икибаны. Недавно на RSDN (ссылку потерял, автору спасибо) видел пример generic класса для подобной обертки. Написал по памяти.

Код вспомогательных классов:

namespace test

{

    public static class DHelpers

    {

        public static Disposable<T> MakeDisposable<T>(this T obj, Action<T> dispose){

            return new Disposable<T>(obj, dispose);

        }

        public static Disposable<T> MakeDisposable<T>(this T obj, Action<T> begin, Action<T> dispose){

            return new Disposable<T>(obj, begin, dispose);

        }

    }

 

    public class Disposable<T> : IDisposable

    {

        private readonly Action<T> dispose;

        public readonly T @value;

 

        public Disposable(T @value, Action<T> dispose){

            this.dispose = dispose;

            this.@value = @value;

        }

        public Disposable(T @value, Action<T> begin, Action<T> dispose){

            this.dispose = dispose;

            this.@value = @value;

            begin(@value);

        }

        public Disposable(Func<T> get, Action<T> dispose){

            this.@value = get();

            this.dispose = dispose;

        }

        public void Dispose() { dispose(@value); }

    }

}



Пример использования:

namespace test

{

    ..............

 

 

    public class MadClass

    {

        public MadClass() { Console.WriteLine("Create"); }

        public void Close() { Console.WriteLine("Close"); }

        public void Action() { Console.WriteLine("Using"); }

    }

 

    internal class Program

    {

        private static void Main()

        {

            using (var mad = (new MadClass()).MakeDisposable(x => x.Close()))

            {

                mad.value.Action();

            }

        }

    }

}



Благодаря closure можно оборачивать не только классы но и локальные переменные:

bool value = false;

Console.WriteLine(value);

using (value.MakeDisposable(_ => value = true, _ => value = false))

{

    Console.WriteLine(value);

}

Console.WriteLine(value);



Вариант кода на Nemerle для решения той же задачи(автор - WolfHound c RSDN. в принципе по коду можно найти тему в которой это было). Что интересно - не понадобился отдельный класс. И расширения тоже не понадобились. И даже ключевое слово using как часть языка не понадобилось...Метопрограммирование...итить.

Сам макрос (примитивный, в реальном коде нужно было бы усложнить):

using Nemerle.Compiler;

 

public macro ScopeGuard(begin, end, body)

syntax ("scope", "(", begin, ";", end, ")", body)

{

    <[

    {

        $begin;

        try

        {

            $body

        }

        finally

        {

            $end;

        }

    }

    ]>

}



Использование:

using System;

using System.Console;

using Nemerle.Utility;

 

class MadClass

{

    public this() { WriteLine("create"); }

    public Close() : void { WriteLine("close"); }

}

 

module Program

{

    Main() : void

    {

        scope (def x = MadClass(); x.Close())

        {

            WriteLine("Hi!");

        }

        _ = ReadKey();

    }

}



Ну и под конец С++. Писать стандартную RAII обертку бессмысленно. Напишу лучше про "нестандартное" использование shared_ptr.

Итак, вот часто встречаемый вариант:

{

    SomeType* p = GetSomeType();

    DoSomethingWithSomeType(p);

    ReleaseSomeType();

}


С использование boost::shared_ptr этот код может выглядеть вот так:

{

    boost::shared_ptr<void> p(GetSomeType(),boost::bind(&ReleaseSomeType,_1));

    DoSomethingWithSomeType(p.get());

}



Выглядит не очень практично? Вот реальные варианты использования (выдрано из личного кода)
раз

boost::shared_ptr<wchar_t>    pszBuffer(

                        reinterpret_cast<wchar_t*>(*m_consolePaste.get()),

                        boost::bind<BOOL>(::VirtualFreeEx, ::GetCurrentProcess(), _1, NULL, MEM_RELEASE));


и два

m_hSharedEvent = boost::shared_ptr<void>(

    ::CreateEvent(NULL, FALSE, FALSE, (name + std::wstring(L"_event")).c_str()),

                    ::CloseHandle);



То ли еще будет...когда в С++ появятся лямбды и замыкания.

Хотя вариант Nemerle мне кажется наиболее оптимальным с точки зрения бритвы Оккама.

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

Comparison of string performance using C#,C++(STL,boost) and C - part 3, results

Previous parts:
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++

This comparison 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++.

Goal - compare string performance on plain C with permormance of C++ code using STL and boost. I try to be as fear as possible and select "real-life"-like task. I add C# performance only when I notice difference between plain C and "C++ with STL".

For C/C++ I use VS 2005, release version, speed optimization. For C# I use VS 2008, Express Edition. My computer is Athlon 64 2-core 2.8 GHz, 2Gb of ram. Windows Vista.

Results, sorted by speed (I do many test runs and this is "average" results for 10000 iterations)






Boost String Algorithms Library0.596665 sec
select_many0.100862 sec
boost::tokenizer0.086117 sec
"naive" C++0.053814 sec
C#0,041314 sec
C0.003925 sec


Conclusion - C code is still 10 times faster then C# code, and still may be a bit optimized. But I didn't optimize C# code, even more, I try to write it in modern C#3.0 style using lambda/generics. When I rewrite C# code using StringBuilder it was 4 times slower then C code.

As for C++ code ("naive" or with boost) - either I don't familiar with some secret STL optimization technics...or...

And results using Boost String Algorithms Library looks really bad.

I see that C code use different algorithm then C++ or C# code. But this was C "idiomatic" way, again at least as I understand it.
Also I see that C code is not "general" enought, it will fail on strings like "word1--word2","word1-word2-". But my goal was to solve the problem. Pre-generalization is almost as bad as needless pre-optimization.

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

Compare string performance using C#,C++(STL,boost) and C - part 1, C#
Compare string performance using C#,C++(STL,boost) and C - part 3, results
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 use something that I call "idiomatic C" and 4 different "idiomatic" C++ solutions:

1. Using "Boost String Algorithms Library".
2. Using "select_many" kind of function.
3. Using Boost Tokenizer
4. Using "naive" С++ with std::strings.

C code:

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;

}



Using "Boost String Algorithms Library".

        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,":");



Using "select_many" kind of function

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


Using Boost Tokenizer

        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;

            }

        }



Using "naive" С++ with std::strings

        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;

            }

        }



Whole programm code:

#include "stdafx.h"

#include <string>

#include <algorithm>

 

#include <boost/tokenizer.hpp>

#include <boost/algorithm/string.hpp>

#include <boost/bind.hpp>

 

#define NUM_ITERATIONS 10000

 

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;

}

 

 

 

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

    HiPerfTimer pt;

    pt.start();

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

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

    pt.stop();

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

}

 

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

    HiPerfTimer pt;

    pt.start();

 

    for (int i = 0; i < NUM_ITERATIONS; ++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 String Algorithms Library - %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_ITERATIONS; ++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_ITERATIONS; ++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("naive 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_ITERATIONS; ++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");

 

    test_boost_string_algorithm(w);

    test_select_many(w);

    test_boost_tokenizer(w);

    test_plain_cpp(w);

 

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

    test_plain_c(w1,5);

    return 0;

}

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();

        }

    }

}