CSHARP_GEPARD Telegram 89
Бежим по дереву #алгоритм #память #скорость

Дерево - очень полезная структура данных. Помимо того, что про него спрашивают на собеседованиях, дерево помогает хранить иерархически упорядоченные данные. Например, дерево элементов HTML, дерево зависимостей сущностей в игре, дерево подразделений в компании. Имплементация дерева лаконична и проста:

public class Tree<T>(Tree<T>? parent, T value) {
public readonly List<Tree<T>> Children = [];
public readonly Tree<T>? Parent = parent;
public readonly T Value = value;

public Tree<T> Add(T value) {
var child = new Tree<T>(this, value);
Children.Add(child);
return child;
}
}


Используя связь по ссылке (ведь Tree это класс), мы можем быстро перемещаться по дереву. Имплементация выше предельно упрощена и лишь передаёт суть происходящего.

При работе с деревом, иногда возникает необходимость собрать ВСЕ дочерние элементы. Например, нам надо ответить на вопрос сколько людей работает во всём "Департаменте IT". Это означает, что мы должны просуммировать всех людей, входящих в родительский департамент, потом всех людей из дочерних отделов, а затем всех людей в отделах, входящих в эти отделы, вплоть до самого низа организационной иерархии.

Для решения этой задачи мы можем написать метод с использованием страшного и мощного yield:
public IEnumerable<Tree<T>> GetChildren() {
if (Children.Count == 0) yield break;

foreach (var child in Children)
{
yield return child;
foreach (var subChild in child.GetChildren(true))
{
yield return subChild;
}
}
}

Получается красиво и лакнично. Но, увы, очень прожорливо по памяти. Напомню, что yield не бесплатен и его мощь является следствием генерации объекта перечисления, который является классом, а, значит, каждое его инстанциирование загрязняет память. "Вложенность" при подобном подходе порождает ещё бОльший взрыв потребления памяти. Например, если у родителя 7 дочерних элементов, а у каждого из дочерних ещё 6, а у каждого из них ещё 5... короче, на 13700 элементах потребление памяти составляет 1.2Мб. Это много, особенно, если дерево используется постоянно.

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

[ThreadStatic]
private static Stack<Tree<T>>? _traverseBuffer;

...

public IEnumerable<Tree<T>> TraverseYield() {
var buffer = _traverseBuffer ?? new Stack<Tree<T>>(128);
_traverseBuffer = null;

foreach (var child in Children)
{
buffer.Push(child);
}

while (buffer.Count > 0)
{
var current = buffer.Pop();
foreach (var child in current.Children)
{
buffer.Push(child);
}

yield return current;
}

_traverseBuffer = buffer;
}


Побный подход не только ускоряет работу (почти в 4 раза), но и позволяет почти избавиться от аллокации (48 байт против 1.2Мб).

Текст бенчмарка в комментариях. Результаты бенчмарка я тоже помещу в комментарии, так как телеграмм сужает пост, если в нём картинка. С телефона это не заметно, а вот на компьютере - очень, в результате чего код почти невозможно читать.
🔥33👍125🤯2



tgoop.com/csharp_gepard/89
Create:
Last Update:

Бежим по дереву #алгоритм #память #скорость

Дерево - очень полезная структура данных. Помимо того, что про него спрашивают на собеседованиях, дерево помогает хранить иерархически упорядоченные данные. Например, дерево элементов HTML, дерево зависимостей сущностей в игре, дерево подразделений в компании. Имплементация дерева лаконична и проста:

public class Tree<T>(Tree<T>? parent, T value) {
public readonly List<Tree<T>> Children = [];
public readonly Tree<T>? Parent = parent;
public readonly T Value = value;

public Tree<T> Add(T value) {
var child = new Tree<T>(this, value);
Children.Add(child);
return child;
}
}


Используя связь по ссылке (ведь Tree это класс), мы можем быстро перемещаться по дереву. Имплементация выше предельно упрощена и лишь передаёт суть происходящего.

При работе с деревом, иногда возникает необходимость собрать ВСЕ дочерние элементы. Например, нам надо ответить на вопрос сколько людей работает во всём "Департаменте IT". Это означает, что мы должны просуммировать всех людей, входящих в родительский департамент, потом всех людей из дочерних отделов, а затем всех людей в отделах, входящих в эти отделы, вплоть до самого низа организационной иерархии.

Для решения этой задачи мы можем написать метод с использованием страшного и мощного yield:
public IEnumerable<Tree<T>> GetChildren() {
if (Children.Count == 0) yield break;

foreach (var child in Children)
{
yield return child;
foreach (var subChild in child.GetChildren(true))
{
yield return subChild;
}
}
}

Получается красиво и лакнично. Но, увы, очень прожорливо по памяти. Напомню, что yield не бесплатен и его мощь является следствием генерации объекта перечисления, который является классом, а, значит, каждое его инстанциирование загрязняет память. "Вложенность" при подобном подходе порождает ещё бОльший взрыв потребления памяти. Например, если у родителя 7 дочерних элементов, а у каждого из дочерних ещё 6, а у каждого из них ещё 5... короче, на 13700 элементах потребление памяти составляет 1.2Мб. Это много, особенно, если дерево используется постоянно.

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

[ThreadStatic]
private static Stack<Tree<T>>? _traverseBuffer;

...

public IEnumerable<Tree<T>> TraverseYield() {
var buffer = _traverseBuffer ?? new Stack<Tree<T>>(128);
_traverseBuffer = null;

foreach (var child in Children)
{
buffer.Push(child);
}

while (buffer.Count > 0)
{
var current = buffer.Pop();
foreach (var child in current.Children)
{
buffer.Push(child);
}

yield return current;
}

_traverseBuffer = buffer;
}


Побный подход не только ускоряет работу (почти в 4 раза), но и позволяет почти избавиться от аллокации (48 байт против 1.2Мб).

Текст бенчмарка в комментариях. Результаты бенчмарка я тоже помещу в комментарии, так как телеграмм сужает пост, если в нём картинка. С телефона это не заметно, а вот на компьютере - очень, в результате чего код почти невозможно читать.

BY C# Heppard


Share with your friend now:
tgoop.com/csharp_gepard/89

View MORE
Open in Telegram


Telegram News

Date: |

Read now Hui said the messages, which included urging the disruption of airport operations, were attempts to incite followers to make use of poisonous, corrosive or flammable substances to vandalize police vehicles, and also called on others to make weapons to harm police. While some crypto traders move toward screaming as a coping mechanism, many mental health experts have argued that “scream therapy” is pseudoscience. Scientific research or no, it obviously feels good. In handing down the sentence yesterday, deputy judge Peter Hui Shiu-keung of the district court said that even if Ng did not post the messages, he cannot shirk responsibility as the owner and administrator of such a big group for allowing these messages that incite illegal behaviors to exist. More>>
from us


Telegram C# Heppard
FROM American