blob: b71a906ea098bc79b31a4fcf1cd0d71ff39ee83b [file] [log] [blame]
#!/usr/bin/env ruby
# coding: utf-8
require 'getoptlong'
require 'pathname'
$benchmarks_all = [
# Single-threaded benchmarks.
"churn",
"list_allocate",
"tree_allocate",
"tree_churn",
"fragment",
"fragment_iterate",
"medium",
"big",
# Benchmarks based on browser recordings.
"facebook",
"reddit",
"flickr",
"theverge",
"nimlang",
# Multi-threaded benchmark variants.
"message_one",
"message_many",
"churn --parallel",
"list_allocate --parallel",
"tree_allocate --parallel",
"tree_churn --parallel",
"fragment --parallel",
"fragment_iterate --parallel",
# These tests often crash TCMalloc: <rdar://problem/13657137>.
"medium --parallel",
"big --parallel",
# Enable these tests to test memory footprint. The way they run is not
# really compatible with throughput testing.
# "reddit_memory_warning --runs 0",
# "flickr_memory_warning --runs 0",
# "theverge_memory_warning --runs 0",
# Enable this test to test shrinking back down from a large heap while a process remains active.
# The way it runs is not really compatible with throughput testing.
# "balloon"
"facebook --parallel",
"reddit --parallel",
"flickr --parallel",
"theverge --parallel",
# "nimlang --use-thread-id",
]
$benchmarks_memory = [
"facebook",
"reddit",
"flickr",
"theverge",
"nimlang"
]
$benchmarks_memory_warning = [
"reddit_memory_warning --runs 0",
"flickr_memory_warning --runs 0",
"theverge_memory_warning --runs 0",
]
$benchmarks = $benchmarks_all
$heap = 0
def usage
puts "run-malloc-benchmarks [options] /path/to/MallocBench Name:/path/to/libmbmalloc.dylib [ Name:/path/to/libmbmalloc.dylib ]"
puts
puts " Runs a suite of memory allocation and access benchmarks."
puts
puts " <Name:/path/to/libmbmalloc.dylib> is a symbolic name followed by a path to libmbmalloc.dylib."
puts
puts " Specify \"SystemMalloc\" to test the built-in libc malloc."
puts " Specify \"NanoMalloc\" to test the built-in libc malloc using the NanoMalloc zone."
puts
puts " Example usage:"
puts
puts " run-malloc-benchmarks /BUILD/MallocBench SystemMalloc:/BUILD/libmbmalloc.dylib NanoMalloc:/BUILD/libmbmalloc.dylib"
puts " run-malloc-benchmarks /BUILD/MallocBench FastMalloc:/BUILD/FastMalloc/libmbmalloc.dylib"
puts " run-malloc-benchmarks --benchmark churn SystemMalloc:/BUILD/libmbmalloc.dylib FastMalloc:/BUILD/FastMalloc/libmbmalloc.dylib"
puts
puts "Options:"
puts
puts " --benchmark <benchmark> Select a single benchmark to run instead of the full suite."
puts " --heap <heap> Set a baseline heap size."
puts
end
class Dylib
attr_reader :name
attr_reader :path
def initialize(name, path)
@name = name
@path = path
end
end
class Results
attr_reader :executionTime
attr_reader :peakMemory
attr_reader :memoryAtEnd
def initialize(executionTime, peakMemory, memoryAtEnd)
@executionTime = executionTime
@peakMemory = peakMemory
@memoryAtEnd = memoryAtEnd
end
end
class Stat
attr_reader :benchmark
attr_reader :result
def initialize(benchmark, result)
@benchmark = benchmark
@result = result[/\d+/].to_i
end
end
class TimeStat < Stat
def to_s
@result + "ms"
end
end
class MemoryStat < Stat
def to_s
@result + "kB"
end
end
class PeakMemoryStat < Stat
def to_s
@result + "kB"
end
end
def lpad(str, chars)
if str.length > chars
str
else
"%#{chars}s"%(str)
end
end
def rpad(str, chars)
while str.length < chars
str += " "
end
str
end
def computeArithmeticMean(array)
sum = 0.0
array.each {
| value |
sum += value
}
(sum / array.length)
end
def computeGeometricMean(array)
mult = 1.0
array.each {
| value |
mult *= value ? value : 1.0
}
(mult ** (1.0 / array.length))
end
def computeHarmonicMean(array)
1.0 / computeArithmeticMean(array.collect{ | value | 1.0 / value })
end
def lowerIsBetter(a, b, better, worse)
if b < a
return "^ " + (a.to_f / b.to_f).round(2).to_s + "x " + better
end
if b == a
return ""
end
"! " + (b.to_f / a.to_f).round(2).to_s + "x " + worse
end
def lowerIsFaster(a, b)
lowerIsBetter(a, b, "faster", "slower")
end
def lowerIsSmaller(a, b)
lowerIsBetter(a, b, "smaller", "bigger")
end
def numberWithDelimiter(number)
number.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse
end
def prettify(number, suffix)
numberWithDelimiter(number) + suffix
end
def parseOptions
GetoptLong.new(
['--benchmark', GetoptLong::REQUIRED_ARGUMENT],
['--memory', GetoptLong::NO_ARGUMENT],
['--memory_warning', GetoptLong::NO_ARGUMENT],
['--heap', GetoptLong::REQUIRED_ARGUMENT],
['--help', GetoptLong::NO_ARGUMENT],
).each {
| opt, arg |
case opt
when '--benchmark'
$benchmarks = [ arg ]
when '--memory'
$benchmarks = $benchmarks_memory
when '--memory_warning'
$benchmarks = $benchmarks_memory_warning
when '--heap'
$heap = arg
when '--help'
usage
exit 1
else
raise "bad option: #{opt}"
end
}
if ARGV.length < 1
puts "Error: No MallocBench specified."
exit 1
end
if ARGV.length < 2
puts "Error: No dylib specified."
exit 1
end
$mallocBench = File.absolute_path(ARGV.shift)
if !File.exists?($mallocBench)
puts "File not found: #{$mallocBench}."
exit 1
end
$buildDir = Pathname.new($mallocBench).dirname
dylibs = []
ARGV.each {
| arg |
name, path = arg.split(":")
if !name || name.length < 1 ||
!path || path.length < 1
puts "Invalid <Name:/path/to/dylib>: '#{arg}'."
exit 1
end
dylib = Dylib.new(name, File.expand_path(path))
if !File.exists?(dylib.path)
puts "File not found: #{dylib.path}."
exit 1
end
dylibs.push(dylib)
}
dylibs
end
def runBenchmarks(dylibs)
executionTime = []
peakMemory = []
memoryAtEnd = []
$benchmarks.each {
| benchmark |
executionTime.push([])
peakMemory.push([])
memoryAtEnd.push([])
dylibs.each {
| dylib |
$stderr.print "\rRUNNING #{dylib.name}: #{benchmark}... "
env = "DYLD_LIBRARY_PATH='#{Pathname.new(dylib.path).dirname}' "
env += "LD_LIBRARY_PATH='#{Pathname.new(dylib.path).dirname}' "
if dylib.name == "NanoMalloc"
env += "MallocNanoZone=1 "
elsif dylib.name == "SystemMalloc"
env += "MallocNanoZone=0 "
end
input = "cd '#{$buildDir}'; #{env} '#{$mallocBench}' --benchmark #{benchmark} --heap #{$heap}}"
output =`#{input}`
splitOutput = output.split("\n")
executionTime[-1].push(TimeStat.new(benchmark, splitOutput[1]))
peakMemory[-1].push(PeakMemoryStat.new(benchmark, splitOutput.length > 3 ? splitOutput[2] : "0"))
memoryAtEnd[-1].push(MemoryStat.new(benchmark, splitOutput.length > 2 ? splitOutput[3] : "0"))
}
}
$stderr.print "\r \n"
Results.new(executionTime, peakMemory, memoryAtEnd)
end
def printResults(dylibs, results)
def printHeader(dylibs, fieldSize)
print
print lpad("", fieldSize)
print lpad(dylibs[0].name, fieldSize)
if dylibs.length > 1
print lpad(dylibs[1].name, fieldSize)
print lpad("Δ", fieldSize)
end
print "\n"
end
def printMetric(name, results, compareFunction, suffix, fieldSize)
def printMean(name, results, meanFunction, compareFunction, suffix, fieldSize)
means = []
means.push(meanFunction.call(results.collect { | stats | stats[0].result }))
print rpad(" " + name, fieldSize)
print lpad("#{prettify(means[0].round, suffix)}", fieldSize)
if results[0][1]
means.push(meanFunction.call(results.collect { | stats | stats[1].result }))
print lpad("#{prettify(means[1].round, suffix)}", fieldSize)
print lpad(compareFunction.call(means[0], means[1]), fieldSize)
end
print "\n"
end
if results[0][0].result == 0
return
end
print name + ":\n"
results.each {
| stats |
print rpad(" " + stats[0].benchmark, fieldSize)
print lpad("#{prettify(stats[0].result, suffix)}", fieldSize)
if stats[1]
print lpad("#{prettify(stats[1].result, suffix)}", fieldSize)
print lpad(compareFunction.call(stats[0].result, stats[1].result), fieldSize)
end
print "\n"
}
print "\n"
printMean("<geometric mean>", results, method(:computeGeometricMean), compareFunction, suffix, fieldSize)
printMean("<arithmetic mean>", results, method(:computeArithmeticMean), compareFunction, suffix, fieldSize)
printMean("<harmonic mean>", results, method(:computeHarmonicMean), compareFunction, suffix, fieldSize)
print "\n"
end
fieldSize = ($benchmarks + ["<arithmetic mean>"]).collect {
| benchmark |
benchmark.size
}.max + 4
printHeader(dylibs, fieldSize)
printMetric("Execution Time", results.executionTime, method(:lowerIsFaster), "ms", fieldSize)
printMetric("Peak Memory", results.peakMemory, method(:lowerIsSmaller), "kB", fieldSize)
printMetric("Memory at End", results.memoryAtEnd, method(:lowerIsSmaller), "kB", fieldSize)
end
def main
begin
dylibs = parseOptions()
results = runBenchmarks(dylibs)
printResults(dylibs, results)
rescue => exception
puts
puts
puts exception
puts exception.backtrace
puts
end
end
main()