#c# #performance #coding-style
#c# #Производительность #стиль кодирования
Вопрос:
У меня есть коллекция объектов A. У каждого A есть поле, которое соотносится с объектом B, из которого у меня есть другая коллекция. Другими словами, каждый B присоединяется к подмножеству коллекции As (но только концептуально, а не в коде). Это поле, связывающее A с B, может меняться в течение срока службы системы. Существуют системные требования, которые препятствуют изменению этой структуры.
Если мне нужно многократно выполнять операции над каждым набором A для каждого B, было бы лучше повторно использовать метод Where() для коллекции A или создать другую коллекцию, которой владеет B, и класс, который управляет добавлением и удалением соответствующих элементов.
Позвольте мне посмотреть, смогу ли я зафиксировать это в коде:
class A {
public B owner;
...
}
class B {
...
}
class FrequentlyCalledAction {
public DoYourThing(B current) {
List<A> relevantItems = listOfAllAItems.Where(x => x.owner == current).ToList()
foreach (A item in relevantItems) {
...
}
}
}
Против:
class A {
public B owner;
...
}
class B {
public List<A> itsItems;
}
class FrequentlyCalledAction {
public void DoYourThing(B current) {
foreach (A item in current.itsItems) {
...
}
}
}
class AManager {
public void moveItem(A item, B from, B to) {
from.itsItems.remove(item);
to.itsItems.add(item);
}
}
Комментарии:
1. Как часто вы это делаете? Как часто меняются отношения A-> B? Насколько велик набор As и Bs? Разве у B не может быть ссылки на A?
2. DoYourThing — один из самых частых вызовов в системе. Отношения A-> B меняются, возможно, один раз за каждые 4 вызова DoYourThing. Они оба представляют собой небольшие наборы — может быть, 50 B, может быть, 100 A.
3. Как часто A должен знать, кто его владелец? Не могли бы вы просто сохранить A для их владельца и определить, кто их владелец, когда вам это нужно?
4. Существуют и другие части системы, которые требуют, чтобы A знал своего владельца.
5. Что вы решили сделать в итоге?
Ответ №1:
Это в первую очередь зависит от размера наборов. Если элементов всего несколько, накладные расходы, связанные с решением two, больше, чем прирост производительности.
В этом случае я бы использовал решение one, поскольку оно имеет лучшую читаемость и менее сложное в управлении.
Если в наборе тысячи элементов, я бы выбрал второе решение. moveItems
Метод представляет собой операцию O (n), но кажется, что в вашем сценарии больше операций чтения, чем записи. Таким образом, вы получаете большую производительность за счет более структурированного дизайна.
Комментарии:
1. Я думаю, вы можете улучшить MoveItems до O (1), используя HashSets.
Ответ №2:
На самом деле все зависит от размера вашей коллекции. Sol 2 более сложный, но более быстрый для большой коллекции, в то время как sol1 может быть очень быстрым для менее чем 100/1000 элементов или около того.
Комментарии:
1. Есть ли недостаток в использовании пространства в решении 2? Имеет ли это значение, поскольку все они на самом деле просто ссылки? Перевешивает ли увеличение производительности?
2. Опять же, все зависит от размера коллекции, но на самом деле в списке будут сохранены только ссылки.
Ответ №3:
Поскольку наборы небольшие (~ 100 элементов) и они часто меняются (~ каждые 4 итерации), сделайте это, а затем посмотрите, есть ли у вас проблема.
public DoYourThing(B current)
{
foreach(A item in listOfAllAItems.Where(a => a.owner == current))
{
...
}
}
Я не вижу никакого смысла в приведении IEnumrable<A> к IList<A> .
Если это вызывает у вас проблемы с производительностью, я не думаю AManager
, что это ваш лучший ответ, хотя это может зависеть от того, насколько сильно меняются отношения.
Ответ №4:
Если вы выберете решение 2, возможно, стоит использовать HashSet, а не список. Хэш-набор равен O (1) для добавления и удаления, тогда как список равен O (1) для добавления и O (n) для удаления.
Другой вариант — это тот, преимущество которого в том, что пользователям A amp; B не нужно помнить об использовании AManager
:
static class GlobalDictionary
{
private static Dictionary<B,HashSet<A>> dictionary = new Dictionary<B,HashSet<A>>();
public static HashSet<A> this[B obj]
{
// You could remove the set and have it check to see if a Set exists for a B
// if not you would create it.
get {return dictionary[obj];}
set {dictionary[obj] = value;}
}
}
class A
{
private B owner;
public B Owner
{
get{ return owner;}
set
{
if (owner != null) GlobalDictionary[owner].Remove(this);
owner = value;
GlobalDictionary[owner].Add(this);
}
}
}
class B
{
public B()
{
GlobalDictionary[this] = new HashSet<A>();
}
public IEnumerable<A> Children
{
get
{
return GlobalDictionary[this];
}
}
}
Я не пробовал это, так что, вероятно, это потребует некоторых настроек, но вы должны понять идею.
Комментарии:
1. Возможно, вы захотите подумать о том, как удалить B из глобального словаря, когда на них больше ничего не ссылается, чтобы сборщик мусора мог их удалить.