using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using Glass.Editing; namespace Glass.PerformanceTests { /// /// Times an edit followed by undoing it, at sizes around the point where undo starts keeping pages of the text in /// place of a copy of it, for each point worth considering. A threshold no edit reaches is the control, since it /// keeps every edit as a copy. /// public static class UndoBenchmark { public const string Argument = "--undo"; public const string Usage = "Usage: Glass.PerformanceTests --undo [text sizes in chars, comma separated]"; public static readonly int[] DefaultSizes = { 10000000 }; public static readonly int[] EditSizes = { 1000, 3000, 4096, 6000, 8192, 12000, 16384, 65536, 1000000 }; public const int ChunkSize = 1000000; public const int MostCharsTheControlCopies = 200000000; public sealed class Variant { public string Name; public int Threshold; } public static readonly Variant[] Variants = { new Variant { Name = "copy (control)", Threshold = int.MaxValue }, new Variant { Name = "1 page", Threshold = PagedText.DefaultPageSize }, new Variant { Name = "2 pages", Threshold = PagedText.DefaultPageSize * 2 }, new Variant { Name = "4 pages", Threshold = PagedText.DefaultPageSize * 4 }, }; private static EditBuffer Loaded(int size) { var returnMe = new EditBuffer(); string chunk = TypingBenchmark.TextOfAbout(ChunkSize); while (returnMe.Length < size) { returnMe.Replace(returnMe.Length, 0, chunk); } returnMe.ClearHistory(); return returnMe; } private static void MeasureOnce(string name, Action action) { GC.Collect(); long bytesBefore = GC.GetAllocatedBytesForCurrentThread(); long start = Stopwatch.GetTimestamp(); action(); double milliseconds = (Stopwatch.GetTimestamp() - start) * 1000.0 / Stopwatch.Frequency; Console.WriteLine(string.Format(CultureInfo.InvariantCulture, " {0,-34} {1,10:F2} ms {2,9:F1} MB allocated", name, milliseconds, (GC.GetAllocatedBytesForCurrentThread() - bytesBefore) / 1048576.0)); } public static int Run(string[] args) { var sizes = new List(DefaultSizes); if (args.Length > 2) { Console.Error.WriteLine(Usage); return 1; } if (args.Length == 2) { sizes.Clear(); foreach (string part in args[1].Split(',')) { if (!int.TryParse(part, NumberStyles.None, CultureInfo.InvariantCulture, out int size) || size < 1) { Console.Error.WriteLine(Usage); return 1; } sizes.Add(size); } } string bitness = Environment.Is64BitProcess ? "x64" : "x86"; Console.WriteLine($"Edit then undo, {bitness}, {PagedText.DefaultPageSize}-char pages."); foreach (int size in sizes) { Console.WriteLine($"--- {size:N0} chars"); EditBuffer buffer = Loaded(size); int middle = buffer.Length / 2; foreach (Variant variant in Variants) { buffer.PagedStepThreshold = variant.Threshold; Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure( "typing, " + variant.Name, () => buffer.Replace(middle, 0, "x")))); buffer.ClearHistory(); } foreach (int editSize in EditSizes) { if (editSize > size / 4) { continue; } string pasted = TypingBenchmark.TextOfAbout(editSize); foreach (Variant variant in Variants) { buffer.PagedStepThreshold = variant.Threshold; Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure( $"{editSize} del+undo, {variant.Name}", () => { buffer.Replace(middle, editSize, ""); buffer.Undo(); }))); Console.WriteLine(TypingBenchmark.Format(TypingBenchmark.Measure( $"{editSize} paste+undo, {variant.Name}", () => { buffer.Replace(middle, 0, pasted); buffer.Undo(); }))); buffer.ClearHistory(); } } foreach (Variant variant in Variants) { // The control copies the half it deletes, a gigabyte at the largest sizes. if (variant.Threshold == int.MaxValue && size > MostCharsTheControlCopies) { continue; } buffer.PagedStepThreshold = variant.Threshold; int half = buffer.Length / 2; int quarter = buffer.Length / 4; MeasureOnce("delete half, " + variant.Name, () => buffer.Replace(quarter, half, "")); MeasureOnce(" undo, " + variant.Name, () => buffer.Undo()); MeasureOnce(" redo, " + variant.Name, () => buffer.Redo()); MeasureOnce(" undo, " + variant.Name, () => buffer.Undo()); buffer.ClearHistory(); } } return 0; } } }