Re[5]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Evgeny.Panasyuk Россия  
Дата: 18.10.13 00:35
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>Примерно вот так:

[...]
SD>Конечно, код на самом деле чуть сложнее, но общую идею должно быть видно. Не исключаю, что есть способы сделать это более удобно или эффективно. Если вы их знаете, подсказывайте.

Во-первых, как я понял, ready_ может быть vector'ом (если нельзя ивалидировать ссылки то либо не выходить за пределы capacity сделав предварительный reserve, либо тогда уже deque).
Далее — .size() ведь у ready_ оценивается?

Во-вторых, я не вижу смысла в pending_ — соединение во время handshake может жить в лямбде (если оно как shared_ptr, то просто захватить, если только movable — то move'нуть через bind, если есть C++1y то generalized capture).
Re[6]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Evgeny.Panasyuk Россия  
Дата: 18.10.13 00:52
Оценка:
EP>Во-вторых, я не вижу смысла в pending_ — соединение во время handshake может жить в лямбде (если оно как shared_ptr, то просто захватить, если только movable — то move'нуть через bind, если есть C++1y то generalized capture).

То есть получится как-то так:
auto &&conn = make_unique<connection>(service_, endpoint_);
auto &conn_ref = *conn;

conn_ref.async_connect
(
    bind
    (
        [this](decltype(conn) &c, int _errno)
        {
            if (_errno == 0)
                ready_conn = ready_.insert(ready_.end(), std::move(c));
        }, move(conn), _1
    )
);
Re[6]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: SkyDance Земля  
Дата: 18.10.13 00:59
Оценка:
EP>Во-первых, как я понял, ready_ может быть vector'ом (если нельзя ивалидировать ссылки то либо не выходить за пределы capacity сделав предварительный reserve, либо тогда уже deque).

Cоединение одноразовое. Когда оно забирается (borrow) из пула, его следует удалить из ready_. Обратно оно уже не возвращается (на самом деле, попадает в список requested_ тем же макаром).

EP>Далее — .size() ведь у ready_ оценивается?


И у pending_ тоже, когда выясняется, надо ли еще добавлять в пул (у пула непростой алгоритм autoscaling'а, спецификации этого места менять мы не можем).

EP>Во-вторых, я не вижу смысла в pending_ — соединение во время handshake может жить в лямбде (если оно как shared_ptr, то просто захватить, если только movable — то move'нуть через bind, если есть C++1y то generalized capture).


Соединение не shared_ptr, только movable. C++1y нет, есть только C++ 11. Но даже если бы был, все равно pending_ нужно иметь для того, чтобы при закрытии пула немедленно закрыть все pending соединения.
Re[7]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Evgeny.Panasyuk Россия  
Дата: 18.10.13 01:55
Оценка:
Здравствуйте, SkyDance, Вы писали:

EP>>Во-первых, как я понял, ready_ может быть vector'ом (если нельзя ивалидировать ссылки то либо не выходить за пределы capacity сделав предварительный reserve, либо тогда уже deque).

SD>Cоединение одноразовое. Когда оно забирается (borrow) из пула, его следует удалить из ready_. Обратно оно уже не возвращается (на самом деле, попадает в список requested_ тем же макаром).

Насколько я понял, ready_ всё ещё может быть вектором.

EP>>Во-вторых, я не вижу смысла в pending_ — соединение во время handshake может жить в лямбде (если оно как shared_ptr, то просто захватить, если только movable — то move'нуть через bind, если есть C++1y то generalized capture).

SD>Соединение не shared_ptr, только movable. C++1y нет, есть только C++ 11.

Для C++11 я показал вариант выше.

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


Если pending_ список нужен в любом случае — тот тут особо ничего не поделать. + использовать отдельный счётчик из-за O(N) .size()

Далее, если важна производительность, можно попробовать избавится от аллокаций:
1. Убрать unique_ptr — использовать просто connection — оно должно быть movable
2. Попытаться избежать аллокации узлов в pending_. Нужно не удалять узел, а делать splice в empty list (нужная версия splice — O(1)).
Другой вариант: сделать простой list_pool: узлы разных списков будут плотно хранится в одном векторе/дэке.
Re[8]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: SkyDance Земля  
Дата: 18.10.13 02:43
Оценка:
EP>Насколько я понял, ready_ всё ещё может быть вектором.

Не совсем понимаю, как. Соеднение в ready_ может быть закрыто со стороны peer, в этот момент его нужно удалить из ready_, притом это легко может быть и в середине вектора, что инвалидирует все итераторы за искомым.

EP>Если pending_ список нужен в любом случае — тот тут особо ничего не поделать. + использовать отдельный счётчик из-за O(N) .size()


Так и поступили.

EP>1. Убрать unique_ptr — использовать просто connection — оно должно быть movable


connection — интерфейс, за которым прячутся полиморфные реализации разных вариантов соединений, т.е. нужно иметь его по указателю. Кроме того, unique_ptr оверхеда практически не добавляет.

EP>2. Попытаться избежать аллокации узлов в pending_. Нужно не удалять узел, а делать splice в empty list (нужная версия splice — O(1)).


Примерно так и сделано behind the scenes с помощью custom allocator, которые, к счастью, в C++ 11 уже более-менее рабочие.

EP>Другой вариант: сделать простой list_pool: узлы разных списков будут плотно хранится в одном векторе/дэке.


Да, такую реализацию рассматривали, в ней придется самостоятельно заниматься менеджментом указателей left/right, не хотелось. Возможно, вернемся к этому варианту, если профайлер докажет такую необходимость. Пока вроде ее нет.
Re[7]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Erop Россия  
Дата: 18.10.13 07:15
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>Это не особенности поведения STL, а таки стандарт C++. Как раз на implementation defined особенности у нас нигде не было закладок.



А как вы это проверяли?..

В общем я бы не рискнул на такую спорную, тем более недавно изменившуюся тонкость закладываться, а вы рискнули. Ретроспективно я как бы оказался прав, а вы нет. Но задним умом всякий умён же.

Конструктивный вывод может состоять в том, что бы, подумать, а не заложились лы вы ещё на какое-нибудь тонкое место, и написать тестик, который се такие места тестит.
Сильно сэкономит время на детект ситуации не только при смене версии gcc, но и при порте, если вдруг понадобится...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[9]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Evgeny.Panasyuk Россия  
Дата: 18.10.13 08:32
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>Не совсем понимаю, как. Соеднение в ready_ может быть закрыто со стороны peer, в этот момент его нужно удалить из ready_,


Согласен, этот момент пропустил.

SD>притом это легко может быть и в середине вектора, что инвалидирует все итераторы за искомым.


Да, удалять из середины вектора — не самая лучшая затея (инвалидация и O(N)).

EP>>1. Убрать unique_ptr — использовать просто connection — оно должно быть movable


SD>connection — интерфейс, за которым прячутся полиморфные реализации разных вариантов соединений, т.е. нужно иметь его по указателю. Кроме того, unique_ptr оверхеда практически не добавляет.


Сам unique_ptr не добавляет, а добавляет сопутствующая аллокация + индерекции при использовании. Если нужно полиморфное поведение, то есть несколько вариантов:
1. boost::varaint, или если нет, то самописный велик. Уберёт аллокацию и индерекцию, но размер будет максимальным из всех соединений + sizeof(tag) + alignment, и набор типов прибит гвоздями во время компиляции.
2. использовать простой per-thread аллокатор, либо pool'ы для соединений разных типов — существенно удешевит аллокацию.

EP>>2. Попытаться избежать аллокации узлов в pending_. Нужно не удалять узел, а делать splice в empty list (нужная версия splice — O(1)).

SD>Примерно так и сделано behind the scenes с помощью custom allocator, которые, к счастью, в C++ 11 уже более-менее рабочие.

Я имел ввиду, что вместо
pending_connect_.erase(it);
ready_.insert(ready_.end(), std::move(c));

можно просто сделать
ready_.splice(ready_.end(), pending_connect_, it);

Конечно свой аллокатор для узлов списка будет полезен сам по себе, и существенно сократит стоимость erase+insert, но splice всё-таки намного дешевле. То есть можно использовать и аллокатор и splice.

EP>>Другой вариант: сделать простой list_pool: узлы разных списков будут плотно хранится в одном векторе/дэке.


SD>Да, такую реализацию рассматривали, в ней придется самостоятельно заниматься менеджментом указателей left/right, не хотелось. Возможно, вернемся к этому варианту, если профайлер докажет такую необходимость. Пока вроде ее нет.


Насколько я вижу, в этом случае ручной доступ к left-right будет только в трёх случаях, два из которых тривиальные:
1. создание нового узла — установка left и right на nil
2. получение следующего узла из текущего (например для обхода всех pending)
3. splice — переброс узла из одного списка, в другой (причём достаточно переброски в начало, а не в любую позицию)

Handle'ом к узлу/список может быть простой uint16 (дешевле указателя на x32), либо если соединений больше 65536, то тогда уже uint32 (дешевле указателя на x64):
template <typename T, typename NodeHandle = uint16_t>
class ListPool
{
    struct Node
    {
        T value; 
        NodeHandle left, right; 
    };
    vector<Node> pool; 
    NodeHandle free_list;
// ...
};
Re: Грабли: gcc 4.8.1 не соответствует C++ 11
От: jazzer Россия Skype: enerjazzer
Дата: 18.10.13 15:42
Оценка:
Здравствуйте, SkyDance, Вы писали:

SD>Не знаю, кто там у них такой умный, но на всякий случай предупрежу об этом занятном эффекте. Все-таки, обычно принято ругать MSVC, а тут мне очень хочется оторвать кому-то руки и пришить таки к плечам.


Я знаю, что у тебя буст нельзя, а вот у тех, кому можно, проблем нет — они юзают boost::size и boost::distance — первый для std::list просто-напросто не определен, потому что нельзя (было) гарантировать O(1)
Так что и напарываться не на что.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[10]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Evgeny.Panasyuk Россия  
Дата: 19.10.13 22:16
Оценка:
Здравствуйте, Erop, Вы писали:

EP>>Но это ведь не проблема STL, а вообще любых сторонних библиотек, на любую тему

E>Нет, тут легко нарваться на то, что код вокруг использует один stl, а ты другой. Ну, сажем, что-то может не так слинковаться, например...

Я вот, например, в статической библиотеке спокойно использую Boost такой версии, какой мне удобно. А клиенты могут использовать любой другой.
Одно из решений проблемы в переименовании namespace'а (влияет на mangling) + как синтаксический сахар namespace alias (не влияет на mangling).
Утилита Boost.BCP умеет автоматически переименовывать namespace и вставлять namespace alias. Например так:
// replaces
namespace boost {
// with
namespace any_name_you_wish {} namespace boost = any_name_you_wish; namespace any_name_you_wish {


AFAIK, в STL библиотеках типа SGI STL или STLPort используется не прибитое гвоздями имя namespace'а, а что-то типа макроса, то есть ещё легче чем в Boost.
Re[10]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: SkyDance Земля  
Дата: 20.10.13 22:49
Оценка:
EP>1. boost::varaint, или если нет, то самописный велик. Уберёт аллокацию и индерекцию, но размер будет максимальным из всех соединений + sizeof(tag) + alignment, и набор типов прибит гвоздями во время компиляции.

Не можем, тип соединения — runtime, берется из конфигурации.

EP>2. использовать простой per-thread аллокатор, либо pool'ы для соединений разных типов — существенно удешевит аллокацию.


А это уже сделано, опять же behind the scenes, я про аллокаторы просто не стал писать, ибо оно не относится к теме.

EP>Конечно свой аллокатор для узлов списка будет полезен сам по себе, и существенно сократит стоимость erase+insert, но splice всё-таки намного дешевле. То есть можно использовать и аллокатор и splice.


Можно, но arena allocator в моём случае один atomic fetch and add, освобождается всё вместе с самим пулом. Можно, конечно, посмотреть, будет ли со splice еще быстрее, но, во-первых, вряд ли, во-вторых, даже если будет, весь этот выигрыш будет околонулевым в сравнении с единственным вызовом функции socket connect()

EP>Насколько я вижу, в этом случае ручной доступ к left-right будет только в трёх случаях, два из которых тривиальные:


Да, я понимаю. Просто пока не видно необходимости, если верить профайлеру, в этом месте проблем больше нет.
Re[11]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Erop Россия  
Дата: 21.10.13 05:37
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>AFAIK, в STL библиотеках типа SGI STL или STLPort используется не прибитое гвоздями имя namespace'а, а что-то типа макроса, то есть ещё легче чем в Boost.


Ну что-то такое да, можно, но это уже почти совсем тоже самое, что просты выдрать нужную версию листа и положить в другой наймспей, жа и юзать
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[11]: Грабли: gcc 4.8.1 не соответствует C++ 11
От: Evgeny.Panasyuk Россия  
Дата: 21.10.13 12:40
Оценка:
Здравствуйте, SkyDance, Вы писали:

EP>>1. boost::varaint, или если нет, то самописный велик. Уберёт аллокацию и индерекцию, но размер будет максимальным из всех соединений + sizeof(tag) + alignment, и набор типов прибит гвоздями во время компиляции.

SD>Не можем, тип соединения — runtime, берется из конфигурации.

boost::variant это как раз runtime dispatch по tag'у.

Кстати, если после считывания конфигурации используется только один тип соединения, и этот тип соединения не подгружается из внешних динамических библиотек, то этот runtime dispatch можно вынести на самый вверх call stack'а, а не делать при работе с каждым отдельным соединением.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.