Re[66]: MS забило на дотнет. Питону - да, сишарпу - нет?
От: vdimas Россия  
Дата: 16.09.21 14:57
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Ну вот как раз джависты показывали, что после уплотнения GC узлы подобных объектов располагаются в памяти рядом, т.к. доступны по ссылкам только из родительских узлов, т.е. по мере естественного обхода ссылок выстроились в правильный порядок в памяти.

S>Это всё ещё значительно менее плотно, чем int[].

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

Т.е., тебе пока требуется "просто локальность", а в ней разница в 64 байта или 1кб практически отсутствует в разогретом кеше.


S>>>Это как раз будет убивать производительность на маленьких коллекциях.

V>>Твои графики имеющейся дотнетной реализации показывают, что не будет.
S>Там если присмотреться, то для некоторых размеров мы объезжаем дотнетную реализацию.

Я не вижу на графиках объезжания на малых объемах данных, но ты утверждаешь обратное.


V>>Т.е. рост дерева будет дороже, но можно будет замерить доступ по чтению (если успешно заинлайнится), т.е. чтобы понять — стоит ли вообще копать в эту сторону.

S>Рост будет стоить ровно столько же.

Это с большой копипастой.
Я бы для простоты сделал на виртуальных методах или if-ах с последующим разбоксированием.
Это же все-равно лишь эксперимент для замера эффективности другой функциональности.


V>>Так непонятен график.

V>>Он должен быть линейным в логарифмической шкале для имеющейся реализации.
V>>Что такого может произойти на 256 элементах, что до него рост линейный, потом скачок, потом опять линейный рост?
S>Я полагаю — пересечение границы какого-то из кэшей.

Когда просто по чтению?
Та граница должна будет пересечься и у тебя в каком-то месте.

Можно было бы повторить замеры с предварительным full GC после наполнения дерева.


S>>>Потому что не взлетает даже когда у нас "рекурсивный" вызов делается напрямую, по call CONST.

V>>Ну ты же обогнал имеющуюся реализацию?
S>Я обогнал на коде без вызовов.

Код без вызовов не объясняет отклонение от log(N).


V>>Косвенные вызовы тоже умеют обрабатываться предвыборкой до какой-то степени.

S>Вряд ли.

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


V>>И там лишние 8 байт на Cell особой роли играть не будут.

S>Да там не нужен этот Cell вообще. Это же B+ дерево — тип у всех детей любой ноды одинаков. Поэтому достаточно хранить ровно одну ссылку на getItem.

А да, в твоей релизации одинаковы.
Тем более.


S>Но это всё ещё не поможет — даже когда вместо косвенного вызова внутри getItem стоит прямой вызов на getItem вложенного класса, производительность падает на дно.


Но в дотнетной реализации именно это?



V>>Алгоритм можно расширить на n-арное дерево.

V>>Считай, что твой 16-арный узел — это 8 по 2, но не принципиально, пусть там было бы не 16, а 17.
V>>Алгоритм выявляет место, где требуется произвести поворот дерева.
S>Хм. Сходу не очень понимаю, как будет работать поворот небинарного дерева.

При повороте дочерний узел заменяет собой родительский узел.
В иммутабельном виде как таковой "замены" нет, есть перестроение подиерархии.
Красно-черные позволяют при перестроении перестраивать ограниченное кол-во узлов, т.к. допускается разница в уровнях в 1 по разным веткам.

А у тебя при росте вложенности потребуется перестроить всё дерево, насколько я сейчас понял.


V>>Речь про арность.

S>Речь про лишние вызовы. Они доминируют над арностью.

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

В иммутабельном виде этой функциональности нет, насколько я понимаю, поэтому обход достаточно дорогой.


S>Я же говорю — был прошлогодний код, который работал на гомогенном дереве. Но в нём были проблемы с выделением лишней памяти в листьях. График, который я показал — оттуда.

S>Сейчас я пытаюсь реализовать код, в котором нет лишних данных. Технически, это получилось, но вот лишние вызовы портят всю малину.

Попробуй опять с гомогенными узлами.
Там никакая рекурсия не нужна, вирутальность на верхнем уровне тоже, просто сдвигаешься в цикле до листового узла.

Причём, твой узел заведомо "знает", ссылается ли он на листья или ветки, это один флаг.
Затем можно приводить ссылку на дочерний узел через Unsafe.As<T>(Object).

Смотри какие фокусы выдаёт:
class Box<T>
    where T : struct 
{
    public readonly T Value;

    public Box(T value) => Value = value;
}

class Box {
    public static Box<T> From<T>(T value) where T : struct => new(value);
}

private static void Main() {
    object boxedDouble = Box.From(1.0);
    var boxedLong = Unsafe.As<Box<long>>(boxedDouble);
    var doubleBits = boxedLong.Value;
    var isPositive = (doubleBits & 1L << 63) == 0;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.