Ваш вариант использования обновляет List<T>
, который может содержать миллионы записей, а обновленные записи могут быть вложенным списком или просто одной записью.
Ниже приведена схема:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string Category { get; set; }
}
Содержит ли Product
первичный ключ, что означает, что каждый объект Product
может быть однозначно идентифицирован, нет дубликатов и каждое обновление предназначено для одной уникальной записи?
Если Да, то лучше расположить List<T>
в форме Dictionary<int,T>
, что будет означать, что для IEnumerable<T>
каждое обновление будет иметь временную сложность O(1)
, и это будет означать, что все обновления могут выполняться в зависимости от размер IEnumerable<T>
, который, я не ожидаю, будет очень большим, и хотя потребуется дополнительное выделение памяти для другой структуры данных, но это будет очень быстрое решение. @JamieLupton уже предоставил решение в аналогичных строках
В случае, если Product
повторяется, первичный ключ отсутствует, тогда приведенное выше решение недействительно, тогда идеальным способом просмотра List<T>
является двоичный поиск, временная сложность которого составляет O(logN)
.
Теперь, поскольку размер IEnumerable<T>
сравнительно мал, скажем, M, поэтому общая временная сложность будет O(M*logN)
, где M намного меньше, чем N, и ею можно пренебречь.
List<T>
поддерживает API бинарного поиска, который предоставляет индекс элемента, который затем можно использовать для обновления объекта по соответствующему индексу, проверьте пример здесь
Лучшим вариантом, по моему мнению, для такого большого количества записей будет параллельная обработка вместе с бинарным поиском.
Теперь, поскольку безопасность потоков является проблемой, что я обычно делаю, так это делю List<T>
на List<T>[]
, поскольку тогда каждый блок может быть назначен отдельному потоку, простым способом является использование MoreLinq
пакетного API, где вы можете получить количество системных процессоров. используя Environment.ProcessorCount
, а затем создайте IEnumerable<IEnumerable<T>>
следующим образом:
var enumerableList = List<T>.Batch(Environment.ProcessorCount).ToList();
Другой способ - следующий пользовательский код:
public static class MyExtensions
{
// data - List<T>
// dataCount - Calculate once and pass to avoid accessing the property everytime
// Size of Partition, which can be function of number of processors
public static List<T>[] SplitList<T>(this List<T> data, int dataCount, int partitionSize)
{
int remainderData;
var fullPartition = Math.DivRem(dataCount, partitionSize, out remainderData);
var listArray = new List<T>[fullPartition];
var beginIndex = 0;
for (var partitionCounter = 0; partitionCounter < fullPartition; partitionCounter++)
{
if (partitionCounter == fullPartition - 1)
listArray[partitionCounter] = data.GetRange(beginIndex, partitionSize + remainderData);
else
listArray[partitionCounter] = data.GetRange(beginIndex, partitionSize);
beginIndex += partitionSize;
}
return listArray;
}
}
Теперь вы можете создать Task[]
, где каждый Task
назначается каждому элементу List<T>
, на List<T>[]
, сгенерированном выше, затем двоичный поиск для каждого подраздела. Хотя это повторяется, но будет использовать возможности параллельной обработки и двоичного поиска. Каждый Task
может быть запущен, а затем мы можем подождать, используя Task.WaitAll(taskArray)
, чтобы дождаться завершения обработки задачи.
Кроме того, если вы хотите создать Dictionary<int,T>[]
и, таким образом, использовать параллельную обработку, это будет самым быстрым.
Окончательную интеграцию List<T>[]
в List<T>
можно выполнить с помощью Linq Aggregation
или SelectMany
следующим образом:
List<T>[] splitListArray = Fetch splitListArray;
// Process splitListArray
var finalList = splitListArray.SelectMany(obj => obj).ToList()
Другим вариантом может быть использование Parallel.ForEach
вместе с потокобезопасной структурой данных, такой как ConcurrentBag<T>
, или может быть ConcurrentDictionary<int,T>
в случае, если вы заменяете полный объект, но если его свойство обновляется, тогда будет работать простой List<T>
. Parallel.ForEach
внутренне используйте разделитель диапазона, аналогичный тому, что я предложил выше
Упомянутые выше решения в идеале зависят от вашего варианта использования, вы сможете использовать комбинацию для достижения наилучшего результата. Дайте мне знать, если вам нужен конкретный пример
person
Mrinal Kamboj
schedule
06.11.2018