Improve podcast episode GridEntry creation performance.
Tested on a library with ~5000 podcast episodes on an AMD Ryzen 7700X. Startup time decreases by ~400 ms in Release mode.
This commit is contained in:
parent
f0d7a7bf64
commit
657a7bb6bc
@ -1,7 +1,6 @@
|
|||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Dinah.Core.Threading;
|
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using System;
|
using System;
|
||||||
@ -9,7 +8,7 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationUiBase.GridView
|
namespace LibationUiBase.GridView
|
||||||
@ -311,6 +310,36 @@ namespace LibationUiBase.GridView
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates <see cref="IGridEntry"/> for all non-episode books in an enumeration of <see cref="DataLayer.LibraryBook"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
|
||||||
|
public static async Task<List<TEntry>> GetAllProductsAsync<TEntry>(IEnumerable<LibraryBook> libraryBooks, Func<LibraryBook, bool> includeIf, Func<LibraryBook, TEntry> factory)
|
||||||
|
where TEntry : IGridEntry
|
||||||
|
{
|
||||||
|
var products = libraryBooks.Where(includeIf).ToArray();
|
||||||
|
if (products.Length == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
int parallelism = int.Max(1, Environment.ProcessorCount - 1);
|
||||||
|
|
||||||
|
(int batchSize, int rem) = int.DivRem(products.Length, parallelism);
|
||||||
|
if (rem != 0) batchSize++;
|
||||||
|
|
||||||
|
var syncContext = SynchronizationContext.Current;
|
||||||
|
|
||||||
|
//Asynchronously create a GridEntry for every book in the library
|
||||||
|
var tasks = products.Chunk(batchSize).Select(batch => Task.Run(() =>
|
||||||
|
{
|
||||||
|
SynchronizationContext.SetSynchronizationContext(syncContext);
|
||||||
|
return batch.Select(factory).OfType<TEntry>().ToArray();
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
~GridEntry()
|
~GridEntry()
|
||||||
{
|
{
|
||||||
PictureStorage.PictureCached -= PictureStorage_PictureCached;
|
PictureStorage.PictureCached -= PictureStorage_PictureCached;
|
||||||
|
|||||||
@ -3,8 +3,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace LibationUiBase.GridView
|
namespace LibationUiBase.GridView
|
||||||
{
|
{
|
||||||
@ -36,29 +34,9 @@ namespace LibationUiBase.GridView
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates <see cref="LibraryBookEntry{TStatus}"/> for all non-episode books in an enumeration of <see cref="LibraryBook"/>.
|
/// Creates <see cref="LibraryBookEntry{TStatus}"/> for all non-episode books in an enumeration of <see cref="LibraryBook"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
|
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="System.Threading.SynchronizationContext.Current"/> to be valid.</remarks>
|
||||||
public static async Task<List<IGridEntry>> GetAllProductsAsync(IEnumerable<LibraryBook> libraryBooks)
|
public static async Task<List<IGridEntry>> GetAllProductsAsync(IEnumerable<LibraryBook> libraryBooks)
|
||||||
{
|
=> await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsProduct(), lb => new LibraryBookEntry<TStatus>(lb) as IGridEntry);
|
||||||
var products = libraryBooks.Where(lb => lb.Book.IsProduct()).ToArray();
|
|
||||||
if (products.Length == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
int parallelism = int.Max(1, Environment.ProcessorCount - 1);
|
|
||||||
|
|
||||||
(int batchSize, int rem) = int.DivRem(products.Length, parallelism);
|
|
||||||
if (rem != 0) batchSize++;
|
|
||||||
|
|
||||||
var syncContext = SynchronizationContext.Current;
|
|
||||||
|
|
||||||
//Asynchronously create an ILibraryBookEntry for every book in the library
|
|
||||||
var tasks = products.Chunk(batchSize).Select(batch => Task.Run(() =>
|
|
||||||
{
|
|
||||||
SynchronizationContext.SetSynchronizationContext(syncContext);
|
|
||||||
return batch.Select(lb => new LibraryBookEntry<TStatus>(lb) as IGridEntry);
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (await Task.WhenAll(tasks)).SelectMany(a => a).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
protected override string GetBookTags() => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using DataLayer;
|
using DataLayer;
|
||||||
|
using Dinah.Core.Collections.Generic;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -62,56 +63,32 @@ namespace LibationUiBase.GridView
|
|||||||
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
|
/// <remarks>Can be called from any thread, but requires the calling thread's <see cref="SynchronizationContext.Current"/> to be valid.</remarks>
|
||||||
public static async Task<List<ISeriesEntry>> GetAllSeriesEntriesAsync(IEnumerable<LibraryBook> libraryBooks)
|
public static async Task<List<ISeriesEntry>> GetAllSeriesEntriesAsync(IEnumerable<LibraryBook> libraryBooks)
|
||||||
{
|
{
|
||||||
var seriesBooks = libraryBooks.Where(lb => lb.Book.IsEpisodeParent()).ToArray();
|
var seriesEntries = await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeParent(), lb => new SeriesEntry<TStatus>(lb, []) as ISeriesEntry);
|
||||||
var allEpisodes = libraryBooks.Where(lb => lb.Book.IsEpisodeChild()).ToArray();
|
var seriesDict = seriesEntries.ToDictionarySafe(s => s.AudibleProductId);
|
||||||
|
await GetAllProductsAsync(libraryBooks, lb => lb.Book.IsEpisodeChild(), CreateAndLinkEpisodeEntry);
|
||||||
|
|
||||||
var seriesEntries = new ISeriesEntry[seriesBooks.Length];
|
//sort episodes by series order descending and update SeriesEntry
|
||||||
var seriesEpisodes = new ILibraryBookEntry[seriesBooks.Length][];
|
foreach (var series in seriesEntries)
|
||||||
|
|
||||||
var syncContext = SynchronizationContext.Current;
|
|
||||||
var options = new ParallelOptions { MaxDegreeOfParallelism = int.Max(1, Environment.ProcessorCount - 1) };
|
|
||||||
|
|
||||||
//Asynchronously create an ILibraryBookEntry for every episode in the library
|
|
||||||
await Parallel.ForEachAsync(getAllEpisodes(), options, createEpisodeEntry);
|
|
||||||
|
|
||||||
//Match all episode entries to their corresponding parents
|
|
||||||
for (int i = seriesEntries.Length - 1; i >= 0; i--)
|
|
||||||
{
|
{
|
||||||
var series = seriesEntries[i];
|
series.Children.Sort((a, b) => -a.SeriesOrder.CompareTo(b.SeriesOrder));
|
||||||
|
|
||||||
//Sort episodes by series order descending, then add them to their parent's entry
|
|
||||||
Array.Sort(seriesEpisodes[i], (a, b) => -a.SeriesOrder.CompareTo(b.SeriesOrder));
|
|
||||||
series.Children.AddRange(seriesEpisodes[i]);
|
|
||||||
series.UpdateLibraryBook(series.LibraryBook);
|
series.UpdateLibraryBook(series.LibraryBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
return seriesEntries.Where(s => s.Children.Count != 0).Cast<ISeriesEntry>().ToList();
|
return seriesEntries.Where(s => s.Children.Count != 0).ToList();
|
||||||
|
|
||||||
//Create a LibraryBookEntry for a single episode
|
//Create a LibraryBookEntry for an episode and link it to its series parent
|
||||||
ValueTask createEpisodeEntry((int seriesIndex, int episodeIndex, LibraryBook episode) data, CancellationToken cancellationToken)
|
LibraryBookEntry<TStatus> CreateAndLinkEpisodeEntry(LibraryBook episode)
|
||||||
{
|
{
|
||||||
SynchronizationContext.SetSynchronizationContext(syncContext);
|
foreach (var s in episode.Book.SeriesLink)
|
||||||
var parent = seriesEntries[data.seriesIndex];
|
|
||||||
seriesEpisodes[data.seriesIndex][data.episodeIndex] = new LibraryBookEntry<TStatus>(data.episode, parent);
|
|
||||||
return ValueTask.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Enumeration all series episodes, along with the index to its seriesEntries entry
|
|
||||||
//and an index to its seriesEpisodes entry
|
|
||||||
IEnumerable<(int seriesIndex, int episodeIndex, LibraryBook episode)> getAllEpisodes()
|
|
||||||
{
|
|
||||||
for (int i = 0; i < seriesBooks.Length; i++)
|
|
||||||
{
|
{
|
||||||
var series = seriesBooks[i];
|
if (seriesDict.TryGetValue(s.Series.AudibleSeriesId, out var seriesParent))
|
||||||
var childEpisodes = allEpisodes.FindChildren(series);
|
{
|
||||||
|
var entry = new LibraryBookEntry<TStatus>(episode, seriesParent);
|
||||||
SynchronizationContext.SetSynchronizationContext(syncContext);
|
seriesParent.Children.Add(entry);
|
||||||
seriesEntries[i] = new SeriesEntry<TStatus>(series, []);
|
return entry;
|
||||||
seriesEpisodes[i] = new ILibraryBookEntry[childEpisodes.Count];
|
}
|
||||||
|
|
||||||
for (int j = 0; j < childEpisodes.Count; j++)
|
|
||||||
yield return (i, j, childEpisodes[j]);
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user