diff --git a/sandbox/Benchmark/RecordPerSecondColumn.cs b/sandbox/Benchmark/RecordPerSecondColumn.cs index e4a8dcfa..e00aa474 100644 --- a/sandbox/Benchmark/RecordPerSecondColumn.cs +++ b/sandbox/Benchmark/RecordPerSecondColumn.cs @@ -19,7 +19,7 @@ public class LogWritesPerSecond : IColumn { public string Id => nameof(LogWritesPerSecond); - public string ColumnName => "recs/ms"; + public string ColumnName => "recs/sec"; public bool AlwaysShow => true; @@ -42,7 +42,7 @@ public string GetValue(Summary summary, BenchmarkCase benchmarkCase) var statistics = summary[benchmarkCase].ResultStatistics; var mean = TimeInterval.FromNanoseconds(statistics.Mean); var ms = TimeInterval.FromMilliseconds(1); - return (ms.ToMilliseconds() / (mean.ToMilliseconds() / n)).ToString("F3"); + return (ms.ToMilliseconds() / (mean.ToMilliseconds() / n) * 1000).ToString("F3"); } return "-"; } diff --git a/sandbox/ConsoleApp/Program.cs b/sandbox/ConsoleApp/Program.cs index c2b9c537..b51d7b52 100644 --- a/sandbox/ConsoleApp/Program.cs +++ b/sandbox/ConsoleApp/Program.cs @@ -16,27 +16,22 @@ using ZLogger.Providers; using System.Numerics; using System.Text.Json; +using System.IO.Hashing; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; var factory = LoggerFactory.Create(logging => { logging.AddZLogger(zLogger => { - zLogger.AddConsole(console => + zLogger.AddFile("foo.log", option => { - console.InternalErrorLogger = ex => Console.WriteLine(ex); + option.InternalErrorLogger = ex => Console.WriteLine(ex); - console.ConfigureEnableAnsiEscapeCode = true; - console.OutputEncodingToUtf8 = true; - console.UseJsonFormatter(formatter => + option.UsePlainTextFormatter(formatter => { - formatter.IncludeProperties = IncludeProperties.None; - + formatter.SetPrefixFormatter($"{0:timeonly} | {1:short} | ", (template, info) => template.Format(info.Timestamp, info.LogLevel)); }); - - //console.UsePlainTextFormatter(formatter => - //{ - // formatter.SetPrefixFormatter($"{0:timeonly} | {1:short} | ", (template, info) => template.Format(info.Timestamp, info.LogLevel)); - //}); }); }); }); @@ -44,16 +39,29 @@ var logger = factory.CreateLogger(); -var v3 = new MyVector3 { X = 10.2f, Y = 999.99f, Z = 32.1f }; - -logger.ZLogInformation($"foo = {v3}"); +var x = 10; +var y = 20; +var z = 30; +logger.ZLogInformation($"x={x} y={y} z={z}"); +//while (true) +//{ +// for (int i = 0; i < 100000; i++) +// { +// logger.ZLogInformation($"x={x} y={y} z={z}"); +// } +// Thread.Sleep(TimeSpan.FromSeconds(4)); +//} +var hoge = "hugahuga"; +Console.WriteLine(GC.GetGeneration(hoge)); // int.MaxValue factory.Dispose(); - +var test = new LiteralList(new List { "hoge", null, "huga" }); +test.GetHashCode(); +test.Equals(test); @@ -106,4 +114,57 @@ public ValueTask DisposeAsync() { return default; } +} + +readonly struct LiteralList : IEquatable +{ + readonly List literals; + + public LiteralList(List literals) + { + this.literals = literals; + } + + [ThreadStatic] + static XxHash3? xxhash; + + public override int GetHashCode() + { + var h = xxhash; + if (h == null) + { + h = xxhash = new XxHash3(); + } + else + { + h.Reset(); + } + + var span = CollectionsMarshal.AsSpan(literals); + foreach (var item in span) + { + h.Append(MemoryMarshal.AsBytes(item.AsSpan())); + } + + // https://github.com/Cyan4973/xxHash/issues/453 + // XXH3 64bit -> 32bit, okay to simple cast answered by XXH3 author. + return unchecked((int)h.GetCurrentHashAsUInt64()); + } + + public bool Equals(LiteralList other) + { + var xs = CollectionsMarshal.AsSpan(literals); + var ys = CollectionsMarshal.AsSpan(other.literals); + + if (xs.Length == ys.Length) + { + for (int i = 0; i < xs.Length; i++) + { + if (xs[i] != ys[i]) return false; + } + return true; + } + + return false; + } } \ No newline at end of file diff --git a/src/ZLogger/Internal/ObjectPool.cs b/src/ZLogger/Internal/ObjectPool.cs index 27311ea5..b36b3be6 100644 --- a/src/ZLogger/Internal/ObjectPool.cs +++ b/src/ZLogger/Internal/ObjectPool.cs @@ -19,23 +19,11 @@ internal sealed class ObjectPool int gate; T? root; -#if USE_LIMIT_AND_SIZE - int size; - readonly int limit; - - public ObjectPool(int limit) - { - this.limit = limit; - } - - public int Size => size; -#endif - [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryPop([NotNullWhen(true)] out T? result) { - // Instead of lock, use CompareExchange gate. - // In a worst case, missed cached object(create new one) but it's not a big deal. + // similar as ImmutableInterlocked + TRY_AGAIN: if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) { var v = root; @@ -44,9 +32,6 @@ public bool TryPop([NotNullWhen(true)] out T? result) ref var nextNode = ref v.NextNode; root = nextNode; nextNode = null; -#if USE_LIMIT_AND_SIZE - size--; -#endif result = v; Volatile.Write(ref gate, 0); return true; @@ -54,6 +39,10 @@ public bool TryPop([NotNullWhen(true)] out T? result) Volatile.Write(ref gate, 0); } + else + { + goto TRY_AGAIN; + } result = default; return false; } @@ -61,29 +50,18 @@ public bool TryPop([NotNullWhen(true)] out T? result) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryPush(T item) { + TRY_AGAIN: if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) { item.NextNode = root; root = item; Volatile.Write(ref gate, 0); return true; - -#if USE_LIMIT_AND_SIZE - if (size < limit) - { - item.NextNode = root; - root = item; - size++; - Volatile.Write(ref gate, 0); - return true; - } - else - { - Volatile.Write(ref gate, 0); - } -#endif } - return false; + else + { + goto TRY_AGAIN; + } } } } diff --git a/src/ZLogger/LogStates/InterpolatedStringLogState.cs b/src/ZLogger/LogStates/InterpolatedStringLogState.cs index 33786e7b..4b00b05d 100644 --- a/src/ZLogger/LogStates/InterpolatedStringLogState.cs +++ b/src/ZLogger/LogStates/InterpolatedStringLogState.cs @@ -32,7 +32,7 @@ internal class InterpolatedStringLogState : IZLoggerFormattable, IReferenceCount InterpolatedStringLogState() { this.magicalBoxStorage = new byte[1024]; - this.parameters = new InterpolatedStringParameter[32]; + this.parameters = new InterpolatedStringParameter[24]; } public static InterpolatedStringLogState Create(int formattedCount) diff --git a/src/ZLogger/ZLoggerInterpolatedStringHandler.cs b/src/ZLogger/ZLoggerInterpolatedStringHandler.cs index 43cb06d9..fc935395 100644 --- a/src/ZLogger/ZLoggerInterpolatedStringHandler.cs +++ b/src/ZLogger/ZLoggerInterpolatedStringHandler.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Concurrent; using System.IO; +using System.IO.Hashing; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -280,13 +281,56 @@ public LiteralList(List literals) this.literals = literals; } +#if NET8_0_OR_GREATER + + // literals are all const string, in .NET 8 it is allocated in Non-GC Heap so can compare by address. + // https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/#non-gc-heap + static ReadOnlySpan AsBytes(ReadOnlySpan literals) + { + return MemoryMarshal.CreateSpan( + ref Unsafe.As(ref MemoryMarshal.GetReference(literals)), + literals.Length * Unsafe.SizeOf()); + } + + public override int GetHashCode() + { + return unchecked((int)XxHash3.HashToUInt64(AsBytes(CollectionsMarshal.AsSpan(literals)))); + } + + public bool Equals(LiteralList other) + { + var xs = CollectionsMarshal.AsSpan(literals); + var ys = CollectionsMarshal.AsSpan(other.literals); + + return AsBytes(xs).SequenceEqual(AsBytes(ys)); + } + +#else + + [ThreadStatic] + static XxHash3? xxhash; + public override int GetHashCode() { + var h = xxhash; + if (h == null) + { + h = xxhash = new XxHash3(); + } + else + { + h.Reset(); + } + var span = CollectionsMarshal.AsSpan(literals); + foreach (var item in span) + { + h.Append(MemoryMarshal.AsBytes(item.AsSpan())); + } + // https://github.com/Cyan4973/xxHash/issues/453 // XXH3 64bit -> 32bit, okay to simple cast answered by XXH3 author. - var source = AsBytes(span); - return unchecked((int)System.IO.Hashing.XxHash3.HashToUInt64(source)); + return unchecked((int)h.GetCurrentHashAsUInt64()); } public bool Equals(LiteralList other) @@ -294,25 +338,19 @@ public bool Equals(LiteralList other) var xs = CollectionsMarshal.AsSpan(literals); var ys = CollectionsMarshal.AsSpan(other.literals); - return AsBytes(xs).SequenceEqual(AsBytes(ys)); + if (xs.Length == ys.Length) + { + for (int i = 0; i < xs.Length; i++) + { + if (xs[i] != ys[i]) return false; + } + return true; + } + + return false; } - // convert const strings as address sequence - static ReadOnlySpan AsBytes(ReadOnlySpan literals) - { -#if NETSTANDARD2_0 - if (literals.IsEmpty) - return default; - - return Shims.CreateSpan( - ref Unsafe.As(ref Unsafe.AsRef(in literals[0])), - literals.Length * Unsafe.SizeOf()); -#else - return MemoryMarshal.CreateSpan( - ref Unsafe.As(ref MemoryMarshal.GetReference(literals)), - literals.Length * Unsafe.SizeOf()); #endif - } } }