#!/usr/bin/env dart // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. import 'package:kernel/kernel.dart'; import 'package:kernel/class_hierarchy.dart'; import 'package:args/args.dart'; import 'class_hierarchy_basic.dart'; import 'dart:math'; import 'dart:io'; ArgParser argParser = new ArgParser() ..addFlag('basic', help: 'Measure the basic implementation', negatable: false) ..addOption('cycle', abbr: 'c', help: 'Build N copies of the class hierarchy and cycle queries ' 'between them', defaultsTo: '1'); String usage = ''' Usage: class_hierarchy_bench [options] FILE.dill Options: ${argParser.usage} '''; main(List args) { if (args.length == 0) { print(usage); exit(1); } ArgResults options = argParser.parse(args); if (options.rest.length != 1) { print('Exactly one file must be given'); exit(1); } String filename = options.rest.single; Component component = loadComponentFromBinary(filename); ClassHierarchy buildHierarchy() { return options['basic'] ? new BasicClassHierarchy(component) : new ClassHierarchy(component); } var watch = new Stopwatch()..start(); buildHierarchy(); int coldBuildTime = watch.elapsedMilliseconds; watch.reset(); const int numBuildTrials = 100; for (int i = 0; i < numBuildTrials; i++) { buildHierarchy(); } int hotBuildTime = watch.elapsedMilliseconds ~/ numBuildTrials; int hierarchyCount = int.parse(options['cycle']); var hierarchies = []; for (int i = 0; i < hierarchyCount; i++) { hierarchies.add(buildHierarchy()); } List classes = hierarchies.first.classes.toList(); int currentHierarchy = 0; ClosedWorldClassHierarchy getClassHierarchy() { currentHierarchy = (currentHierarchy + 1) % hierarchies.length; return hierarchies[currentHierarchy]; } { var classHierarchy = getClassHierarchy(); if (classHierarchy is ClosedWorldClassHierarchy) { for (Class class_ in classes) { classHierarchy.hasProperSubtypes(class_); } } } Random rnd = new Random(12345); const int numQueryTrials = 100000; // Measure isSubclassOf, isSubmixtureOf, isSubtypeOf, getClassAsInstanceOf. // Warm-up run to ensure the JIT compiler does not favor the first query we // test. watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int first = rnd.nextInt(classes.length); int second = rnd.nextInt(classes.length); Class firstClass = classes[first]; Class secondClass = classes[second]; classHierarchy.isSubclassOf(firstClass, secondClass); classHierarchy.isSubtypeOf(firstClass, secondClass); classHierarchy.getClassAsInstanceOf(firstClass, secondClass); } watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int first = rnd.nextInt(classes.length); int second = rnd.nextInt(classes.length); Class firstClass = classes[first]; Class secondClass = classes[second]; classHierarchy.isSubclassOf(firstClass, secondClass); } int subclassQueryTime = watch.elapsedMicroseconds; watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int first = rnd.nextInt(classes.length); int second = rnd.nextInt(classes.length); Class firstClass = classes[first]; Class secondClass = classes[second]; classHierarchy.isSubtypeOf(firstClass, secondClass); } int subtypeQueryTime = watch.elapsedMicroseconds; watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int first = rnd.nextInt(classes.length); int second = rnd.nextInt(classes.length); Class firstClass = classes[first]; Class secondClass = classes[second]; classHierarchy.getClassAsInstanceOf(firstClass, secondClass); } int asInstanceOfQueryTime = watch.elapsedMicroseconds; // Estimate the overhead from test case generation. watch.reset(); for (int i = 0; i < numQueryTrials; i++) { int first = rnd.nextInt(classes.length); int second = rnd.nextInt(classes.length); classes[first]; classes[second]; } int queryNoise = watch.elapsedMicroseconds; subclassQueryTime -= queryNoise; subtypeQueryTime -= queryNoise; asInstanceOfQueryTime -= queryNoise; String subclassPerSecond = perSecond(subclassQueryTime, numQueryTrials); String subtypePerSecond = perSecond(subtypeQueryTime, numQueryTrials); String asInstanceOfPerSecond = perSecond(asInstanceOfQueryTime, numQueryTrials); // Measure getDispatchTarget and getDispatchTargets. watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int classId = rnd.nextInt(classes.length); Class classNode = classes[classId]; classHierarchy.getDispatchTarget(classNode, new Name('toString')); } int dispatchToStringTime = watch.elapsedMicroseconds; watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int classId = rnd.nextInt(classes.length); Class classNode = classes[classId]; classHierarchy.getDispatchTarget(classNode, new Name('getFloo')); } int dispatchGenericGetTime = watch.elapsedMicroseconds; watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int classId = rnd.nextInt(classes.length); Class classNode = classes[classId]; for (var _ in classHierarchy.getDispatchTargets(classNode)) {} } int dispatchAllTargetsTime = watch.elapsedMicroseconds; // Measure getInterfaceMember and getInterfaceMembers. watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int classId = rnd.nextInt(classes.length); Class classNode = classes[classId]; classHierarchy.getInterfaceMember(classNode, new Name('toString')); } int interfaceToStringTime = watch.elapsedMicroseconds; watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int classId = rnd.nextInt(classes.length); Class classNode = classes[classId]; classHierarchy.getInterfaceMember(classNode, new Name('getFloo')); } int interfaceGenericGetTime = watch.elapsedMicroseconds; watch.reset(); for (int i = 0; i < numQueryTrials; i++) { var classHierarchy = getClassHierarchy(); int classId = rnd.nextInt(classes.length); Class classNode = classes[classId]; for (var _ in classHierarchy.getInterfaceMembers(classNode)) {} } int interfaceAllTargetsTime = watch.elapsedMicroseconds; // Estimate overhead from test case generation. watch.reset(); for (int i = 0; i < numQueryTrials; i++) { int classId = rnd.nextInt(classes.length); classes[classId]; } int dispatchTargetNoise = watch.elapsedMicroseconds; dispatchToStringTime -= dispatchTargetNoise; dispatchGenericGetTime -= dispatchTargetNoise; dispatchAllTargetsTime -= dispatchTargetNoise; interfaceToStringTime -= dispatchTargetNoise; interfaceGenericGetTime -= dispatchTargetNoise; interfaceAllTargetsTime -= dispatchTargetNoise; String dispatchToStringPerSecond = perSecond(dispatchToStringTime, numQueryTrials); String dispatchGetPerSecond = perSecond(dispatchGenericGetTime, numQueryTrials); String dispatchAllTargetsPerSecond = perSecond(dispatchAllTargetsTime, numQueryTrials); String interfaceToStringPerSecond = perSecond(interfaceToStringTime, numQueryTrials); String interfaceGetPerSecond = perSecond(interfaceGenericGetTime, numQueryTrials); String interfaceAllTargetsPerSecond = perSecond(interfaceAllTargetsTime, numQueryTrials); watch.reset(); var classHierarchy = getClassHierarchy(); int numberOfOverridePairs = 0; for (var class_ in classes) { classHierarchy.forEachOverridePair(class_, (member, supermember, isSetter) { ++numberOfOverridePairs; }); } int overrideTime = watch.elapsedMicroseconds; String overridePairsPerSecond = perSecond(overrideTime, numberOfOverridePairs); Map classIds = new Map(); for (Class class_ in classes) { classIds[class_] = classIds.length; } List depth = new List(classes.length); for (int i = 0; i < depth.length; ++i) { int parentDepth = 0; var classNode = classes[i]; for (var supertype in classNode.supers) { var superclass = supertype.classNode; int index = classIds[superclass]; if (!(index < i)) { throw '${classNode.name}($i) extends ${superclass.name}($index)'; } assert(index < i); parentDepth = max(parentDepth, depth[index]); } depth[i] = parentDepth + 1; } List depthHistogram = getHistogramOf(depth); double averageDepth = average(depth); double medianDepth = median(depth); int totalDepth = sum(depth); int numberOfClasses = classes.length; String expenseHistogram = classHierarchy.getExpenseHistogram().skip(1).join(' '); print(''' classes: $numberOfClasses build.cold: $coldBuildTime ms build.hot: $hotBuildTime ms query.isSubclassOf: $subclassPerSecond query.isSubtypeOf: $subtypePerSecond query.getClassAsInstanceOf: $asInstanceOfPerSecond query.getDispatchTarget(toString): $dispatchToStringPerSecond query.getDispatchTarget(getFloo): $dispatchGetPerSecond query.getDispatchTargets.iterate: $dispatchAllTargetsPerSecond query.getInterfaceMember(toString): $interfaceToStringPerSecond query.getInterfaceMember(getFloo): $interfaceGetPerSecond query.getInterfaceMembers.iterate: $interfaceAllTargetsPerSecond isSubtypeOf.expense-histogram: $expenseHistogram isSubtypeOf.compression-ratio: ${classHierarchy.getCompressionRatio()} asInstanceOf.table-size: ${classHierarchy.getSuperTypeHashTableSize()} depth.histogram: ${depthHistogram.skip(1).join(' ')} depth.average: $averageDepth depth.median: $medianDepth depth.total: $totalDepth overrides.total: $numberOfOverridePairs overrides.iterate: ${overrideTime ~/ 1000} ms ($overridePairsPerSecond) '''); } String perSecond(int microseconds, int trials) { double millionsPerSecond = trials / microseconds; return '${millionsPerSecond.toStringAsFixed(1)} M/s'; } List getHistogramOf(Iterable values) { List result = []; for (int value in values) { while (result.length <= value) { result.add(0); } ++result[value]; } return result; } double average(Iterable values) { double sum = 0.0; int length = 0; for (num x in values) { sum += x; ++length; } return length == 0 ? 0.0 : sum / length; } double median(Iterable values) { List list = values.toList(growable: false)..sort(); if (list.isEmpty) return 0.0; int mid = list.length ~/ 2; return list.length % 2 == 0 ? ((list[mid] + list[mid + 1]) / 2) : list[mid].toDouble(); } num sum(Iterable values) { num result = 0; for (var x in values) { result += x; } return result; }