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 1 commit
Commits
File filter
Filter file types
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.

Always

Just for now

Next
Reduce some allocations with the magic of spans etc.
  • Loading branch information
cvium committed Apr 30, 2021
commit 608cba817c54df60959079719bafca4d7d54269a
@@ -1007,15 +1007,12 @@ private static void DeserializeProviderIds(string value, BaseItem 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), part.Slice(providerDelimiterIndex + 1));
}
}
}
@@ -1057,9 +1054,8 @@ private void DeserializeImages(string value, BaseItem item)
return;
}

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);

@@ -1094,41 +1090,89 @@ public void AppendItemImageInfo(StringBuilder bldr, ItemImageInfo image)
.Append(hash.Replace('*', '/').Replace('|', '\\'));
}

public ItemImageInfo ItemImageInfoFromValueString(string value)
private 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)
{
return null;
}

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

image.Path = RestorePath(parts[0]);
var image = new ItemImageInfo
{
Path = RestorePath(path.ToString())
};

if (long.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var ticks))
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('*');
ReadOnlySpan<char> widthSpan = value[..nextSegment];

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

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)
nextSegment += 1;
if (nextSegment < value.Length - 1)
{
image.BlurHash = parts[5].Replace('/', '*').Replace('\\', '|');
value = value[nextSegment..];
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);
}
}

@@ -2118,27 +2162,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)
@@ -2327,9 +2350,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;
}
}
}
@@ -2575,10 +2621,21 @@ public int GetCount(InternalItemsQuery query)
query.Limit = query.Limit.Value + 4;
}

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

commandText += 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)
@@ -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());
}
}
}
}
ProTip! Use n and p to navigate between commits in a pull request.