measure.in.rb 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. #!/usr/bin/env ruby
  2. #
  3. # Copyright Louis Dionne 2013-2017
  4. # Distributed under the Boost Software License, Version 1.0.
  5. # (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
  6. #
  7. #
  8. # When called as a program, this script runs the command line given in
  9. # arguments and returns the total time. This is similar to the `time`
  10. # command from Bash.
  11. #
  12. # This file can also be required as a Ruby module to gain access to the
  13. # methods defined below.
  14. #
  15. # NOTE:
  16. # This file must not be used as-is. It must be processed by CMake first.
  17. require 'benchmark'
  18. require 'open3'
  19. require 'pathname'
  20. require 'ruby-progressbar'
  21. require 'tilt'
  22. def split_at(n, list)
  23. before = list[0...n] || []
  24. after = list[n..-1] || []
  25. return [before, after]
  26. end
  27. # types : A sequence of strings to put in the mpl::vector.
  28. # Using this method requires including
  29. # - <boost/mpl/vector.hpp>
  30. # - <boost/mpl/push_back.hpp>
  31. def mpl_vector(types)
  32. fast, rest = split_at(20, types)
  33. rest.inject("boost::mpl::vector#{fast.length}<#{fast.join(', ')}>") { |v, t|
  34. "boost::mpl::push_back<#{v}, #{t}>::type"
  35. }
  36. end
  37. # types : A sequence of strings to put in the mpl::list.
  38. # Using this method requires including
  39. # - <boost/mpl/list.hpp>
  40. # - <boost/mpl/push_front.hpp>
  41. def mpl_list(types)
  42. prefix, fast = split_at([types.length - 20, 0].max, types)
  43. prefix.reverse.inject("boost::mpl::list#{fast.length}<#{fast.join(', ')}>") { |l, t|
  44. "boost::mpl::push_front<#{l}, #{t}>::type"
  45. }
  46. end
  47. # values : A sequence of strings representing values to put in the fusion::vector.
  48. # Using this method requires including
  49. # - <boost/fusion/include/make_vector.hpp>
  50. # - <boost/fusion/include/push_back.hpp>
  51. def fusion_vector(values)
  52. fast, rest = split_at(10, values)
  53. rest.inject("boost::fusion::make_vector(#{fast.join(', ')})") { |xs, v|
  54. "boost::fusion::push_back(#{xs}, #{v})"
  55. }
  56. end
  57. # values : A sequence of strings representing values to put in the fusion::list.
  58. # Using this method requires including
  59. # - <boost/fusion/include/make_list.hpp>
  60. # - <boost/fusion/include/push_back.hpp>
  61. def fusion_list(values)
  62. fast, rest = split_at(10, values)
  63. rest.inject("boost::fusion::make_list(#{fast.join(', ')})") { |xs, v|
  64. "boost::fusion::push_back(#{xs}, #{v})"
  65. }
  66. end
  67. # Turns a CMake-style boolean into a Ruby boolean.
  68. def cmake_bool(b)
  69. return true if b.is_a? String and ["true", "yes", "1"].include?(b.downcase)
  70. return true if b.is_a? Integer and b > 0
  71. return false # otherwise
  72. end
  73. # aspect must be one of :compilation_time, :bloat, :execution_time
  74. def measure(aspect, template_relative, range, env = {})
  75. measure_file = Pathname.new("#{MEASURE_FILE}")
  76. template = Pathname.new(template_relative).expand_path
  77. range = range.to_a
  78. if ENV["BOOST_HANA_JUST_CHECK_BENCHMARKS"] && range.length >= 2
  79. range = [range[0], range[-1]]
  80. end
  81. make = -> (target) {
  82. command = "@CMAKE_COMMAND@ --build @CMAKE_BINARY_DIR@ --target #{target}"
  83. stdout, stderr, status = Open3.capture3(command)
  84. }
  85. progress = ProgressBar.create(format: '%p%% %t | %B |',
  86. title: template_relative,
  87. total: range.size,
  88. output: STDERR)
  89. range.map do |n|
  90. # Evaluate the ERB template with the given environment, and save
  91. # the result in the `measure.cpp` file.
  92. code = Tilt::ERBTemplate.new(template).render(nil, input_size: n, env: env)
  93. measure_file.write(code)
  94. # Compile the file and get timing statistics. The timing statistics
  95. # are output to stdout when we compile the file because of the way
  96. # the `compile.benchmark.measure` CMake target is setup.
  97. stdout, stderr, status = make["#{MEASURE_TARGET}"]
  98. raise "compilation error: #{stdout}\n\n#{stderr}\n\n#{code}" if not status.success?
  99. ctime = stdout.match(/\[compilation time: (.+)\]/i)
  100. # Size of the generated executable in KB
  101. size = File.size("@CMAKE_CURRENT_BINARY_DIR@/#{MEASURE_TARGET}").to_f / 1000
  102. # If we didn't match anything, that's because we went too fast, CMake
  103. # did not have the time to see the changes to the measure file and
  104. # the target was not rebuilt. So we sleep for a bit and then retry
  105. # this iteration.
  106. (sleep 0.2; redo) if ctime.nil?
  107. stat = ctime.captures[0].to_f if aspect == :compilation_time
  108. stat = size if aspect == :bloat
  109. # Run the resulting program and get timing statistics. The statistics
  110. # should be written to stdout by the `measure` function of the
  111. # `measure.hpp` header.
  112. if aspect == :execution_time
  113. stdout, stderr, status = make["#{MEASURE_TARGET}.run"]
  114. raise "runtime error: #{stderr}\n\n#{code}" if not status.success?
  115. match = stdout.match(/\[execution time: (.+)\]/i)
  116. if match.nil?
  117. raise ("Could not find [execution time: ...] bit in the output. " +
  118. "Did you use the `measure` function in the `measure.hpp` header? " +
  119. "stdout follows:\n#{stdout}")
  120. end
  121. stat = match.captures[0].to_f
  122. end
  123. progress.increment
  124. [n, stat]
  125. end
  126. ensure
  127. measure_file.write("")
  128. progress.finish if progress
  129. end
  130. def time_execution(erb_file, range, env = {})
  131. measure(:execution_time, erb_file, range, env)
  132. end
  133. def time_compilation(erb_file, range, env = {})
  134. measure(:compilation_time, erb_file, range, env)
  135. end
  136. if __FILE__ == $0
  137. command = ARGV.join(' ')
  138. time = Benchmark.realtime { `#{command}` }
  139. puts "[command line: #{command}]"
  140. puts "[compilation time: #{time}]"
  141. end