Class: Bullshit::Clock

Inherits:
Object
  • Object
show all
Defined in:
lib/bullshit.rb

Overview

A Clock instance is used to take measurements while benchmarking.

Constant Summary collapse

TIMES =
[ :real, :total, :user, :system ]
ALL_COLUMNS =
[ :scatter ] + TIMES + [ :repeat ]
TIMES_MAX =
TIMES.map { |t| t.to_s.size }.max

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bc_method) ⇒ Clock

Returns a Clock instance for CaseMethod instance bc_method.


242
243
244
245
246
247
# File 'lib/bullshit.rb', line 242

def initialize(bc_method)
  @bc_method = bc_method
  @times = Hash.new { |h, k| h[k] = [] }
  @repeat = 0
  @scatter = 0
end

Instance Attribute Details

#bc_methodObject (readonly)

Returns the benchmark method for this Clock instance.


324
325
326
# File 'lib/bullshit.rb', line 324

def bc_method
  @bc_method
end

#repeatObject

Number of repetitions this clock has measured.


327
328
329
# File 'lib/bullshit.rb', line 327

def repeat
  @repeat
end

#slopesObject (readonly)

Return all the slopes of linear regressions computed during data truncation phase.


334
335
336
# File 'lib/bullshit.rb', line 334

def slopes
  @slopes
end

#timeObject (readonly)

Last time object used for real time measurement.


330
331
332
# File 'lib/bullshit.rb', line 330

def time
  @time
end

Class Method Details

.repeat(bc_method) ⇒ Object

Use a Clock instance to measure the time necessary to do bc_method.case.iterations repetitions of bc_method.


251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/bullshit.rb', line 251

def self.repeat(bc_method)
  clock = new(bc_method)
  bs = clock.case.batch_size.abs
  bs = 1 if !bs or bs < 0
  clock.case.iterations.times do
    bc_method.before_run
    $DEBUG and warn "Calling #{bc_method.name}."
    clock.inc_scatter
    clock.measure do
      bs.times { yield }
    end
    bc_method.after_run
  end
  clock
end

.scale_range(bc_method) ⇒ Object

Iterate over the range of the RangeCase instance of this bc_method and take measurements (including scattering).


294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/bullshit.rb', line 294

def self.scale_range(bc_method)
  clock = new(bc_method)
  my_case = clock.case
  bs = my_case.batch_size.abs
  bs = 1 if !bs or bs < 0
  for a in my_case.range
    begin
      my_case.args = (a.dup rescue a).freeze
      clock.inc_scatter
      my_case.scatter.times do
        bc_method.before_run
        $DEBUG and warn "Calling #{bc_method.name}."
        clock.measure do
          bs.times { yield }
        end
        bc_method.after_run
      end
    ensure
      my_case.args = nil
    end
  end
  clock
end

.time(bc_method) ⇒ Object

Use a Clock instance to measure how many repetitions of bc_method can be done in bc_method.case.duration seconds (a float value). If the bc_method.case.batch_size is >1 duration is multiplied by batch_size because the measured times are averaged by batch_size.


271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/bullshit.rb', line 271

def self.time(bc_method)
  clock = new(bc_method)
  duration = clock.case.duration.abs
  if bs = clock.case.batch_size and bs > 1
    duration *= bs
  end
  until_at = Time.now + duration
  bs = clock.case.batch_size.abs
  bs = 1 if !bs or bs < 0
  begin 
    bc_method.before_run
    $DEBUG and warn "Calling #{bc_method.name}."
    clock.inc_scatter
    clock.measure do
      bs.times { yield }
    end
    bc_method.after_run
  end until clock.time > until_at
  clock
end

.timesObject

The times which should be displayed in the output.


510
511
512
# File 'lib/bullshit.rb', line 510

def self.times
  TIMES.map { |t| t.to_s }
end

.to_aObject

Return column names in relation to Clock#to_a method.


366
367
368
# File 'lib/bullshit.rb', line 366

def self.to_a
  %w[ #scatter ] + TIMES + %w[ repeat ]
end

Instance Method Details

#<<(times) ⇒ Object

Add the array times to this clock's time measurements. times consists of the time measurements in float values in order of TIMES.


338
339
340
341
342
343
344
# File 'lib/bullshit.rb', line 338

def <<(times)
  r = times.shift
  @repeat += 1 if @times[:repeat].last != r
  @times[:repeat] << r
  TIMES.zip(times) { |t, time| @times[t] << time.to_f }
  self
end

#analysisObject

Returns a Hash of Analysis object for all of TIMES's time keys.


347
348
349
350
351
352
353
# File 'lib/bullshit.rb', line 347

def analysis
  @analysis ||= Hash.new do |h, time|
    time = time.to_sym
    times = @times[time]
    h[time] = Analysis.new(times)
  end
end

#arithmetic_mean(time) ⇒ Object Also known as: mean

Returns the arithmetic mean of time.


493
494
495
# File 'lib/bullshit.rb', line 493

def arithmetic_mean(time)
  analysis[time.to_sym].mean
end

#autocorrelation(time) ⇒ Object

Return the array of autocorrelation values for time.


520
521
522
# File 'lib/bullshit.rb', line 520

def autocorrelation(time)
  analysis[time.to_sym].autocorrelation
end

#autocorrelation_plot(time) ⇒ Object

Returns the arrays for the autocorrelation plot, the first array for the numbers of lag measured, the second for the autocorrelation value.


526
527
528
529
530
531
# File 'lib/bullshit.rb', line 526

def autocorrelation_plot(time)
  r = autocorrelation time
  start = @times[:repeat].first
  ende = (start + r.size)
  (start...ende).to_a.zip(r)
end

#call_time_meanObject

Seconds per call (mean)


467
468
469
# File 'lib/bullshit.rb', line 467

def call_time_mean
  mean(self.case.compare_time)
end

#call_time_medianObject

Seconds per call (median)


483
484
485
# File 'lib/bullshit.rb', line 483

def call_time_median
  median(self.case.compare_time)
end

#calls(call_time_type) ⇒ Object

Calls per second of the call_time_type, e. g. :call_time_mean or :call_time_median.


473
474
475
# File 'lib/bullshit.rb', line 473

def calls(call_time_type)
  __send__(call_time_type) ** -1
end

#calls_meanObject

Calls per second (mean)


478
479
480
# File 'lib/bullshit.rb', line 478

def calls_mean
  call_time_mean ** -1
end

#calls_medianObject

Calls per second (median)


488
489
490
# File 'lib/bullshit.rb', line 488

def calls_median
  call_time_median ** -1
end

#caseObject

The benchmark case class this clock belongs to (via bc_method).


319
320
321
# File 'lib/bullshit.rb', line 319

def case
  @bc_method.case.class
end

#cover?(other) ⇒ Boolean

Return true, if other's mean value is indistinguishable from this object's mean after filtering out the noise from the measurements with a Welch's t-Test. This mean's that differences in the mean of both clocks might not inidicate a real performance difference and may be caused by chance.

Returns:

  • (Boolean)

360
361
362
363
# File 'lib/bullshit.rb', line 360

def cover?(other)
  time = self.case.compare_time.to_sym
  analysis[time].cover?(other.analysis[time], self.case.covering.alpha_level.abs)
end

#detect_autocorrelation(time) ⇒ Object

Returns the q value for the Ljung-Box statistic of this time's analysis.detect_autocorrelation method.


448
449
450
451
452
# File 'lib/bullshit.rb', line 448

def detect_autocorrelation(time)
  analysis[time.to_sym].detect_autocorrelation(
    self.case.autocorrelation.max_lags.to_i,
    self.case.autocorrelation.alpha_level.abs)
end

#detect_outliers(time) ⇒ Object

Return a result hash with the number of :very_low, :low, :high, and :very_high outliers, determined by the box plotting algorithm run with :median and :iqr parameters. If no outliers were found or the iqr is less than epsilon, nil is returned.


458
459
460
# File 'lib/bullshit.rb', line 458

def detect_outliers(time)
  analysis[time.to_sym].detect_outliers(self.case.outliers_factor.abs)
end

#file_path(*args) ⇒ Object

Return the result of CaseMethod#file_path for this clock's bc_method.


534
535
536
# File 'lib/bullshit.rb', line 534

def file_path(*args)
  @bc_method.file_path(*args)
end

#find_truncation_offsetObject

Find an offset from the start of the measurements in this clock to truncate the initial data until a stable state has been reached and return it as an integer.


553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/bullshit.rb', line 553

def find_truncation_offset
  truncation = self.case.truncate_data
  slope_angle = self.case.truncate_data.slope_angle.abs
  time = self.case.compare_time.to_sym
  ms = analysis[time].measurements.reverse
  offset = ms.size - 1
  @slopes = []
  ModuleFunctions.array_window(ms, truncation.window_size) do |data|
    lr = LinearRegression.new(data)
    a = lr.a
    @slopes << [ offset, a ]
    a.abs > slope_angle and break
    offset -= 1
  end
  offset < 0 ? 0 : offset
end

#geometric_mean(time) ⇒ Object

Returns the geometric mean of time.


505
506
507
# File 'lib/bullshit.rb', line 505

def geometric_mean(time)
  analysis[time.to_sym].geometric_mean
end

#harmonic_mean(time) ⇒ Object

Returns the harmonic mean of time.


500
501
502
# File 'lib/bullshit.rb', line 500

def harmonic_mean(time)
  analysis[time.to_sym].harmonic_mean
end

#histogram(time) ⇒ Object

Return the Histogram for the time values.


515
516
517
# File 'lib/bullshit.rb', line 515

def histogram(time)
  analysis[time.to_sym].histogram(self.case.histogram.bins)
end

#inc_scatterObject

Increment scatter counter by one.


392
393
394
# File 'lib/bullshit.rb', line 392

def inc_scatter
  @scatter += 1
end

#max(time) ⇒ Object

Returns the maximum for the time (one of TIMES' symbols).


432
433
434
# File 'lib/bullshit.rb', line 432

def max(time)
  analysis[time.to_sym].max
end

#measureObject

Take a single measurement. This method should be called with the code to benchmark in a block.


398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/bullshit.rb', line 398

def measure
  before = take_time
  yield
  after = take_time
  @repeat += 1
  @times[:repeat] << @repeat
  @times[:scatter] << @scatter
  bs = self.case.batch_size.abs
  if bs and bs > 1
    TIMES.each_with_index { |t, i| @times[t] << (after[i] - before[i]) / bs }
  else
    TIMES.each_with_index { |t, i| @times[t] << after[i] - before[i] }
  end
  @analysis = nil
end

#median(time) ⇒ Object

Returns the median of the time values (one of TIMES' symbols).


437
438
439
# File 'lib/bullshit.rb', line 437

def median(time)
  analysis[time.to_sym].median
end

#min(time) ⇒ Object

Returns the minimum for the time (one of TIMES' symbols).


427
428
429
# File 'lib/bullshit.rb', line 427

def min(time)
  analysis[time.to_sym].min
end

#percentile(time, p = 50) ⇒ Object

Returns the p-percentile of the time values (one of TIMES' symbols).


442
443
444
# File 'lib/bullshit.rb', line 442

def percentile(time, p = 50)
  analysis[time.to_sym].percentile p
end

#sample_standard_deviation(time) ⇒ Object

Returns the sample standard deviation for the time (one of TIMES' symbols).


416
417
418
# File 'lib/bullshit.rb', line 416

def sample_standard_deviation(time)
  analysis[time.to_sym].sample_standard_deviation
end

#sample_standard_deviation_percentage(time) ⇒ Object

Returns the sample standard deviation for the time (one of TIMES' symbols) in percentage of its arithmetic mean.


422
423
424
# File 'lib/bullshit.rb', line 422

def sample_standard_deviation_percentage(time)
  analysis[time.to_sym].sample_standard_deviation_percentage
end

#take_timeObject

Takes the times an returns an array, consisting of the times in the order of enumerated in the TIMES constant.


383
384
385
386
387
388
389
# File 'lib/bullshit.rb', line 383

def take_time
  @time, times = Time.now, Process.times
  user_time = times.utime + times.cutime    # user time of this process and its children
  system_time = times.stime + times.cstime  # system time of this process and its children
  total_time = user_time + system_time      # total time of this process and its children
  [ @time.to_f, total_time, user_time, system_time ]
end

#to_aObject

Returns the measurements as an array of arrays.


371
372
373
374
375
376
377
378
379
# File 'lib/bullshit.rb', line 371

def to_a
  if @repeat >= 1
    (::Bullshit::Clock::ALL_COLUMNS).map do |t|
      analysis[t].measurements
    end.transpose
  else
    []
  end
end

#truncate_data(offset) ⇒ Object

Truncate the measurements stored in this clock starting from the integer offset.


540
541
542
543
544
545
546
547
548
# File 'lib/bullshit.rb', line 540

def truncate_data(offset)
  for t in ALL_COLUMNS
    times = @times[t]
    @times[t] = @times[t][offset, times.size]
    @repeat = @times[t].size
  end
  @analysis = nil
  self
end