| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| namespace Apache.Ignite.Benchmarks |
| { |
| using System; |
| using System.Collections.Generic; |
| using System.Diagnostics; |
| using System.Linq; |
| using System.Text; |
| using System.Threading; |
| using Apache.Ignite.Benchmarks.Result; |
| |
| /// <summary> |
| /// Benchmark base class. |
| /// </summary> |
| internal abstract class BenchmarkBase |
| { |
| /** Result writer type: console. */ |
| private const string ResultWriterConsole = "console"; |
| |
| /** Result writer type: file. */ |
| private const string ResultWriterFile = "file"; |
| |
| /** Default duration. */ |
| private const int DefaultDuration = 60; |
| |
| /** Default maximum errors count. */ |
| private const int DefaultMaxErrors = 100; |
| |
| /** Default percentile result buckets count. */ |
| private const int DEfaultResultBucketCount = 10000; |
| |
| /** Default percentile result bucket interval. */ |
| private const int DefaultResultBucketInterval = 100; |
| |
| /** Default batch size. */ |
| private const int DefaultBatchSize = 1; |
| |
| /** Default result wrier. */ |
| private const string DefaultResultWriter = ResultWriterConsole; |
| |
| /** Start flag. */ |
| private volatile bool _start; |
| |
| /** Stop flag. */ |
| private volatile bool _stop; |
| |
| /** Warmup flag. */ |
| private volatile bool _warmup = true; |
| |
| /** Ready threads. */ |
| private int _readyThreads; |
| |
| /** Finished threads. */ |
| private volatile int _finishedThreads; |
| |
| /** Descriptors. */ |
| private volatile ICollection<BenchmarkOperationDescriptor> _descs; |
| |
| /** Benchmark tasks. */ |
| private volatile ICollection<BenchmarkTask> _tasks; |
| |
| /** Percentile results. */ |
| private volatile IDictionary<string, long[]> _percentiles; |
| |
| /** Currently completed operations. */ |
| private long _curOps; |
| |
| /** Error count. */ |
| private long _errorCount; |
| |
| /** Watches to count total execution time. */ |
| private readonly Stopwatch _totalWatch = new Stopwatch(); |
| |
| /** Warmup barrier. */ |
| private Barrier _barrier; |
| |
| /** Benchmark result writer. */ |
| private IBenchmarkResultWriter _writer; |
| |
| /// <summary> |
| /// Default constructor. |
| /// </summary> |
| protected BenchmarkBase() |
| { |
| Duration = DefaultDuration; |
| MaxErrors = DefaultMaxErrors; |
| ResultBucketCount = DEfaultResultBucketCount; |
| ResultBucketInterval = DefaultResultBucketInterval; |
| BatchSize = DefaultBatchSize; |
| ResultWriter = DefaultResultWriter; |
| } |
| |
| /// <summary> |
| /// Run the benchmark. |
| /// </summary> |
| public void Run() |
| { |
| PrintDebug("Started benchmark: " + this); |
| |
| ValidateArguments(); |
| |
| if (ResultWriter.ToLower().Equals(ResultWriterConsole)) |
| _writer = new BenchmarkConsoleResultWriter(); |
| else |
| _writer = new BenchmarkFileResultWriter(); |
| |
| OnStarted(); |
| |
| PrintDebug("Benchmark setup finished."); |
| |
| try |
| { |
| _descs = new List<BenchmarkOperationDescriptor>(); |
| |
| GetDescriptors(_descs); |
| |
| if (_descs.Count == 0) |
| throw new Exception("No tasks provided for benchmark."); |
| |
| // Initialize writer. |
| var opNames = new List<string>(_descs.Select(desc => desc.Name)); |
| |
| PrintDebug(() => |
| { |
| var sb = new StringBuilder("Operations: "); |
| |
| foreach (var opName in opNames) |
| sb.Append(opName).Append(" "); |
| |
| return sb.ToString(); |
| }); |
| |
| _writer.Initialize(this, opNames); |
| |
| PrintDebug("Initialized result writer."); |
| |
| // Start worker threads. |
| _tasks = new List<BenchmarkTask>(Threads); |
| |
| PrintDebug("Starting worker threads: " + Threads); |
| |
| for (var i = 0; i < Threads; i++) |
| { |
| var task = new BenchmarkTask(this, _descs); |
| |
| _tasks.Add(task); |
| |
| new Thread(task.Run).Start(); |
| } |
| |
| PrintDebug("Waiting worker threads to start: " + Threads); |
| |
| // Await for all threads to start in spin loop. |
| while (Thread.VolatileRead(ref _readyThreads) < Threads) |
| Thread.Sleep(10); |
| |
| PrintDebug("Worker threads started: " + Threads); |
| |
| // Start throughput writer thread. |
| var writerThread = new Thread(new ThroughputTask(this).Run) {IsBackground = true}; |
| |
| writerThread.Start(); |
| |
| PrintDebug("Started throughput writer thread."); |
| |
| // Start warmup thread if needed. |
| if (Warmup > 0) |
| { |
| var thread = new Thread(new WarmupTask(this, Warmup).Run) {IsBackground = true}; |
| |
| thread.Start(); |
| |
| PrintDebug("Started warmup timeout thread: " + Warmup); |
| } |
| else |
| _warmup = false; |
| |
| _barrier = new Barrier(Threads, b => |
| { |
| Console.WriteLine("Warmup finished."); |
| |
| _totalWatch.Start(); |
| }); |
| |
| // Start timeout thread if needed. |
| if (Duration > 0) |
| { |
| if (Operations > 0) |
| PrintDebug("Duration argument is ignored because operations number is set: " + |
| Operations); |
| else |
| { |
| var thread = new Thread(new TimeoutTask(this, Warmup + Duration).Run) {IsBackground = true}; |
| |
| thread.Start(); |
| |
| PrintDebug("Started duration timeout thread: " + Duration); |
| } |
| } |
| |
| // Let workers start execution. |
| _start = true; |
| |
| // Await workers completion. |
| PrintDebug("Awaiting worker threads completion."); |
| |
| Monitor.Enter(this); |
| |
| try |
| { |
| while (_finishedThreads < Threads) |
| Monitor.Wait(this); |
| } |
| finally |
| { |
| Monitor.Exit(this); |
| } |
| |
| PrintDebug("Worker threads completed."); |
| } |
| finally |
| { |
| OnFinished(); |
| |
| _totalWatch.Stop(); |
| |
| PrintDebug("Tear down invoked."); |
| |
| if (PrintThroughputInfo()) |
| { |
| var avgThroughput = _totalWatch.ElapsedMilliseconds == 0 |
| ? 0 |
| : _curOps*1000/_totalWatch.ElapsedMilliseconds; |
| |
| var avgLatency = _curOps == 0 |
| ? 0 |
| : (double) _totalWatch.ElapsedMilliseconds*Threads/_curOps; |
| |
| Console.WriteLine("Finishing benchmark [name=" + GetType().Name + |
| ", time=" + _totalWatch.ElapsedMilliseconds + |
| "ms, ops=" + _curOps + |
| ", threads=" + Threads + |
| ", avgThroughput=" + avgThroughput + |
| ", avgLatency=" + string.Format("{0:0.000}ms", avgLatency) + ']'); |
| } |
| else |
| { |
| Console.WriteLine("Finishing benchmark [name=" + GetType().Name + |
| ", time=" + _totalWatch.ElapsedMilliseconds + |
| "ms, ops=" + Operations + |
| ", threads=" + Threads + ']'); |
| } |
| } |
| |
| _percentiles = new Dictionary<string, long[]>(_descs.Count); |
| |
| foreach (var desc in _descs) |
| _percentiles[desc.Name] = new long[ResultBucketCount]; |
| |
| foreach (var task in _tasks) |
| task.CollectPercentiles(_percentiles); |
| |
| foreach (var percentile in _percentiles) |
| _writer.WritePercentiles(percentile.Key, ResultBucketInterval, percentile.Value); |
| |
| _writer.Commit(); |
| |
| PrintDebug("Results committed to output writer."); |
| } |
| |
| /// <summary> |
| /// Consumes passed argument. |
| /// </summary> |
| /// <param name="name">Argument name.</param> |
| /// <param name="val">Value.</param> |
| /// <returns>True if argument was consumed.</returns> |
| public void Configure(string name, string val) |
| { |
| var prop = BenchmarkUtils.GetProperty(this, name); |
| |
| if (prop != null) |
| BenchmarkUtils.SetProperty(this, prop, val); |
| } |
| |
| /// <summary> |
| /// Start callback. |
| /// </summary> |
| protected virtual void OnStarted() |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Warmup finished callback. Executed by each worker thread once. |
| /// </summary> |
| protected virtual void OnWarmupFinished() |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Batch execution started callback. |
| /// </summary> |
| /// <param name="state">State.</param> |
| protected virtual void OnBatchStarted(BenchmarkState state) |
| { |
| // No-op. |
| } |
| |
| /// <summary> |
| /// Batch execution finished callback. |
| /// </summary> |
| /// <param name="state">State.</param> |
| /// <param name="duration">Duration.</param> |
| /// <returns>True if this result must be counted.</returns> |
| protected virtual bool OnBatchFinished(BenchmarkState state, long duration) |
| { |
| return true; |
| } |
| |
| /// <summary> |
| /// Benchmarh finished callback. |
| /// </summary> |
| protected virtual void OnFinished() |
| { |
| // No-op. |
| } |
| |
| /// <returns>Flag indicating whether benchmark should print throughput information.</returns> |
| protected virtual bool PrintThroughputInfo() |
| { |
| return true; |
| } |
| |
| /// <summary> |
| /// Internal arguments validation routine. |
| /// </summary> |
| /// <returns>True if base class must validate common arguments, false otherwise.</returns> |
| protected virtual bool ValidateArgumentsEx() |
| { |
| return true; |
| } |
| |
| /// <summary> |
| /// Print debug to console. |
| /// </summary> |
| /// <param name="msg">Message</param> |
| protected void PrintDebug(string msg) |
| { |
| if (Debug) |
| Console.WriteLine("[DEBUG] " + Thread.CurrentThread.ManagedThreadId + ": " + msg); |
| } |
| |
| /// <summary> |
| /// Print debug to console. |
| /// </summary> |
| /// <param name="msgFunc">Message delegate.</param> |
| protected void PrintDebug(Func<string> msgFunc) |
| { |
| if (Debug) |
| PrintDebug(msgFunc.Invoke()); |
| } |
| |
| /// <summary> |
| /// Add operation descriptors. |
| /// </summary> |
| /// <param name="descs">Collection where operation descriptors must be added.</param> |
| protected abstract void GetDescriptors(ICollection<BenchmarkOperationDescriptor> descs); |
| |
| /// <summary> |
| /// Invoked when single thread is ready to actual execution. |
| /// </summary> |
| private void OnThreadReady() |
| { |
| Interlocked.Increment(ref _readyThreads); |
| } |
| |
| /// <summary> |
| /// Invoked when single thread finished execution. |
| /// </summary> |
| private void OnThreadFinished() |
| { |
| Monitor.Enter(this); |
| |
| try |
| { |
| // ReSharper disable once NonAtomicCompoundOperator |
| _finishedThreads++; |
| |
| Monitor.PulseAll(this); |
| } |
| finally |
| { |
| Monitor.Exit(this); |
| } |
| |
| PrintDebug("Worker thread finished."); |
| } |
| |
| /// <summary> |
| /// Validate arguments. |
| /// </summary> |
| private void ValidateArguments() |
| { |
| if (ValidateArgumentsEx()) |
| { |
| if (Threads <= 0) |
| throw new Exception("Threads must be positive: " + Threads); |
| |
| if (Warmup < 0) |
| throw new Exception("Warmup cannot be negative: " + Warmup); |
| |
| if (Duration < 0) |
| throw new Exception("Duration cannot be negative: " + Duration); |
| |
| if (BatchSize <= 0) |
| throw new Exception("BatchSize must be positive: " + BatchSize); |
| |
| if (MaxErrors < 0) |
| throw new Exception("MaxErrors cannot be negative: " + MaxErrors); |
| |
| if (ResultWriter == null || !ResultWriter.ToLower().Equals(ResultWriterConsole) |
| && !ResultWriter.ToLower().Equals(ResultWriterFile)) |
| throw new Exception("Invalid ResultWriter: " + ResultWriter); |
| |
| if (ResultWriter.ToLower().Equals(ResultWriterFile) && ResultFolder == null) |
| throw new Exception("ResultFolder must be set for file result writer."); |
| |
| if (ResultBucketCount <= 0) |
| throw new Exception("ResultBucketCount must be positive: " + ResultBucketCount); |
| |
| if (ResultBucketInterval <= 0) |
| throw new Exception("ResultBucketInterval must be positive: " + ResultBucketInterval); |
| } |
| } |
| |
| /// <summary> |
| /// Get current throughput across all currenlty running threads. |
| /// </summary> |
| /// <returns>Current throughput.</returns> |
| private IDictionary<string, Tuple<long, long>> GetCurrentThroughput() |
| { |
| var total = new Dictionary<string, Tuple<long, long>>(_descs.Count); |
| |
| foreach (var desc in _descs) |
| total[desc.Name] = new Tuple<long, long>(0, 0); |
| |
| foreach (var task in _tasks) |
| task.CollectThroughput(total); |
| |
| return total; |
| } |
| |
| /** <inheritDoc /> */ |
| public override string ToString() |
| { |
| var sb = new StringBuilder(GetType().Name).Append('['); |
| |
| var first = true; |
| |
| var props = BenchmarkUtils.GetProperties(this); |
| |
| foreach (var prop in props) |
| { |
| if (first) |
| first = false; |
| else |
| sb.Append(", "); |
| |
| sb.Append(prop.Name).Append('=').Append(prop.GetValue(this, null)); |
| } |
| |
| sb.Append(']'); |
| |
| return sb.ToString(); |
| } |
| |
| /* COMMON PUBLIC PROPERTIES. */ |
| |
| /// <summary> |
| /// Amount of worker threads. |
| /// </summary> |
| public int Threads { get; set; } |
| |
| /// <summary> |
| /// Warmup duration in secnods. |
| /// </summary> |
| public int Warmup { get; set; } |
| |
| /// <summary> |
| /// Duration in seconds. |
| /// </summary> |
| public int Duration { get; set; } |
| |
| /// <summary> |
| /// Maximum amount of operations to perform. |
| /// </summary> |
| public int Operations { get; set; } |
| |
| /// <summary> |
| /// Single measurement batch size. |
| /// </summary> |
| public int BatchSize { get; set; } |
| |
| /// <summary> |
| /// Maximum amount of errors before benchmark exits. |
| /// </summary> |
| public int MaxErrors { get; set; } |
| |
| /// <summary> |
| /// Debug flag. |
| /// </summary> |
| public bool Debug { get; set; } |
| |
| /// <summary> |
| /// Result writer type. |
| /// </summary> |
| public string ResultWriter { get; set; } |
| |
| /// <summary> |
| /// Result output folder. |
| /// </summary> |
| public string ResultFolder { get; set; } |
| |
| /// <summary> |
| /// Percentile result buckets count. |
| /// </summary> |
| public int ResultBucketCount { get; set; } |
| |
| /// <summary> |
| /// Percnetile result bucket interval in microseconds. |
| /// </summary> |
| public long ResultBucketInterval { get; set; } |
| |
| /* INNER CLASSES. */ |
| |
| /// <summary> |
| /// Benchmark worker task. |
| /// </summary> |
| private class BenchmarkTask |
| { |
| /** Benchmark. */ |
| private readonly BenchmarkBase _benchmark; |
| |
| /** Descriptors. */ |
| private readonly BenchmarkOperationDescriptor[] _descs; |
| |
| /** Results. */ |
| private readonly IDictionary<string, Result> _results; |
| |
| /** Stop watch. */ |
| private readonly Stopwatch _watch = new Stopwatch(); |
| |
| /** Benchmark state. */ |
| private readonly BenchmarkState _state; |
| |
| /// <summary> |
| /// Constructor. |
| /// </summary> |
| /// <param name="benchmark">Benchmark.</param> |
| /// <param name="descList">Descriptor list.</param> |
| public BenchmarkTask(BenchmarkBase benchmark, |
| ICollection<BenchmarkOperationDescriptor> descList) |
| { |
| _benchmark = benchmark; |
| |
| _state = new BenchmarkState(); |
| |
| _results = new Dictionary<string, Result>(descList.Count); |
| |
| var totalWeight = 0; |
| |
| var ticksPerSlot = benchmark.ResultBucketInterval*Stopwatch.Frequency/1000000; |
| |
| if (ticksPerSlot == 0) |
| throw new Exception("Too low bucket interval: " + benchmark.ResultBucketInterval); |
| |
| foreach (var desc in descList) |
| { |
| _results[desc.Name] = new Result(benchmark.ResultBucketCount, ticksPerSlot); |
| |
| totalWeight += desc.Weight; |
| } |
| |
| _descs = new BenchmarkOperationDescriptor[totalWeight]; |
| |
| var idx = 0; |
| |
| foreach (var desc in descList) |
| { |
| for (var i = 0; i < desc.Weight; i++) |
| _descs[idx++] = desc; |
| } |
| } |
| |
| /// <summary> |
| /// Task routine. |
| /// </summary> |
| public void Run() |
| { |
| try |
| { |
| _benchmark.OnThreadReady(); |
| |
| _benchmark.PrintDebug("Worker thread ready."); |
| |
| while (!_benchmark._start) |
| Thread.Sleep(10); |
| |
| _benchmark.PrintDebug("Worker thread started benchmark execution."); |
| |
| var warmupIteration = true; |
| |
| long maxDur = 0; |
| |
| long maxOps = _benchmark.Operations; |
| |
| while (!_benchmark._stop) |
| { |
| if (warmupIteration && !_benchmark._warmup) |
| { |
| warmupIteration = false; |
| |
| _benchmark.OnWarmupFinished(); |
| |
| _state.StopWarmup(); |
| |
| _benchmark._barrier.SignalAndWait(); |
| } |
| |
| if (!warmupIteration) |
| { |
| if (maxOps > 0 && Interlocked.Read(ref _benchmark._curOps) > maxOps) |
| break; |
| } |
| |
| var desc = _descs.Length == 1 |
| ? _descs[0] |
| : _descs[BenchmarkUtils.GetRandomInt(_descs.Length)]; |
| |
| var res = true; |
| |
| _benchmark.OnBatchStarted(_state); |
| |
| _watch.Start(); |
| |
| try |
| { |
| for (var i = 0; i < _benchmark.BatchSize; i++) |
| { |
| desc.Operation(_state); |
| |
| _state.IncrementCounter(); |
| } |
| |
| if (!warmupIteration) |
| Interlocked.Add(ref _benchmark._curOps, _benchmark.BatchSize); |
| } |
| catch (Exception e) |
| { |
| Console.WriteLine("Exception: " + e); |
| |
| res = false; |
| |
| if (_benchmark.MaxErrors > 0 && |
| Interlocked.Increment(ref _benchmark._errorCount) > _benchmark.MaxErrors) |
| { |
| lock (_benchmark) |
| { |
| Console.WriteLine("Benchmark is stopped due to too much errors: " + |
| _benchmark.MaxErrors); |
| |
| Environment.Exit(-1); |
| } |
| } |
| } |
| finally |
| { |
| _watch.Stop(); |
| |
| var curDur = _watch.ElapsedTicks; |
| |
| if (res) |
| res = _benchmark.OnBatchFinished(_state, curDur); |
| |
| _state.Reset(); |
| |
| if (curDur > maxDur) |
| { |
| maxDur = curDur; |
| |
| _benchmark.PrintDebug("The longest execution [warmup=" + warmupIteration + |
| ", dur(nsec)=" + maxDur*1000000000/Stopwatch.Frequency + ']'); |
| } |
| |
| _watch.Reset(); |
| |
| if (!warmupIteration && res) |
| _results[desc.Name].Add(curDur); |
| } |
| } |
| } |
| finally |
| { |
| _benchmark.PrintDebug("Worker thread stopped."); |
| |
| _benchmark.OnThreadFinished(); |
| } |
| } |
| |
| /// <summary> |
| /// Collect throughput for the current task. |
| /// </summary> |
| /// <param name="total">Total result.</param> |
| public void CollectThroughput(IDictionary<string, Tuple<long, long>> total) |
| { |
| foreach (var result in _results) |
| { |
| var old = total[result.Key]; |
| |
| total[result.Key] = new Tuple<long, long>(old.Item1 + result.Value.Duration, |
| old.Item2 + result.Value.OpCount); |
| } |
| } |
| |
| /// <summary> |
| /// Collect percnetiles for the current task. |
| /// </summary> |
| /// <param name="total"></param> |
| public void CollectPercentiles(IDictionary<string, long[]> total) |
| { |
| foreach (var result in _results) |
| { |
| var arr = total[result.Key]; |
| |
| for (var i = 0; i < arr.Length; i++) |
| arr[i] += result.Value.Slots[i]; |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Timeout task to stop execution. |
| /// </summary> |
| private class TimeoutTask |
| { |
| /** Benchmark. */ |
| private readonly BenchmarkBase _benchmark; |
| |
| /** Duration. */ |
| private readonly long _dur; |
| |
| /// <summary> |
| /// Constructor. |
| /// </summary> |
| /// <param name="benchmark">Benchmark.</param> |
| /// <param name="dur">Duration.</param> |
| public TimeoutTask(BenchmarkBase benchmark, long dur) |
| { |
| _benchmark = benchmark; |
| _dur = dur; |
| } |
| |
| /// <summary> |
| /// Task routine. |
| /// </summary> |
| public void Run() |
| { |
| try |
| { |
| Thread.Sleep(TimeSpan.FromSeconds(_dur)); |
| } |
| finally |
| { |
| _benchmark._stop = true; |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Warmup task to clear warmup flag. |
| /// </summary> |
| private class WarmupTask |
| { |
| /** Benchmark. */ |
| private readonly BenchmarkBase _benchmark; |
| |
| /** Duration. */ |
| private readonly long _dur; |
| |
| /// <summary> |
| /// Constructor. |
| /// </summary> |
| /// <param name="benchmark">Benchmark.</param> |
| /// <param name="dur">Duration.</param> |
| public WarmupTask(BenchmarkBase benchmark, long dur) |
| { |
| _benchmark = benchmark; |
| _dur = dur; |
| } |
| |
| /// <summary> |
| /// Task routine. |
| /// </summary> |
| public void Run() |
| { |
| try |
| { |
| Thread.Sleep(TimeSpan.FromSeconds(_dur)); |
| } |
| finally |
| { |
| _benchmark._warmup = false; |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Throughput write task. |
| /// </summary> |
| private class ThroughputTask |
| { |
| /** Benchmark. */ |
| private readonly BenchmarkBase _benchmark; |
| |
| /** Last recorded result. */ |
| private IDictionary<string, Tuple<long, long>> _lastResults; |
| |
| /// <summary> |
| /// Constructor. |
| /// </summary> |
| /// <param name="benchmark">Benchmark.</param> |
| public ThroughputTask(BenchmarkBase benchmark) |
| { |
| _benchmark = benchmark; |
| } |
| |
| public void Run() |
| { |
| while (!_benchmark._stop) |
| { |
| Thread.Sleep(1000); |
| |
| if (_benchmark._start && !_benchmark._warmup) |
| { |
| var results = _benchmark.GetCurrentThroughput(); |
| |
| if (_benchmark._finishedThreads > 0) |
| return; // Threads are stopping, do not collect any more. |
| |
| foreach (var pair in results) |
| { |
| Tuple<long, long> old; |
| |
| if (_lastResults != null && _lastResults.TryGetValue(pair.Key, out old)) |
| _benchmark._writer.WriteThroughput(pair.Key, pair.Value.Item1 - old.Item1, |
| pair.Value.Item2 - old.Item2); |
| else |
| _benchmark._writer.WriteThroughput(pair.Key, pair.Value.Item1, |
| pair.Value.Item2); |
| } |
| |
| _lastResults = results; |
| } |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Benchmark result. Specific for each operation. |
| /// </summary> |
| private class Result |
| { |
| /** Slots. */ |
| public readonly long[] Slots; |
| |
| /** Slot duration in ticks. */ |
| private readonly long _slotDuration; |
| |
| /** Total operations count. */ |
| public long OpCount; |
| |
| /** Total duration. */ |
| public long Duration; |
| |
| /// <summary> |
| /// Constructor. |
| /// </summary> |
| /// <param name="slotCnt">Slot count.</param> |
| /// <param name="slotDuration">Slot duration in ticks.</param> |
| public Result(long slotCnt, long slotDuration) |
| { |
| Slots = new long[slotCnt]; |
| |
| _slotDuration = slotDuration; |
| } |
| |
| /// <summary> |
| /// Add result. |
| /// </summary> |
| /// <param name="curDur">Current duration in ticks.</param> |
| public void Add(long curDur) |
| { |
| var idx = (int) (curDur/_slotDuration); |
| |
| if (idx >= Slots.Length) |
| idx = Slots.Length - 1; |
| |
| Slots[idx] += 1; |
| |
| OpCount++; |
| Duration += curDur; |
| } |
| } |
| } |
| } |