Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce some allocations with the magic of spans etc. #5938

Merged
merged 7 commits into from May 5, 2021
Merged
Changes from all commits
Commits
File filter
Filter file types
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.

Always

Just for now

@@ -1002,15 +1002,12 @@ internal static void DeserializeProviderIds(string value, IHasProviderIds item)
return;
}

var parts = value.Split('|', StringSplitOptions.RemoveEmptyEntries);

foreach (var part in parts)
foreach (var part in value.SpanSplit('|'))
{
var idParts = part.Split('=');

if (idParts.Length == 2)
var providerDelimiterIndex = part.IndexOf('=');
if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('='))
{
item.SetProviderId(idParts[0], idParts[1]);
item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString());
}
}
}
@@ -1045,9 +1042,8 @@ internal ItemImageInfo[] DeserializeImages(string value)
return Array.Empty<ItemImageInfo>();
}

var parts = value.Split('|' , StringSplitOptions.RemoveEmptyEntries);
var list = new List<ItemImageInfo>();
foreach (var part in parts)
foreach (var part in value.SpanSplit('|'))
{
var image = ItemImageInfoFromValueString(part);

@@ -1086,41 +1082,93 @@ private void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
}
}

public ItemImageInfo ItemImageInfoFromValueString(string value)
internal ItemImageInfo ItemImageInfoFromValueString(ReadOnlySpan<char> value)
{
var parts = value.Split('*', StringSplitOptions.None);
var nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
return null;
}

if (parts.Length < 3)
ReadOnlySpan<char> path = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
return null;
}

var image = new ItemImageInfo();
ReadOnlySpan<char> dateModified = value[..nextSegment];
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
nextSegment = value.Length;
}

image.Path = RestorePath(parts[0]);
ReadOnlySpan<char> imageType = value[..nextSegment];

if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
var image = new ItemImageInfo
{
Path = RestorePath(path.ToString())
};

if (long.TryParse(dateModified, NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
{
image.DateModified = new DateTime(ticks, DateTimeKind.Utc);
}

if (Enum.TryParse(parts[2], true, out ImageType type))
if (Enum.TryParse(imageType.ToString(), true, out ImageType type))
{
image.Type = type;
}

if (parts.Length >= 5)
// Optional parameters: width*height*blurhash
if (nextSegment + 1 < value.Length - 1)
{
if (int.TryParse(parts[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
&& int.TryParse(parts[4], NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1 || nextSegment == value.Length)
{
return image;
}

ReadOnlySpan<char> widthSpan = value[..nextSegment];

value = value[(nextSegment + 1)..];
nextSegment = value.IndexOf('*');
if (nextSegment == -1)
{
nextSegment = value.Length;
}

ReadOnlySpan<char> heightSpan = value[..nextSegment];

if (int.TryParse(widthSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var width)
&& int.TryParse(heightSpan, NumberStyles.Integer, CultureInfo.InvariantCulture, out var height))
{
image.Width = width;
image.Height = height;
}

if (parts.Length >= 6)
if (nextSegment < value.Length - 1)
{
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
value = value[(nextSegment + 1)..];
var length = value.Length;

Span<char> blurHashSpan = stackalloc char[length];
for (int i = 0; i < length; i++)
{
var c = value[i];
blurHashSpan[i] = c switch
{
'/' => '*',
'\\' => '|',
_ => c
};
}

image.BlurHash = new string(blurHashSpan);
}
}

@@ -2110,27 +2158,6 @@ private static bool EnableJoinUserData(InternalItemsQuery query)

private readonly ItemFields[] _allFields = Enum.GetValues<ItemFields>();

private string[] GetColumnNamesFromField(ItemFields field)
{
switch (field)
{
case ItemFields.Settings:
return new[] { "IsLocked", "PreferredMetadataCountryCode", "PreferredMetadataLanguage", "LockedFields" };
case ItemFields.ServiceName:
return new[] { "ExternalServiceId" };
case ItemFields.SortName:
return new[] { "ForcedSortName" };
case ItemFields.Taglines:
return new[] { "Tagline" };
case ItemFields.Tags:
return new[] { "Tags" };
case ItemFields.IsHD:
return Array.Empty<string>();
default:
return new[] { field.ToString() };
}
}

private bool HasField(InternalItemsQuery query, ItemFields name)
{
switch (name)
@@ -2319,9 +2346,32 @@ private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerab
{
if (!HasField(query, field))
{
foreach (var fieldToRemove in GetColumnNamesFromField(field))
switch (field)
{
list.Remove(fieldToRemove);
case ItemFields.Settings:
list.Remove("IsLocked");
list.Remove("PreferredMetadataCountryCode");
list.Remove("PreferredMetadataLanguage");
list.Remove("LockedFields");
break;
case ItemFields.ServiceName:
list.Remove("ExternalServiceId");
break;
case ItemFields.SortName:
list.Remove("ForcedSortName");
break;
case ItemFields.Taglines:
list.Remove("Tagline");
break;
case ItemFields.Tags:
list.Remove("Tags");
break;
case ItemFields.IsHD:
// do nothing
break;
default:
list.Remove(field.ToString());
break;
}
}
}
@@ -2568,9 +2618,9 @@ public int GetCount(InternalItemsQuery query)
}

var commandText = "select "
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query);
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query);

var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0)
@@ -590,18 +590,9 @@ private static void AddMediaInfo(MediaSourceInfo mediaSource, bool isAudio)

public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
{
var info = _openStreams.Values.FirstOrDefault(i =>
{
var liveStream = i as ILiveStream;
if (liveStream != null)
{
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
}

return false;
});
var info = _openStreams.FirstOrDefault(i => i.Value != null && string.Equals(i.Value.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase));

return Task.FromResult(info as IDirectStreamProvider);
return Task.FromResult(info.Value as IDirectStreamProvider);
}

public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
@@ -801,22 +801,22 @@ public string GetActiveRecordingPath(string id)

public ActiveRecordingInfo GetActiveRecordingInfo(string path)
{
if (string.IsNullOrWhiteSpace(path))
if (string.IsNullOrWhiteSpace(path) || _activeRecordings.IsEmpty)
{
return null;
}

foreach (var recording in _activeRecordings.Values)
foreach (var (_, recordingInfo) in _activeRecordings)
{
if (string.Equals(recording.Path, path, StringComparison.Ordinal) && !recording.CancellationTokenSource.IsCancellationRequested)
if (string.Equals(recordingInfo.Path, path, StringComparison.Ordinal) && !recordingInfo.CancellationTokenSource.IsCancellationRequested)
{
var timer = recording.Timer;
var timer = recordingInfo.Timer;
if (timer.Status != RecordingStatus.InProgress)
{
return null;
}

return recording;
return recordingInfo;
}
}

@@ -1621,9 +1621,7 @@ private bool FileExists(string path, string timerId)
}

return _activeRecordings
.Values
.ToList()
.Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
.Any(i => string.Equals(i.Value.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Value.Timer.Id, timerId, StringComparison.OrdinalIgnoreCase));
}

private IRecorder GetRecorder(MediaSourceInfo mediaSource)
@@ -661,7 +661,9 @@ public async Task<List<TunerHostInfo>> DiscoverDevices(int discoveryDurationMs,
_modelCache.Clear();
}

cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(discoveryDurationMs).Token, cancellationToken).Token;
using var timedCancellationToken = new CancellationTokenSource(discoveryDurationMs);
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(timedCancellationToken.Token, cancellationToken);
cancellationToken = linkedCancellationTokenSource.Token;
var list = new List<TunerHostInfo>();

// Create udp broadcast discovery message
@@ -150,7 +150,8 @@ protected virtual List<string> GetStreamFilePaths()

public async Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token;
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token);
cancellationToken = linkedCancellationTokenSource.Token;

// use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039
var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT;
@@ -315,10 +315,9 @@ public string GetLocalizedString(string phrase, string culture)
}

const string Prefix = "Core";
var key = Prefix + culture;

return _dictionaries.GetOrAdd(
key,
culture,
f => GetDictionary(Prefix, culture, DefaultCulture + ".json").GetAwaiter().GetResult());
}

@@ -257,20 +257,17 @@ public void ExpireRequests(bool expireAll = false)
}

// Expire stale connection requests
var code = string.Empty;
var values = _currentRequests.Values.ToList();

for (int i = 0; i < values.Count; i++)
foreach (var (_, currentRequest) in _currentRequests)
{
var added = values[i].DateAdded ?? DateTime.UnixEpoch;
if (DateTime.UtcNow > added.AddMinutes(Timeout) || expireAll)
var added = currentRequest.DateAdded ?? DateTime.UnixEpoch;
if (expireAll || DateTime.UtcNow > added.AddMinutes(Timeout))
{
code = values[i].Code;
_logger.LogDebug("Removing expired request {code}", code);
var code = currentRequest.Code;
_logger.LogDebug("Removing expired request {Code}", code);

if (!_currentRequests.TryRemove(code, out _))
{
_logger.LogWarning("Request {code} already expired", code);
_logger.LogWarning("Request {Code} already expired", code);
}
}
}
@@ -269,14 +269,17 @@ public List<GroupInfoDto> ListGroups(SessionInfo session, ListGroupsRequest requ
var user = _userManager.GetUserById(session.UserId);
List<GroupInfoDto> list = new List<GroupInfoDto>();

foreach (var group in _groups.Values)
lock (_groupsLock)
{
// Locking required as group is not thread-safe.
lock (group)
foreach (var (_, group) in _groups)
{
if (group.HasAccessToPlayQueue(user))
// Locking required as group is not thread-safe.
lock (group)
{
list.Add(group.GetInfo());
if (group.HasAccessToPlayQueue(user))
{
list.Add(group.GetInfo());
}
}
}
}
@@ -71,7 +71,8 @@ public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Transco
/// <returns>A <see cref="Task"/>.</returns>
public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken)
{
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken).Token;
using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationToken);
cancellationToken = linkedCancellationTokenSource.Token;

try
{
ProTip! Use n and p to navigate between commits in a pull request.