diff --git a/Ical.Net.Benchmarks/ApplicationWorkflows.cs b/Ical.Net.Benchmarks/ApplicationWorkflows.cs index b80990d82..e3b82b049 100644 --- a/Ical.Net.Benchmarks/ApplicationWorkflows.cs +++ b/Ical.Net.Benchmarks/ApplicationWorkflows.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; namespace Ical.Net.Benchmarks { @@ -17,8 +16,8 @@ public class ApplicationWorkflows private static List GetIcalStrings() { - var currentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var topLevelIcsPath = Path.GetFullPath(Path.Combine(currentDirectory, @"..\..\..\..\", @"Ical.Net.CoreUnitTests\Calendars")); + var testProjectDirectory = Runner.FindParentFolder("Ical.Net.Tests", Directory.GetCurrentDirectory()); + var topLevelIcsPath = Path.GetFullPath(Path.Combine(testProjectDirectory, "Calendars")); return Directory.EnumerateFiles(topLevelIcsPath, "*.ics", SearchOption.AllDirectories) .Select(File.ReadAllText) .Distinct(StringComparer.OrdinalIgnoreCase) diff --git a/Ical.Net.Benchmarks/Ical.Net.Benchmarks.csproj b/Ical.Net.Benchmarks/Ical.Net.Benchmarks.csproj index 46ba15709..18d709eaf 100644 --- a/Ical.Net.Benchmarks/Ical.Net.Benchmarks.csproj +++ b/Ical.Net.Benchmarks/Ical.Net.Benchmarks.csproj @@ -3,6 +3,7 @@ net8.0;net6.0;netcoreapp3.1;net48 Exe + latest diff --git a/Ical.Net.Benchmarks/OccurencePerfTests.cs b/Ical.Net.Benchmarks/OccurencePerfTests.cs index cfbe4c9cc..c397fe3f2 100644 --- a/Ical.Net.Benchmarks/OccurencePerfTests.cs +++ b/Ical.Net.Benchmarks/OccurencePerfTests.cs @@ -41,7 +41,7 @@ public void MultipleEventsWithUntilOccurrencesEventsAsParallel() .ToList(); } - private Calendar GetFourCalendarEventsWithUntilRule() + private static Calendar GetFourCalendarEventsWithUntilRule() { const string tzid = "America/New_York"; const int limit = 4; @@ -99,13 +99,13 @@ public void MultipleEventsWithCountOccurrencesEventsAsParallel() var calendar = GetFourCalendarEventsWithCountRule(); var searchStart = calendar.Events.First().DtStart.AddYears(-1); var searchEnd = calendar.Events.Last().DtStart.AddYears(1).AddDays(10); - var eventOccurrences = calendar.Events + _ = calendar.Events .AsParallel() .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) .ToList(); } - private Calendar GetFourCalendarEventsWithCountRule() + private static Calendar GetFourCalendarEventsWithCountRule() { const string tzid = "America/New_York"; const int limit = 4; diff --git a/Ical.Net.Benchmarks/Runner.cs b/Ical.Net.Benchmarks/Runner.cs index 0a27e12b9..f557dc59a 100644 --- a/Ical.Net.Benchmarks/Runner.cs +++ b/Ical.Net.Benchmarks/Runner.cs @@ -1,4 +1,6 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; +using System.IO; namespace Ical.Net.Benchmarks { @@ -6,13 +8,192 @@ public class Runner { private static void Main(string[] args) { +#if DEBUG + BenchmarkSwitcher.FromAssembly(typeof(ApplicationWorkflows).Assembly).Run(args, new DebugInProcessConfig()); +#else + #region * ApplicationWorkflows results * + /* + // * Summary * + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3) + 13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores + .NET SDK 8.0.403 + [Host] : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + + | Method | Mean | Error | StdDev | + |---------------------------------------------------------------- |---------:|---------:|---------:| + | SingleThreaded | 18.20 ms | 0.184 ms | 0.163 ms | + | ParallelUponDeserialize | 17.56 ms | 0.350 ms | 0.739 ms | + | ParallelUponGetOccurrences | 26.39 ms | 0.294 ms | 0.275 ms | + | ParallelDeserializeSequentialGatherEventsParallelGetOccurrences | 19.39 ms | 0.373 ms | 0.399 ms | + + // * Hints * + Outliers + ApplicationWorkflows.SingleThreaded: Default -> 1 outlier was removed (18.95 ms) + ApplicationWorkflows.ParallelUponGetOccurrences: Default -> 1 outlier was detected (25.79 ms) + + // * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + 1 ms : 1 Millisecond (0.001 sec) + + // ***** BenchmarkRunner: End ***** + */ + #endregion BenchmarkRunner.Run(BenchmarkConverter.TypeToBenchmarks(typeof(ApplicationWorkflows))); + #region * OccurencePerfTests results * + /* + // * Summary * + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3) + 13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores + .NET SDK 8.0.403 + [Host] : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + + | Method | Mean | Error | StdDev | + |----------------------------------------------------------- |-----------:|---------:|---------:| + | MultipleEventsWithUntilOccurrencesSearchingByWholeCalendar | 175.2 us | 1.87 us | 1.66 us | + | MultipleEventsWithUntilOccurrences | 126.6 us | 1.43 us | 1.34 us | + | MultipleEventsWithUntilOccurrencesEventsAsParallel | NA | NA | NA | + | MultipleEventsWithCountOccurrencesSearchingByWholeCalendar | 1,672.7 us | 33.29 us | 31.14 us | + | MultipleEventsWithCountOccurrences | 1,066.6 us | 20.83 us | 30.53 us | + | MultipleEventsWithCountOccurrencesEventsAsParallel | NA | NA | NA | + + Benchmarks with issues (System.AggregateException: One or more errors occurred. (Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct. + OccurencePerfTests.MultipleEventsWithUntilOccurrencesEventsAsParallel: DefaultJob + OccurencePerfTests.MultipleEventsWithCountOccurrencesEventsAsParallel: DefaultJob + + */ + #endregion + BenchmarkRunner.Run(BenchmarkConverter.TypeToBenchmarks(typeof(OccurencePerfTests))); - //BenchmarkRunnerCore.Run(BenchmarkConverter.TypeToBenchmarks(typeof(OccurencePerfTests)), t => InProcessToolchain.Instance); - //BenchmarkRunnerCore.Run(BenchmarkConverter.TypeToBenchmarks(typeof(CalDateTimePerfTests)), t => InProcessToolchain.Instance); - //BenchmarkRunnerCore.Run(BenchmarkConverter.TypeToBenchmarks(typeof(SerializationPerfTests)), t => InProcessToolchain.Instance); - //BenchmarkRunnerCore.Run(BenchmarkConverter.TypeToBenchmarks(typeof(ThroughputTests)), t => InProcessToolchain.Instance); + #region * CalDateTimePerfTests results * + /* + // * Summary * + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3) + 13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores + .NET SDK 8.0.403 + [Host] : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + + | Method | Mean | Error | StdDev | + |----------------------------- |----------:|---------:|---------:| + | EmptyTzid | 97.35 ns | 0.700 ns | 0.620 ns | + | SpecifiedTzid | 219.31 ns | 4.406 ns | 5.729 ns | + | UtcDateTime | 193.75 ns | 3.563 ns | 3.333 ns | + | EmptyTzidToTzid | 412.57 ns | 6.857 ns | 6.414 ns | + | SpecifiedTzidToDifferentTzid | 494.44 ns | 8.299 ns | 7.763 ns | + | UtcToDifferentTzid | 437.86 ns | 3.880 ns | 3.630 ns | + + // * Hints * + Outliers + CalDateTimePerfTests.EmptyTzid: Default -> 1 outlier was removed (101.43 ns) + */ + #endregion + BenchmarkRunner.Run(BenchmarkConverter.TypeToBenchmarks(typeof(CalDateTimePerfTests))); + + #region * SerializationPerfTests results * + /* + // * Summary * + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3) + 13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores + .NET SDK 8.0.403 + [Host] : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + + + | Method | Mean | Error | StdDev | + |--------------------------- |---------:|---------:|---------:| + | Deserialize | 53.40 us | 0.456 us | 0.404 us | + | BenchmarkSerializeCalendar | 13.76 us | 0.266 us | 0.285 us | + + // * Hints * + Outliers + SerializationPerfTests.Deserialize: Default -> 1 outlier was removed (54.96 us) + + // * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + 1 us : 1 Microsecond (0.000001 sec) + + // ***** BenchmarkRunner: End ***** + */ + #endregion + BenchmarkRunner.Run(BenchmarkConverter.TypeToBenchmarks(typeof(SerializationPerfTests))); + + #region * ThroughputTests results * + /* + // * Summary * + + BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.4317/23H2/2023Update/SunValley3) + 13th Gen Intel Core i7-13700K, 1 CPU, 24 logical and 16 physical cores + .NET SDK 8.0.403 + [Host] : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.10 (8.0.1024.46610), X64 RyuJIT AVX2 + + + | Method | Mean | Error | StdDev | + |-------------------------------------- |---------:|----------:|----------:| + | DeserializeAndComputeUntilOccurrences | 2.178 ms | 0.0410 ms | 0.0403 ms | + | DeserializeAndComputeCountOccurrences | 2.116 ms | 0.0277 ms | 0.0245 ms | + + // * Hints * + Outliers + ThroughputTests.DeserializeAndComputeCountOccurrences: Default -> 1 outlier was removed (2.20 ms) + + // * Legends * + Mean : Arithmetic mean of all measurements + Error : Half of 99.9% confidence interval + StdDev : Standard deviation of all measurements + 1 ms : 1 Millisecond (0.001 sec) + + // ***** BenchmarkRunner: End ***** + */ + #endregion + BenchmarkRunner.Run(BenchmarkConverter.TypeToBenchmarks(typeof(ThroughputTests))); +#endif + } + + /// + /// Searches the parent directories of for + /// the first directory with name of . + /// + /// The name of the directory to search. + /// The path where the search starts. + /// The full path of the found folder. + /// + /// The does not exist, or the + /// was not found in the parent directories. + /// + /// The caller does not have the required permission. + /// The specified path, file name, or both exceed the system-defined maximum length. + public static string FindParentFolder(string directoryName, string startPath) + { + if (!Directory.Exists(startPath)) + { + throw new DirectoryNotFoundException($"Start path '{startPath}' not found."); + } + + var currentPath = startPath; + var rootReached = false; + + while (!rootReached && !Directory.Exists(Path.Combine(currentPath, directoryName))) + { + currentPath = Directory.GetParent(currentPath)?.FullName; + rootReached = currentPath == null; + currentPath ??= Directory.GetDirectoryRoot(startPath); + } + + var resultPath = Path.Combine(currentPath, directoryName); + if (!Directory.Exists(resultPath)) throw new DirectoryNotFoundException($"Folder '{directoryName}' not found in parent directories."); + return resultPath; } } } diff --git a/Ical.Net.Benchmarks/SerializationPerfTests.cs b/Ical.Net.Benchmarks/SerializationPerfTests.cs index 16fcc4963..62c08fb94 100644 --- a/Ical.Net.Benchmarks/SerializationPerfTests.cs +++ b/Ical.Net.Benchmarks/SerializationPerfTests.cs @@ -10,57 +10,58 @@ namespace Ical.Net.Benchmarks { public class SerializationPerfTests { - private const string SampleEvent = @"BEGIN:VCALENDAR -PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN -VERSION:2.0 -METHOD:PUBLISH -X-CALSTART:20090621T000000 -X-CALEND:20090622T000000 -X-WR-RELCALID:{0000002E-6380-7FD2-FED7-97EAE70D6611} -X-WR-CALNAME:Parse Error Calendar -BEGIN:VEVENT -ATTENDEE;CN=some.attendee@event.com;RSVP=TRUE:mailto:some.attendee@event.co - m -ATTENDEE;CN=event@calendardemo.net;RSVP=TRUE:mailto:event@calendardemo.net -ATTENDEE;CN=""4th Floor Meeting Room"";CUTYPE=RESOURCE;ROLE=NON-PARTICIPANT;R - SVP=TRUE:mailto:4th.floor.meeting.room@somewhere.com -CLASS:PUBLIC -CREATED:20090621T201527Z -DESCRIPTION:\n -DTEND;VALUE=DATE:20090622 -DTSTAMP:20090621T201612Z -DTSTART;VALUE=DATE:20090621 -LAST-MODIFIED:20090621T201618Z -LOCATION:The Exceptionally Long Named Meeting Room Whose Name Wraps Over Se - veral Lines When Exported From Leading Calendar and Office Software App - lication Microsoft Office 2007 -ORGANIZER;CN=""Event Organizer"":mailto:some.attendee@somewhere.com -PRIORITY:5 -SEQUENCE:0 -SUMMARY;LANGUAGE=en-gb:Example Calendar Export that Blows Up DDay.iCal -TRANSP:TRANSPARENT -UID:040000008200E00074C5B7101A82E00800000000900AD080B5F2C901000000000000000 - 010000000B29680BF9E5DC246B5EDDE228038E71F -X-ALT-DESC;FMTTYPE=text/html:\n\n\n\n\n\n\n\n\n

\n\n\n -X-MICROSOFT-CDO-BUSYSTATUS:FREE -X-MICROSOFT-CDO-IMPORTANCE:1 -X-MICROSOFT-DISALLOW-COUNTER:FALSE -X-MS-OLK-ALLOWEXTERNCHECK:TRUE -X-MS-OLK-APPTSEQTIME:20090621T201612Z -X-MS-OLK-AUTOFILLLOCATION:TRUE -X-MS-OLK-CONFTYPE:0 -BEGIN:VALARM -TRIGGER:-PT1080M -ACTION:DISPLAY -DESCRIPTION:Reminder -END:VALARM -END:VEVENT -END:VCALENDAR -"; + private const string SampleEvent = """ + BEGIN:VCALENDAR + PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN + VERSION:2.0 + METHOD:PUBLISH + X-CALSTART:20090621T000000 + X-CALEND:20090622T000000 + X-WR-RELCALID:{0000002E-6380-7FD2-FED7-97EAE70D6611} + X-WR-CALNAME:Parse Error Calendar + BEGIN:VEVENT + ATTENDEE;CN=some.attendee@event.com;RSVP=TRUE:mailto:some.attendee@event.co + m + ATTENDEE;CN=event@calendardemo.net;RSVP=TRUE:mailto:event@calendardemo.net + ATTENDEE;CN="4th Floor Meeting Room";CUTYPE=RESOURCE;ROLE=NON-PARTICIPANT;R + SVP=TRUE:mailto:4th.floor.meeting.room@somewhere.com + CLASS:PUBLIC + CREATED:20090621T201527Z + DESCRIPTION:\n + DTEND;VALUE=DATE:20090622 + DTSTAMP:20090621T201612Z + DTSTART;VALUE=DATE:20090621 + LAST-MODIFIED:20090621T201618Z + LOCATION:The Exceptionally Long Named Meeting Room Whose Name Wraps Over Se + veral Lines When Exported From Leading Calendar and Office Software App + lication Microsoft Office 2007 + ORGANIZER;CN="Event Organizer":mailto:some.attendee@somewhere.com + PRIORITY:5 + SEQUENCE:0 + SUMMARY;LANGUAGE=en-gb:Example Calendar Export that Blows Up DDay.iCal + TRANSP:TRANSPARENT + UID:040000008200E00074C5B7101A82E00800000000900AD080B5F2C901000000000000000 + 010000000B29680BF9E5DC246B5EDDE228038E71F + X-ALT-DESC;FMTTYPE=text/html:\n\n\n\n\n\n\n\n\n

\n\n\n + X-MICROSOFT-CDO-BUSYSTATUS:FREE + X-MICROSOFT-CDO-IMPORTANCE:1 + X-MICROSOFT-DISALLOW-COUNTER:FALSE + X-MS-OLK-ALLOWEXTERNCHECK:TRUE + X-MS-OLK-APPTSEQTIME:20090621T201612Z + X-MS-OLK-AUTOFILLLOCATION:TRUE + X-MS-OLK-CONFTYPE:0 + BEGIN:VALARM + TRIGGER:-PT1080M + ACTION:DISPLAY + DESCRIPTION:Reminder + END:VALARM + END:VEVENT + END:VCALENDAR + """; [Benchmark] public void Deserialize() => Calendar.Load(SampleEvent).Events.First();