PapiTracerRunner.kt 9.01 KB
Newer Older
1
2
package se.lth.cs.papicounters

3
import com.google.common.graph.MutableValueGraph
4
import com.google.common.graph.ValueGraph
5
import com.google.common.graph.ValueGraphBuilder
6
import se.lth.cs.PapiRunner
7
import se.lth.cs.bcgen.BCBenchmarkPackage
8
import se.lth.cs.methodNameTable2
9
import se.lth.cs.smartmodules.tracer.Tracer
10
11
import se.lth.cs.timing.OperationType
import se.lth.cs.timing.OperationTypeTable
12
import se.lth.util.*
13
14
import java.io.ByteArrayOutputStream
import java.io.PrintStream
15
16
17
18
19
20
21

/**
 * This is a class that works exactly like the PAPI Runner, except it uses
 * instrumented collections instead of getting the counters from "outside".
 * The tracing collections allow to get more information (breakdown of the features per each method
 * instead of each run)
 */
22
open class PapiTracerRunner() : PapiRunner() {
23

24
    internal fun createTracerCollection(bench: BCBenchmarkPackage<*>): Any? {
25
26
27
        // Sometimes we used a tracer collection, we want to replace it anyway.
        val collectionName = bench.dataStructureSimpleName.removeSuffix("Tracer")
        val fakeLocation = bench.benchmarkIdentifier.removeSuffix("Tracer")
28
29

        return when (collectionName) {
30
31
32
33
34
35
36
37
38
            "ArrayList" -> ArrayListTracer<Int>(fakeLocation)
            "LinkedList" -> LinkedListTracer<Int>(fakeLocation)
            "Vector" -> VectorTracer<Int>(fakeLocation)
            "TreeSet" -> TreeSetTracer<Int>(fakeLocation)
            "HashSet" -> HashSetTracer<Int>(fakeLocation)
            "LinkedHashSet" -> LinkedHashSetTracer<Int>(fakeLocation)
            "TreeMap" -> TreeMapTracer<Int, Int>(fakeLocation)
            "HashMap" -> HashMapTracer<Int, Int>(fakeLocation)
            "LinkedHashMap" -> LinkedHashMapTracer<Int, Int>(fakeLocation)
39
40
41
42
            else -> null
        }
    }

43
44
45
46
47
48
49
50
    data class TraceRecord (val allocationSite : String,
                            val featureName : String,
                            val featureValue : Int,
                            val collection : String,
                            val method : String)


    fun makeRecords(text : String): List<TraceRecord> {
Noric Couderc's avatar
Noric Couderc committed
51
52
53
54
55
        val header = text.lines().first()
        val columnIndex = header.trim().split("\t").mapIndexed { index, column -> Pair(column, index) }
            .toMap()

        val cleanedLines = text.lines().drop(1).filter { !it.isEmpty() }
56
57
58

        return cleanedLines.map { line ->
            val splitted = line.split("\t")
Noric Couderc's avatar
Noric Couderc committed
59
60
61
62
63
            TraceRecord(splitted[columnIndex["location"]!!].intern(),
                splitted[columnIndex["feature"]!!].intern(),
                Integer.parseInt(splitted[columnIndex["feature_value"]!!]),
                splitted[columnIndex["collection"]!!].intern(),
                splitted[columnIndex["method"]!!].intern()
64
65
66
67
68
69
70
71
72
73
74
75
            )
        }
    }

    /**
     * Analyzes the output of running an iteration, line by line
     * returns a graph where for each edge :
     * - the source is an allocation site
     * - the target is a feature
     * - the label is the value of that feature
     */
    private fun analyzeOutput(records : List<TraceRecord>): ValueGraph<String, Int> {
76
77
78
        // We can analyze the output to get what we want.
        val g: MutableValueGraph<String, Int> = ValueGraphBuilder.directed().build()

79
80
        for (r in records) {
            val hasZeroInvokes = r.featureName == "invocations" && r.featureValue == 0
81

82
83
84
85
            if (hasZeroInvokes ) {
                continue
            }

86
            val value = g.edgeValueOrDefault(r.allocationSite, r.featureName, 0)?.plus(r.featureValue) // Number
Noric Couderc's avatar
Noric Couderc committed
87
            g.putEdgeValue(r.allocationSite, r.featureName, value!!)
88

89
90
91
92
93
94
            // We also relate methods to their type (insertionCycles, etc)
            val methodType = OperationTypeTable.getType(r.method)
            if (methodType.isPresent) {
                g.putEdgeValue("methodType", methodType.get().toString(), 0)
                g.putEdgeValue(r.method, methodType.get().toString(), 0)
            }
95
96
97
98
99
        }

        return g
    }

100
101
    private var lastCountersUsed : List<PAPICounter>? = null

102
103
    fun runIterations(spec : PapiBenchmarkAnalyzer.RunSpec) : List<List<TraceRecord>> {

104
105
106
        Tracer.reset()
        Tracer.isTracing = true

107
108
109
110
111
112
113
114
115
116
        if (spec.counters() != lastCountersUsed) {
            System.err.println("Counters changed: (${spec.counters()} /vs/ $lastCountersUsed), cleaning")
            val counters = spec.counters()
                .filter { ! it.isSpecial() } // These are active by default.
                .map { it.toPAPIConstant()!! }
            Tracer.clearCounters();
            for (c in counters) {
                Tracer.addCounter(c)
            }
            lastCountersUsed = spec.counters()
117
        }
118

119
        val rangeIterations = 0 until spec.numberRuns
Noric Couderc's avatar
Noric Couderc committed
120
121
122
123
124
125

        return rangeIterations.map {
            runIteration(spec.syntheticBenchmark)
        }
    }

126
    open fun runIteration(syntheticBenchmark: BCBenchmarkPackage<*>): List<TraceRecord> {
Noric Couderc's avatar
Noric Couderc committed
127
        Tracer.reset()
Noric Couderc's avatar
Noric Couderc committed
128
129
        Tracer.JPAPI = true

Noric Couderc's avatar
Noric Couderc committed
130
131
132
133
134
        val collection = createTracerCollection(syntheticBenchmark)
        syntheticBenchmark.datastructure = collection

        val app = syntheticBenchmark
        app.reset()
135

Noric Couderc's avatar
Noric Couderc committed
136
        app.runBenchmark(blackhole)
137

Noric Couderc's avatar
Noric Couderc committed
138
139
        // We get the output from the run
        val stream = ByteArrayOutputStream()
Noric Couderc's avatar
Noric Couderc committed
140
        Tracer.printRecords(1, PrintStream(stream))
141
        return makeRecords(stream.toString())
Noric Couderc's avatar
Noric Couderc committed
142
143
    }

144
    override fun runSpec(spec: PapiBenchmarkAnalyzer.RunSpec): List<PapiBenchmarkAnalyzer.BenchmarkRunData> {
145
146
147
        if (!spec.canBeSampled()) {
            throw RuntimeException("Specification cannot be sampled")
        }
Noric Couderc's avatar
Noric Couderc committed
148

149
        val result = mutableListOf<PapiBenchmarkAnalyzer.BenchmarkRunData>()
150

151
        val iterations = runIterations(spec)
152

153
        // Some post-processing
154
        var iterationIndex = 0
155
156
        iterations.forEach {
            // Analyze text for this iteration
Noric Couderc's avatar
Noric Couderc committed
157
            val g = analyzeOutput(it)
158
            // Get features present in this iteration.
159
            val counterSamples = spec.counters().map { counter ->
160
                val featureNumber = counter.toHexa()
161
162
163
164
165
166
167
168
169
170

                val allocationSites = g.predecessors(featureNumber)
                // There should be only one when we run the benchmarks
                assert(allocationSites.size == 1)

                val allocationSite = allocationSites.first()

                val value = g.edgeValue(allocationSite, featureNumber)

                PapiBenchmarkAnalyzer.BenchmarkRunSample(counter, value.get().toDouble())
171
            }
172
173
174
175

            val report = PapiBenchmarkAnalyzer.BenchmarkRunData(spec.syntheticBenchmark, iterationIndex,
                counterSamples, getCyclesPerOpType(it) )
            result.add(report)
176
177
178
        }

        return result
179
    }
180

181
182
183
184
185
    fun getCyclesPerOpType(iteration : List<TraceRecord>): Map<OperationType, Double> {
        // There should be only one allocation site for all records
        // Because we only record one iteration of one benchmark.
        assert(iteration.map { it.allocationSite  }.toSet().size == 1)

186
187
188
        val result = OperationType.values().associate {
            Pair(it, 0.0)
        }.toMutableMap()
189

190
        val siteToCycles = mutableMapOf<OperationType, Int>()
191

192
        val featureNumber = PAPICounter("PAPI_TOT_CYC").toHexa()
193

194
        // We need to total number of cycles for each alloc site / operation type
195
196
197
198
199
200
201
        for (record in iteration) {
            if (featureNumber == record.featureName) {
                val opType = OperationTypeTable.getType(record.method)
                if (opType.isPresent) {
                    val key = opType.get()
                    val cyclesForOpType = siteToCycles.getOrDefault(key, 0)
                    siteToCycles[key] = cyclesForOpType + record.featureValue
202
203
204
205
                }
            }
        }

206
207
208
209
210
211
212
        for (record in iteration) {
            if (record.featureName == featureNumber) {
                val opType = OperationTypeTable.getType(record.method)
                if (opType.isPresent) {
                    val key = opType.get()
                    val value = record.featureValue.toDouble() / siteToCycles.get(key)!!
                    result[key] = value
213
                }
214
215
216
217
218
            }
        }

        return result
    }
219
220
221
222
}

class MockupPapiTracerRunner() : PapiTracerRunner() {
    override fun runIteration(syntheticBenchmark: BCBenchmarkPackage<*>): List<TraceRecord> {
223
224
225
226
227
        val counters = listOf(PAPICounter("PAPI_TOT_CYC"),
            PAPICounter("PAPI_BR_MSP"),
            PAPICounter("PAPI_BR_CN"),
            PAPICounter("PAPI_L1_DCM"),
            PAPICounter("PAPI_L1_DCA"))
228
229
230
231

        val collection = createTracerCollection(syntheticBenchmark)
        syntheticBenchmark.datastructure = collection

232
        return syntheticBenchmark.trace.flatMap { method ->
233
            val methodOtherFormat = methodNameTable2.inverseBidiMap()[method]
234
235
236
            counters.map { counter ->
                TraceRecord(
                    "alloc1", counter.toHexa(), 1000,
237
                    syntheticBenchmark.dataStructureName,
238
239
240
                    methodOtherFormat ?: method
                )
            }
241
242
243
        }
    }
}