Logger: Optimize Console/File logging targets
Implement a simple ObjectPool to pool up StringBuilders to avoid causing excessive GCing of gen1/2 items when large amounts of log entries are being generated. We can also pre-determine the async overflow action at initialization time, allowing for an easy optimization in the message enqueue function, avoiding a number of comparisons.
This commit is contained in:
parent
03fc0aa731
commit
748e72c496
5 changed files with 175 additions and 64 deletions
|
@ -25,7 +25,7 @@ namespace Ryujinx.Common.Logging
|
|||
|
||||
private BlockingCollection<LogEventArgs> _messageQueue;
|
||||
|
||||
private AsyncLogTargetOverflowAction _overflowAction;
|
||||
private readonly int _overflowTimeout;
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target)
|
||||
: this(target, -1, AsyncLogTargetOverflowAction.Block)
|
||||
|
@ -34,10 +34,8 @@ namespace Ryujinx.Common.Logging
|
|||
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
|
||||
{
|
||||
_target = target;
|
||||
|
||||
_overflowAction = overflowAction;
|
||||
|
||||
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
|
||||
_overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0;
|
||||
|
||||
_messageThread = new Thread(() => {
|
||||
while (!_messageQueue.IsCompleted)
|
||||
|
@ -65,10 +63,7 @@ namespace Ryujinx.Common.Logging
|
|||
{
|
||||
if (!_messageQueue.IsAddingCompleted)
|
||||
{
|
||||
if(!_messageQueue.TryAdd(e) && _overflowAction == AsyncLogTargetOverflowAction.Block)
|
||||
{
|
||||
_messageQueue.Add(e);
|
||||
}
|
||||
_messageQueue.TryAdd(e, _overflowTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ namespace Ryujinx.Common.Logging
|
|||
{
|
||||
public class ConsoleLogTarget : ILogTarget
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
private static readonly ConcurrentDictionary<LogLevel, ConsoleColor> _logColors;
|
||||
|
||||
static ConsoleLogTarget()
|
||||
|
@ -22,46 +24,55 @@ namespace Ryujinx.Common.Logging
|
|||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = _stringBuilderPool.Allocate();
|
||||
|
||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
|
||||
sb.Append(" | ");
|
||||
sb.AppendFormat("{0:d4}", e.ThreadId);
|
||||
sb.Append(' ');
|
||||
sb.Append(e.Message);
|
||||
|
||||
if (e.Data != null)
|
||||
try
|
||||
{
|
||||
PropertyInfo[] props = e.Data.GetType().GetProperties();
|
||||
sb.Clear();
|
||||
|
||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
|
||||
sb.Append(" | ");
|
||||
sb.AppendFormat("{0:d4}", e.ThreadId);
|
||||
sb.Append(' ');
|
||||
sb.Append(e.Message);
|
||||
|
||||
foreach (var prop in props)
|
||||
if (e.Data != null)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
sb.Append(prop.GetValue(e.Data));
|
||||
sb.Append(" - ");
|
||||
PropertyInfo[] props = e.Data.GetType().GetProperties();
|
||||
|
||||
sb.Append(' ');
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
sb.Append(prop.GetValue(e.Data));
|
||||
sb.Append(" - ");
|
||||
}
|
||||
|
||||
// We remove the final '-' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
}
|
||||
|
||||
// We remove the final '-' from the string
|
||||
if (props.Length > 0)
|
||||
if (_logColors.TryGetValue(e.Level, out ConsoleColor color))
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
Console.ForegroundColor = color;
|
||||
|
||||
Console.WriteLine(sb.ToString());
|
||||
|
||||
Console.ResetColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (_logColors.TryGetValue(e.Level, out ConsoleColor color))
|
||||
finally
|
||||
{
|
||||
Console.ForegroundColor = color;
|
||||
|
||||
Console.WriteLine(sb.ToString());
|
||||
|
||||
Console.ResetColor();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(sb.ToString());
|
||||
_stringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,54 +6,61 @@ namespace Ryujinx.Common.Logging
|
|||
{
|
||||
public class FileLogTarget : ILogTarget
|
||||
{
|
||||
private StreamWriter _logWriter;
|
||||
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
private readonly StreamWriter _logWriter;
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
public FileLogTarget(string path)
|
||||
: this(path, FileShare.Read, FileMode.Append)
|
||||
{ }
|
||||
|
||||
public FileLogTarget(string path, FileShare fileShare, FileMode fileMode)
|
||||
{
|
||||
this.Path = path;
|
||||
|
||||
this._logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
StringBuilder sb = _stringBuilderPool.Allocate();
|
||||
|
||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
|
||||
sb.Append(" | ");
|
||||
sb.AppendFormat("{0:d4}", e.ThreadId);
|
||||
sb.Append(' ');
|
||||
sb.Append(e.Message);
|
||||
|
||||
if (e.Data != null)
|
||||
try
|
||||
{
|
||||
PropertyInfo[] props = e.Data.GetType().GetProperties();
|
||||
sb.Clear();
|
||||
|
||||
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
|
||||
sb.Append(" | ");
|
||||
sb.AppendFormat("{0:d4}", e.ThreadId);
|
||||
sb.Append(' ');
|
||||
sb.Append(e.Message);
|
||||
|
||||
foreach (var prop in props)
|
||||
if (e.Data != null)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
sb.Append(prop.GetValue(e.Data));
|
||||
sb.Append(" - ");
|
||||
PropertyInfo[] props = e.Data.GetType().GetProperties();
|
||||
|
||||
sb.Append(' ');
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
sb.Append(prop.GetValue(e.Data));
|
||||
sb.Append(" - ");
|
||||
}
|
||||
|
||||
// We remove the final '-' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
}
|
||||
|
||||
// We remove the final '-' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
_logWriter.WriteLine(sb.ToString());
|
||||
_logWriter.Flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stringBuilderPool.Release(sb);
|
||||
}
|
||||
|
||||
_logWriter.WriteLine(sb.ToString());
|
||||
_logWriter.Flush();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
78
Ryujinx.Common/Pools/ObjectPool.cs
Normal file
78
Ryujinx.Common/Pools/ObjectPool.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class ObjectPool<T>
|
||||
where T : class
|
||||
{
|
||||
private T _firstItem;
|
||||
private readonly T[] _items;
|
||||
|
||||
private readonly Func<T> _factory;
|
||||
|
||||
public ObjectPool(Func<T> factory, int size)
|
||||
{
|
||||
_factory = factory;
|
||||
_items = new T[size - 1];
|
||||
}
|
||||
|
||||
public T Allocate()
|
||||
{
|
||||
var instance = _firstItem;
|
||||
|
||||
if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance))
|
||||
{
|
||||
instance = AllocateInternal();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private T AllocateInternal()
|
||||
{
|
||||
var items = _items;
|
||||
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
var instance = items[i];
|
||||
|
||||
if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance))
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
return _factory();
|
||||
}
|
||||
|
||||
public void Release(T obj)
|
||||
{
|
||||
if (_firstItem == null)
|
||||
{
|
||||
_firstItem = obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseInternal(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseInternal(T obj)
|
||||
{
|
||||
var items = _items;
|
||||
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
if (items[i] == null)
|
||||
{
|
||||
items[i] = obj;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
Ryujinx.Common/Pools/SharedPools.cs
Normal file
20
Ryujinx.Common/Pools/SharedPools.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class SharedPools
|
||||
{
|
||||
private static class DefaultPool<T>
|
||||
where T : class, new()
|
||||
{
|
||||
public static readonly ObjectPool<T> Instance = new ObjectPool<T>(() => new T(), 20);
|
||||
}
|
||||
|
||||
public static ObjectPool<T> Default<T>() where T : class, new()
|
||||
{
|
||||
return DefaultPool<T>.Instance;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue