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:
jduncanator 2019-02-04 17:02:47 +11:00
commit 748e72c496
5 changed files with 175 additions and 64 deletions

View file

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

View file

@ -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,7 +24,11 @@ namespace Ryujinx.Common.Logging
public void Log(object sender, LogEventArgs e)
{
StringBuilder sb = new StringBuilder();
StringBuilder sb = _stringBuilderPool.Allocate();
try
{
sb.Clear();
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
sb.Append(" | ");
@ -64,6 +70,11 @@ namespace Ryujinx.Common.Logging
Console.WriteLine(sb.ToString());
}
}
finally
{
_stringBuilderPool.Release(sb);
}
}
public void Dispose()
{

View file

@ -6,9 +6,9 @@ namespace Ryujinx.Common.Logging
{
public class FileLogTarget : ILogTarget
{
private StreamWriter _logWriter;
private static readonly ObjectPool<StringBuilder> _stringBuilderPool = SharedPools.Default<StringBuilder>();
public string Path { get; private set; }
private readonly StreamWriter _logWriter;
public FileLogTarget(string path)
: this(path, FileShare.Read, FileMode.Append)
@ -16,14 +16,16 @@ namespace Ryujinx.Common.Logging
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();
try
{
sb.Clear();
sb.AppendFormat(@"{0:hh\:mm\:ss\.fff}", e.Time);
sb.Append(" | ");
@ -55,6 +57,11 @@ namespace Ryujinx.Common.Logging
_logWriter.WriteLine(sb.ToString());
_logWriter.Flush();
}
finally
{
_stringBuilderPool.Release(sb);
}
}
public void Dispose()
{

View 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;
}
}
}
}
}

View 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;
}
}
}