Class: Bullshit::Case

Inherits:
Object
  • Object
show all
Extended by:
DSLKit::DSLAccessor
Defined in:
lib/bullshit.rb

Overview

This is the base class of all Benchmarking Cases.

Direct Known Subclasses

RangeCase, RepeatCase, TimeCase

Defined Under Namespace

Modules: CaseExtension

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeCase

Returns a Case instance, that is used to run benchmark methods and measure their running time.



707
708
709
710
711
712
713
# File 'lib/bullshit.rb', line 707

def initialize
  @clocks = []
  if self.class.comparison
    @comparison = Comparison.new
    @comparison.output self.class.output
  end
end

Instance Attribute Details

#clocksObject (readonly)

The clock instances, that were used during a run of this benchmark case.



830
831
832
# File 'lib/bullshit.rb', line 830

def clocks
  @clocks
end

Class Method Details

.autorun_allObject

Autorun all subclasses’ instances, that is all Bullshit cases. If its autorun dsl_accessor is false or it has already run, don’t run the case.



698
699
700
701
702
# File 'lib/bullshit.rb', line 698

def autorun_all
  each do |bc_class|
    bc_class.autorun and bc_class.run_count == 0 and bc_class.run
  end
end

.each(&block) ⇒ Object

Iterate over all subclasses of class Case.



683
684
685
# File 'lib/bullshit.rb', line 683

def each(&block)
  cases.each(&block)
end

.inherited(klass) ⇒ Object



660
661
662
# File 'lib/bullshit.rb', line 660

def inherited(klass)
  klass.extend CaseExtension
end

.output_filename(name) ⇒ Object



670
671
672
673
# File 'lib/bullshit.rb', line 670

def output_filename(name)
  path = File.expand_path(name, output_dir)
  output File.new(path, 'a+')
end

.run(do_compare = true) ⇒ Object

Creates an instance of this class and run it.



851
852
853
# File 'lib/bullshit.rb', line 851

def self.run(do_compare = true)
  new.run do_compare
end

.run_allObject

Run all subclasses’ instances, that is all Bullshit cases, unless they already have run.



689
690
691
692
693
# File 'lib/bullshit.rb', line 689

def run_all
  each do |bc_class|
    bc_class.run_count == 0 and bc_class.run
  end
end

.run_countObject

Returns the total number of run counts run_count.



676
677
678
# File 'lib/bullshit.rb', line 676

def run_count
  cases.inject(0) { |s, c| s + c.run_count }
end

.sorted_bmethodsObject

Return all benchmark methods of this Case instance lexicographically sorted.



722
723
724
# File 'lib/bullshit.rb', line 722

def self.sorted_bmethods
  instance_methods.map { |x| x.to_s }.grep(/\Abenchmark_/).sort
end

Instance Method Details

#[](method_name) ⇒ Object

Return the CaseMethod instance for method_name or nil, if there isn’t any method of this name.



739
740
741
742
# File 'lib/bullshit.rb', line 739

def [](method_name)
  method_name = "benchmark_#{method_name}"
  bmethods.find { |bm| bm.name == method_name }
end

#bmethodsObject

Return all benchmark methods of this Case instance in a random order.



727
728
729
730
731
732
733
734
735
# File 'lib/bullshit.rb', line 727

def bmethods
  unless @bmethods
    @bmethods = self.class.sorted_bmethods.sort_by do
      rand
    end
    @bmethods.map! { |n| CaseMethod.new(n, self) }
  end
  @bmethods
end

#evaluation(clock) ⇒ Object

This method has to be implemented in subclasses, it should return the evaluation results of the benchmarks as a string.

Raises:

  • (NotImplementedError)


908
909
910
# File 'lib/bullshit.rb', line 908

def evaluation(clock)
  raise NotImplementedError, "has to be implemented in subclasses"
end

#longest_nameObject

Return the length of the longest_name of all these methods’ names.



745
746
747
748
# File 'lib/bullshit.rb', line 745

def longest_name
  bmethods.empty? and return 0
  bmethods.map { |x| x.short_name.size }.max
end

#post_run(bc_method) ⇒ Object

Output after bc_method is run.



913
914
915
916
917
918
919
# File 'lib/bullshit.rb', line 913

def post_run(bc_method)
  teardown_name = bc_method.teardown_name
  if respond_to? teardown_name
    $DEBUG and warn "Calling #{teardown_name}."
    __send__(bc_method.teardown_name)
  end
end

#pre_run(bc_method) ⇒ Object

Output before bc_method is run.



885
886
887
888
889
890
891
892
# File 'lib/bullshit.rb', line 885

def pre_run(bc_method)
  setup_name = bc_method.setup_name
  if respond_to? setup_name
    $DEBUG and warn "Calling #{setup_name}."
    __send__(setup_name)
  end
  self.class.output.puts "#{bc_method.long_name}:"
end

#run(do_compare = true) ⇒ Object

Setup, run all benchmark cases (warmup and the real run) and output results, run method speed comparisons, and teardown.



834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
# File 'lib/bullshit.rb', line 834

def run(do_compare = true)
  old_sync, self.class.output.sync = self.class.output.sync, true
  $DEBUG and warn "Calling setup."
  setup
  run_once
  do_compare and @comparison and @comparison.display
  self
rescue => e
  warn "Caught #{e.class}: #{e}\n\n#{e.backtrace.map { |x| "\t#{x}\n" }}"
ensure
  $DEBUG and warn "Calling teardown."
  teardown
  @clocks and write_files
  self.class.output.sync = old_sync
end

#run_method(bc_method) ⇒ Object

Run only pre_run and post_run methods. Yield to the block, if one was given.



896
897
898
899
900
901
902
903
904
# File 'lib/bullshit.rb', line 896

def run_method(bc_method)
  pre_run bc_method
  clock = self.class.clock.__send__(self.class.clock_method, bc_method) do
    __send__(bc_method.name)
  end
  bc_method.clock = clock
  post_run bc_method
  clock
end

#run_onceObject

Run benchmark case once and output results.



751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
# File 'lib/bullshit.rb', line 751

def run_once
  self.class.run_count(self.class.run_count + 1)
  self.class.output.puts Time.now.strftime(' %FT%T %Z ').center(COLUMNS, '=')
  self.class.output.puts "Benchmarking on #{RUBY_DESCRIPTION}."
  self.class.output.puts self.class.message
  self.class.output.puts '=' * COLUMNS, ''
  @clocks.clear
  if self.class.warmup == :aggressive
    self.class.output.puts "Aggressively run all benchmarks for warmup first.", ''
    bmethods.each do |bc_method|
      GC.start
      clock = run_method bc_method
      self.class.output.puts evaluation(clock)
      GC.start
    end
    self.class.output.puts "Aggressive warmup done.", '', '=' * COLUMNS, ''
  end
  first = true
  bmethods.each do |bc_method|
    if first
      first = false
    else
      self.class.output.puts '-' * COLUMNS, ''
    end
    if self.class.warmup
      self.class.output.puts "This first run is only for warmup."
      GC.start
      clock = run_method bc_method
      self.class.output.puts evaluation(clock)
      GC.start
    end
    clock = run_method(bc_method)
    if self.class.truncate_data.enabled
      message = ''
      offset = clock.find_truncation_offset
      if clock.case.data_file
        slopes_file_path = clock.file_path 'slopes'
        message << "Writing slopes data file '#{slopes_file_path}'.\n"
        File.open(slopes_file_path, 'w') do |slopes_file|
          slopes_file.puts %w[#scatter slope] * "\t"
          slopes_file.puts clock.slopes.map { |s| s * "\t" }
        end
      end
      case offset
      when 0
        message << "No initial data truncated.\n =>"\
          " System may have been in a steady state from the beginning."
      when clock.repeat
        message << "After truncating measurements no data would have"\
          " remained.\n => No steady state could be detected."
      else
        if clock.case.data_file
          data_file_path = clock.file_path 'untruncated'
          message << "Writing untruncated measurement data file '#{data_file_path}'.\n"
          File.open(data_file_path, 'w') do |data_file|
            data_file.puts clock.class.to_a * "\t"
            data_file.puts clock.to_a.map { |times| times * "\t" }
          end
        end
        remaining = clock.repeat - offset
        offset_percentage = 100 * offset.to_f / clock.repeat
        message << sprintf("Truncated initial %u measurements: "\
          "%u -> %u (-%0.2f%%).\n", offset, clock.repeat, remaining,
          offset_percentage)
        clock.truncate_data(offset)
      end
      self.class.output.puts evaluation(clock), message
    else
      self.class.output.puts evaluation(clock)
    end
    @clocks << clock
    if @comparison
      @comparison.benchmark(self, bc_method.short_name, :run => false)
    end
  end
  @clocks
end

#setupObject

General setup for all the benchmark methods.



922
923
# File 'lib/bullshit.rb', line 922

def setup
end

#teardownObject

General teardown for all the benchmark methods.



926
927
# File 'lib/bullshit.rb', line 926

def teardown
end

#to_sObject

Return the name of the benchmark case as a string.



716
717
718
# File 'lib/bullshit.rb', line 716

def to_s
  self.class.benchmark_name
end

#write_filesObject

Write all output files after run.



856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
# File 'lib/bullshit.rb', line 856

def write_files
  for clock in @clocks
    if clock.case.data_file data_file_path = clock.file_path
      self.class.output.puts "Writing measurement data file '#{data_file_path}'."
      File.open(data_file_path, 'w') do |data_file|
        data_file.puts clock.class.to_a * "\t"
        data_file.puts clock.to_a.map { |times| times * "\t" }
      end
    end
    if clock.case.histogram.enabled and clock.case.histogram.file
      histogram_file_path = clock.file_path 'histogram'
      self.class.output.puts "Writing histogram file '#{histogram_file_path}'."
      File.open(histogram_file_path, 'w') do |data_file|
        data_file.puts %w[#binleft frequency binright] * "\t"
        data_file.puts clock.histogram(clock.case.compare_time).to_a.map { |times| times * "\t" }
      end
    end
    if clock.case.autocorrelation.enabled and clock.case.autocorrelation.file
      ac_plot_file_path = clock.file_path 'autocorrelation'
      self.class.output.puts "Writing autocorrelation plot file '#{ac_plot_file_path}'."
      File.open(ac_plot_file_path, 'w') do |data_file|
        data_file.puts %w[#lag autocorrelation] * "\t"
        data_file.puts clock.autocorrelation_plot(clock.case.compare_time).to_a.map { |ac| ac * "\t" }
      end
    end
  end
end