1 2
Generators 2 в избранное  новое горячее всё    подписка   модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 09:36
Оценка:39 (7)
Немного пропатчил версию c-smile:
http://www.rsdn.ru/forum/message/2965247.1.aspx
Автор: c-smile
Дата: 27.05.08


Дабы пофиксить следующие вещи, которые всплыли при первой же попытке реально применить Generators:

1. Почему "do {...} while (gen);", а не "while (gen) {...};"? А если последовательность пустая?!

2. Попытка вызвать в теле цикла gen() несколько раз приводит в очень неинтуитивному результату. Хотелось бы что бы новый элемент получался только после прохождения итерации, т.е. после вызова gen.operator bool().

3. Необходимость передавать в макрос stop() реальный элемент. Очень не удобно. Очень. Выход из функции должен означать конец последовательности, никаких элементов уже не должно быть.


И ещё добавил, что можно писать return посередине функции генерации.


Использование теперь выглядит так:

struct node
{
    node* left_;
    node* right_;
    value_t value_;
};

generator(iterate_tree_t, node*) 
{
    node* root;
    node* curr;
    std::vector<node*> node_queue;

public:
    iterate_tree_t(node* root)
        : root (root)
        , curr ()
    {}

    emit(node*)
        if (0 == root)
            return; // <--- можно писать просто return - значит конец последовательности

        curr = root;
        node_queue.clear();
        node_queue.reserve(128);
        
        for (;;)
        {
            yield(curr);

            if (curr->left_ && curr->right_)
            {
                node_queue.push_back(curr->right_);
                curr = curr->left_;
            }
            else if (curr->left_)
            {
                curr = curr->left_;
            }
            else if (curr->right_)
            {
                curr = curr->right_;
            }
            else
            {
                if (node_queue.empty())
                    break;
                curr = node_queue.back();
                node_queue.pop_back();
            }
        }
    stop_emit(); // <--- не надо передавать реальный элемент
};

int main()
{
    iterate_tree_t iter (0);
    // <--- проверка теперь вначале цикла - т.е. поддерживает пустые последовательности
    while (iter.next())
        // <--- в теле можно вызывать iter.value() несколько раз - перехода не след. элемент не будет
        std::cout << "node: " << iter.value()->value_ << " " << iter.value()->value_ << std::endl;
    std::cout << "[end]" << std::endl;
}




Реализация:
template<typename derived_t, typename elem_t>
class generator_base
{
protected:
    int generator_line;
    elem_t generator_value;

public:
    generator_base()
        : generator_line()
    {}

    bool next()
    {
        int generator_cur_line = generator_line;
        generator_line = 0;
        static_cast<derived_t*>(this)->generate_value(generator_cur_line);
        return 0 != generator_line;
    }

    elem_t const& value()
    {
        return generator_value;
    }
};

#define generator(NAME, T) class NAME : public generator_base<NAME, T>

#define emit(T)\
    void generate_value(int generator_cur_line)\
    { switch(generator_cur_line) { case 0:;\
/**/

#define stop_emit() }}

#define yield(V)\
        do {\
            generator_line=__LINE__;\
            generator_value = (V);\
            return;\
            case __LINE__:;\
        } while (0)\
/**/



Теперь стало использовать немного попроще. Вот, например, обход дерева в глубину.
Кстати, если нужны локальные переменные внутри функции генерации, то их можно брать в expression block {}.

generator(iterate_tree_rec_t, node*) 
{
    node* root;
    std::vector<iterate_tree_rec_t> iter_queue;

public:
    iterate_tree_rec_t(node* root)
        : root (root)
    {}

    emit(node*)
        if (0 == root)
            return;

        yield(root);

        {
            // <--- локальная переменная
            size_t i = 0;
            for (; i != root->children_.size(); ++i)
                iter_queue.push_back(root->children_[i]);
        }

        while (iter_queue.size())
        {
            while (iter_queue.back().next())
                yield(iter_queue.back().value());
            iter_queue.pop_back();
        }

    stop_emit();
};


Re: Generators 2 в избранное  новое    модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 09:45
Здравствуйте, remark, Вы писали:

R>Теперь стало использовать немного попроще. Вот, например, обход дерева в глубину.


---------------------------------------------------------------------/\/\/\/\
------------------------------------------------------------------рекурсивный

R>
R>generator(iterate_tree_rec_t, node*) 
R>{
R>    node* root;
R>    std::vector<iterate_tree_rec_t> iter_queue;

R>public:
R>    iterate_tree_rec_t(node* root)
R>        : root (root)
R>    {}

R>    emit(node*)
R>        if (0 == root)
R>            return;

R>        yield(root);

R>        {
R>            // <--- локальная переменная
R>            size_t i = 0;
R>            for (; i != root->children_.size(); ++i)
R>                iter_queue.push_back(root->children_[i]);
R>        }

R>        while (iter_queue.size())
R>        {
R>            while (iter_queue.back().next())
R>                yield(iter_queue.back().value());
R>            iter_queue.pop_back();
R>        }

R>    stop_emit();
R>};
R>


R>
Re: Generators 2 в избранное  новое    модер. 
От: Alexander G 
Дата: 28.05.08 09:50
Здравствуйте, remark, Вы писали:

R>Немного пропатчил версию c-smile:

R>http://www.rsdn.ru/forum/message/2965247.1.aspx
Автор: c-smile
Дата: 27.05.08


Что-то сомневаюсь что из этого может получится что-то практически полезное.

Считаю, что такое лучше делать через настоящие сопрограммы. В windows — на фиберах, как здесь. Как сделать на других платформах — думаю, можно глянуть в Boost.Coroutine
Re[2]: Generators 2 в избранное  новое    модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 10:37
Здравствуйте, Alexander G, Вы писали:

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


R>>Немного пропатчил версию c-smile:

R>>http://www.rsdn.ru/forum/message/2965247.1.aspx
Автор: c-smile
Дата: 27.05.08


AG>Что-то сомневаюсь что из этого может получится что-то практически полезное.


AG>Считаю, что такое лучше делать через настоящие сопрограммы. В windows — на фиберах, как здесь. Как сделать на других платформах — думаю, можно глянуть в Boost.Coroutine



А сколько они "весят"? По объёму занимаемой памяти и по времени переключения контекста?
У меня есть подозрение, что значиииииииительно дороже, чем предложенное решение. Чем несколько байт + 1 переход.

Я думал делать на фиберах даже более существенную вещь, но отказался, т.к. потенциально надо будет создавать миллионы.


Re[3]: Generators 2 в избранное  новое    модер. 
От: Alexander G 
Дата: 28.05.08 11:12
Здравствуйте, remark, Вы писали:

R>Здравствуйте, Alexander G, Вы писали:


R>А сколько они "весят"? По объёму занимаемой памяти и по времени переключения контекста?

R>У меня есть подозрение, что значиииииииительно дороже, чем предложенное решение. Чем несколько байт + 1 переход.

R>Я думал делать на фиберах даже более существенную вещь, но отказался, т.к. потенциально надо будет создавать миллионы.


Я понял.

Но если итератор итерирует по большим бинарным блокам, читаемым из файла, то оверхед на переключение незаметен, а вот прикрутить эту идею к последовательному чтению файла сложнее.
Была задача обрабатывать поток бинарных данных несколькими независимыми способами. Решение было читать кусками, передавать данные в каждый способ через враппер, который делает yield, как только пытается анализировать ещё не прочитанные данные, yield другому способу, когда все способы дошли до yield, читать следующую порцию и вернуть управление способам. В этом случае оверхед от фиберов не чуствовался.

R>

Re[3]: Generators 2 в избранное  новое    модер. 
От: Roman Odaisky 
Дата: 28.05.08 12:00
Здравствуйте, remark, Вы писали:

AG>>Считаю, что такое лучше делать через настоящие сопрограммы. В windows — на фиберах, как здесь. Как сделать на других платформах — думаю, можно глянуть в Boost.Coroutine


R>А сколько они "весят"? По объёму занимаемой памяти и по времени переключения контекста?

R>У меня есть подозрение, что значиииииииительно дороже, чем предложенное решение. Чем несколько байт + 1 переход.

Там переключение контекста должно быть безъядерное, так что оверхед должен быть мал.

R>Я думал делать на фиберах даже более существенную вещь, но отказался, т.к. потенциально надо будет создавать миллионы.


А пробовал?

В POSIX, кстати, уже лет 7 есть {get,set,swap}context.
status=sent (delivered to file: /dev/null)
Re[4]: Generators 2 в избранное  новое    модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 12:38
Здравствуйте, Roman Odaisky, Вы писали:

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


AG>>>Считаю, что такое лучше делать через настоящие сопрограммы. В windows — на фиберах, как здесь. Как сделать на других платформах — думаю, можно глянуть в Boost.Coroutine


R>>А сколько они "весят"? По объёму занимаемой памяти и по времени переключения контекста?

R>>У меня есть подозрение, что значиииииииительно дороже, чем предложенное решение. Чем несколько байт + 1 переход.

RO>Там переключение контекста должно быть безъядерное, так что оверхед должен быть мал.


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

R>>Я думал делать на фиберах даже более существенную вещь, но отказался, т.к. потенциально надо будет создавать миллионы.


RO>А пробовал?


RO>В POSIX, кстати, уже лет 7 есть {get,set,swap}context.


Даже не стал пробовать. Основная проблема — память под стек. Фиберу-то нужен полноценный стек. Ведь не запретишь в функции писать "char temp_buf [4096];", и потом стек вызовов может быть достаточно длинный, включая всякие сторонние библиотеки. Т.е. меньше, чем 64k не отделаешься.

На переключение тоже оверхед по времени будет определенный. Хоть ядра не будет, но всё же, я думаю не менее 50 тактов. А у меня сейчас полный цикл, включающий шедулер и ещё несколько других вещей, — 50 тактов.

А по поводу юниксов, всё ещё хуже. Ситуация практически комическая, в винду фиберы пришли из юниксов и укоренились, а в юниксах они теперь депрекейтед:

It is a pity that xcontext.h is obsolete and that this header file is
missing from gnulib on some platforms as OpenBSD 3.8, Cygwin, mingw, BeOS.

http://article.gmane.org/gmane.comp.lib.boost.threads.devel/316

Правда, я не думаю, что ucontext исчезнет в обозримом будущем, потомучто слишком много кода уже на него завязано...

Re[5]: Generators 2 в избранное  новое    модер. 
От: Roman Odaisky 
Дата: 28.05.08 12:52
Здравствуйте, remark, Вы писали:

R>А по поводу юниксов, всё ещё хуже. Ситуация практически комическая, в винду фиберы пришли из юниксов и укоренились, а в юниксах они теперь депрекейтед:

R>

R>It is a pity that xcontext.h is obsolete and that this header file is
R>missing from gnulib on some platforms as OpenBSD 3.8, Cygwin, mingw, BeOS.

R>http://article.gmane.org/gmane.comp.lib.boost.threads.devel/316

А почему?
status=sent (delivered to file: /dev/null)
Re[6]: Generators 2 в избранное  новое    модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 12:54
Здравствуйте, Roman Odaisky, Вы писали:

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


R>>А по поводу юниксов, всё ещё хуже. Ситуация практически комическая, в винду фиберы пришли из юниксов и укоренились, а в юниксах они теперь депрекейтед:

R>>

R>>It is a pity that xcontext.h is obsolete and that this header file is
R>>missing from gnulib on some platforms as OpenBSD 3.8, Cygwin, mingw, BeOS.

R>>http://article.gmane.org/gmane.comp.lib.boost.threads.devel/316

RO>А почему?


Теперь у них есть потоки! Нафиг им фиберы?

Вообще я не знаю.

Re[5]: Generators 2 в избранное  новое    модер. 
От: Alexander G 
Дата: 28.05.08 13:28
Здравствуйте, remark, Вы писали:

R>Даже не стал пробовать. Основная проблема — память под стек. Фиберу-то нужен полноценный стек. Ведь не запретишь в функции писать "char temp_buf [4096];", и потом стек вызовов может быть достаточно длинный, включая всякие сторонние библиотеки. Т.е. меньше, чем 64k не отделаешься.


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

R>На переключение тоже оверхед по времени будет определенный. Хоть ядра не будет, но всё же, я думаю не менее 50 тактов. А у меня сейчас полный цикл, включающий шедулер и ещё несколько других вещей, — 50 тактов.


Насколько я понимаю в фиберах переключаются всего лишь регистры, без FPU контекста. Это действительно 50 тактов ?
Re[6]: Generators 2 в избранное  новое    модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 13:57
Здравствуйте, Alexander G, Вы писали:

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


AG>Насколько я понимаю в фиберах переключаются всего лишь регистры, без FPU контекста. Это действительно 50 тактов ?



Померить можно, но я не буду, т.к. по памяти меня уже не устраивает. Всё-таки проигрыш в 2000 раз это серьезно, и существенно ограничивает область применения.


Re[7]: Generators 2 в избранное  новое    модер. 
От: Alexander G 
Дата: 28.05.08 17:57
Здравствуйте, remark, Вы писали:

R>Померить можно, но я не буду, т.к. по памяти меня уже не устраивает. Всё-таки проигрыш в 2000 раз это серьезно, и существенно ограничивает область применения.


А если задать размер стека по минимуму ?
::CreateFiberEx(1, 1, ...
Re[8]: Generators 2 в избранное  новое    модер. 
От: remarkhttp://www.1024cores.net/
Дата: 28.05.08 18:54
Здравствуйте, Alexander G, Вы писали:

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


R>>Померить можно, но я не буду, т.к. по памяти меня уже не устраивает. Всё-таки проигрыш в 2000 раз это серьезно, и существенно ограничивает область применения.


AG>А если задать размер стека по минимуму ?
AG>::CreateFiberEx(1, 1, ...
AG>


... тогда всё свалится в какой-то момент от нехватки стека.

Re[8]: Generators 2 в избранное  новое    модер. 
От: Erop 
Дата: 28.05.08 19:06
Здравствуйте, Alexander G, Вы писали:

AG>А если задать размер стека по минимуму ?
AG>::CreateFiberEx(1, 1, ...
AG>

А зачем хаккерить? Неужели рещение с легко пререполняемым стеком тебе кажется надёжнее и поддерживаемее, чем химия с макросами?

Про то, что рабоать всем генераторам на одном стеке выгоднее и по памяти и по использованию кэша я вообще ине говорю, даже...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[9]: Generators 2 в избранное  новое    модер. 
От: Alexander G 
Дата: 29.05.08 06:04
Здравствуйте, Erop, Вы писали:

E>А зачем хаккерить? Неужели рещение с легко пререполняемым стеком тебе кажется надёжнее и поддерживаемее, чем химия с макросами?


Поддерживаемее — да.
Можно использовать локальные переменные без ограничений. Можно использовать подпрограммы и передавать yield туда (включая чужой код, доступный только в бинарном виде). Вообще никаких искусственных синтаксических ограничений и ничего не препятствует лёгкой вставке такого yield в существующие циклы, как и обратной операции.
Re[10]: Generators 2 в избранное  новое    модер. 
От: Erop 
Дата: 29.05.08 06:24
Здравствуйте, Alexander G, Вы писали:

AG>Можно использовать локальные переменные без ограничений. Можно использовать подпрограммы и передавать yield туда (включая чужой код, доступный только в бинарном виде). Вообще никаких искусственных синтаксических ограничений и ничего не препятствует лёгкой вставке такого yield в существующие циклы, как и обратной операции.


Ну, как бы, всегда можно написать state machine вообще без хаккерства.
Хотя я согласен, что эти генераторы с STL не компатибл совсем.
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Generators 2 в избранное  новое    модер. 
От: sokel 
Дата: 29.05.08 06:59
Здравствуйте, remark, Вы писали:

R>Теперь стало использовать немного попроще. Вот, например, обход дерева в глубину.


Извиняюсь за оффтоп, просто недавно тоже встала задача такого обхода дерева. В целях уменьшения объемов данных использую легковесные бинарные деревья, без хранения leftmost нод. То есть искать по ним через root можно, но вот чтобы обойти все элементы, приходится получать begin() левым спуском, хотя для обхода может быть даже не важен порядок. Насколько я знаю, stl-реализации деревьев иногда используют рекурсию (например, при очистке), поскольку глубина всё таки, исходя из самой идеи дерева, не может быть очень уж большой. Так что тут может самодельный стек и излишен. У меня вот всё обошлось указателем на функцию с шаблонным параметром, что-то вроде:

template<typename param> 
void apply(node* n, void (*func)(node*, param&), param& p)
{
    node* l = left(n);
    node* r = right(n);
    if(l) apply(l, func, p);
    if(r) apply(r, func, p);
    func(n, param);
}   
template<typename param> 
void apply(void (*func)(node*, param&), param& p) { if(root) apply(root, func, p); }  


...
void sum(some_node* n, int& sum) { sum+=n->value; }
...
int s = 0;
some_tree.apply(sum, s);
Re: Generators 2 в избранное  новое    модер. 
От: R.K. 
Дата: 29.05.08 15:35
Оценка:33 (3)
Здравствуйте, remark, Вы писали:

R>Немного пропатчил версию c-smile:

R>http://www.rsdn.ru/forum/message/2965247.1.aspx
Автор: c-smile
Дата: 27.05.08


Если поменять еще немного, можно организовать взаимнорекурсивные генераторы
// ...
#define generator(NAME, T, NAME_T) class NAME : public generator_base<NAME_T, T>
// ...


То, что я хочу переписать на с++: Re[22]: Write in Nemerle!
Автор: R.K.
Дата: 04.12.06
(и дальше по теме)...

#include <vector>
#include <deque>
#include <math.h>
#include <time.h>
#include "../generator.h"

template <typename Primes>
generator(gen_map, unsigned, gen_map<Primes>)
{
    const unsigned p0_, p1_, lo_, hi_, bound_;
    std::vector<bool> arr_;
    unsigned i_, n_;

    static unsigned correct(unsigned n, unsigned i)
    { return n & 1 ? n : n + i; }

public:
    gen_map(unsigned p0, unsigned p1)
        : p0_(p0), p1_(p1)
        , lo_(p0 * p0), hi_(p1 * p1)
        , bound_((hi_ - lo_) / 2 - 1)
        , arr_(bound_ + 1, true)
    {
        Primes ps(3);
        for (ps.next(); ps.value() < p1_; ps.next())
        {
            const unsigned i = ps.value();
            for (unsigned j = (correct(lo_ - lo_ % i + i, i) - lo_) / 2
                ; j <= bound_
                ; j += i)
                arr_[j] = false;
        }
    }

    emit(unsigned)
    {
        for (i_ = 1, n_ = lo_ + 2; i_ <= bound_; ++i_, n_ += 2)
        {
            if (!arr_[i_])
                continue;
            yield(n_);
        }
    }
    stop_emit()
};

inline unsigned sqrt_int(unsigned x)
{ return static_cast<unsigned>(sqrt(static_cast<double>(x))); }

generator(primes, unsigned, primes)
{
    const unsigned n_, sq_n_;
    gen_map<primes> *gmap_;
    unsigned p0_, p1_, i_;
    primes *ps_;

    void dispose()
    {
        if (gmap_)
            delete gmap_;
        gmap_ = 0;
    }

public:
    primes(unsigned n = 2)
        : n_(n)
        , sq_n_(sqrt_int(n))
        , gmap_(0)
        , ps_(0)
    {
    }

    ~primes()
    {
        dispose();
        if (ps_)
            delete ps_;
    }

    emit(unsigned)
    {
        if (n_ <= 2) yield(2);
        if (n_ <= 3) yield(3);
        if (n_ <= 5) yield(5);
        if (n_ <= 7) yield(7);
        ps_ = new primes(3);
        ps_->next();
        p1_ = ps_->value();
        for (;;)
        {
            p0_ = p1_;
            ps_->next();
            p1_ = ps_->value();
            if (p1_ <= sq_n_)
                continue;
            dispose();
            gmap_ = new gen_map<primes>(p0_, p1_);
            while (gmap_->next())
                if (gmap_->value() >= n_)
                    yield(gmap_->value());
        }
    }
    stop_emit()
};

int main()
{
    primes ps(100000000);
    int sum = 0;
    long tim0 = clock();
    for (ps.next(); ps.value() <= 120000000; ps.next(), ++sum);
    printf("sum = %d, time: %g sec.\n", sum, (clock() - tim0) / 1000.0);
}
You aren't expected to absorb this
Re[2]: Generators 2 в избранное  новое    модер. 
От: frogkiller 
Дата: 29.05.08 20:14
Здравствуйте, R.K., Вы писали:

RK>То, что я хочу переписать на с++: Re[22]: Write in Nemerle!
Автор: R.K.
Дата: 04.12.06
(и дальше по теме)...


RK>
RK>int main()
RK>{
RK>    primes ps(100000000);
RK>    int sum = 0;
RK>    long tim0 = clock();
RK>    for (ps.next(); ps.value() <= 120000000; ps.next(), ++sum);
RK>    printf("sum = %d, time: %g sec.\n", sum, (clock() - tim0) / 1000.0);
RK>}
RK>


Ну и какой результат на P4-2.8?
У меня на Athlon64 X2, 3800+, 1 Гб оперативки — 0.641 сек. Собирал древним gcc 3.4.2 (MinGW, CodeBlocks под WinXP SP2)
Курица — это инструмент, с помощью которого одно яйцо производит другие.
Re: Generators 2 в избранное  новое    модер. 
От: frogkiller 
Дата: 29.05.08 20:16
Здравствуйте, remark, Вы писали:

R>Немного пропатчил версию c-smile:

R>http://www.rsdn.ru/forum/message/2965247.1.aspx
Автор: c-smile
Дата: 27.05.08


Немного позанудствую:
#define yield(V)\
        do {\
            this->generator_line=__LINE__;\
            this->generator_value = (V);\
            return;\
            case __LINE__:;\
        } while (0)\


R>

Курица — это инструмент, с помощью которого одно яйцо производит другие.
1 2