PapiTracerRunner.kt 9.11 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
        // We can analyze the output to get what we want.
77
78
        assert(!records.isEmpty())

79
80
        val g: MutableValueGraph<String, Int> = ValueGraphBuilder.directed().build()

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

84
85
86
87
            if (hasZeroInvokes ) {
                continue
            }

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

91
92
93
94
95
96
            // 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)
            }
97
98
99
100
101
        }

        return g
    }

102
103
    private var lastCountersUsed : List<PAPICounter>? = null

104
105
    fun runIterations(spec : PapiBenchmarkAnalyzer.RunSpec) : List<List<TraceRecord>> {

106
107
108
        Tracer.reset()
        Tracer.isTracing = true

109
110
111
112
113
114
115
116
117
118
        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()
119
        }
120

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

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

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

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

        val app = syntheticBenchmark
        app.reset()
137

Noric Couderc's avatar
Noric Couderc committed
138
        app.runBenchmark(blackhole)
139

Noric Couderc's avatar
Noric Couderc committed
140
141
        // We get the output from the run
        val stream = ByteArrayOutputStream()
142
143
        Tracer.printRecords(0, PrintStream(stream))

144
        return makeRecords(stream.toString())
Noric Couderc's avatar
Noric Couderc committed
145
146
    }

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

152
        val result = mutableListOf<PapiBenchmarkAnalyzer.BenchmarkRunData>()
153

154
        val iterations = runIterations(spec)
155

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

                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())
174
            }
175
176
177
178

            val report = PapiBenchmarkAnalyzer.BenchmarkRunData(spec.syntheticBenchmark, iterationIndex,
                counterSamples, getCyclesPerOpType(it) )
            result.add(report)
179
180
181
        }

        return result
182
    }
183

184
185
186
    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.
187
        assert(!iteration.isEmpty())
188
189
        assert(iteration.map { it.allocationSite  }.toSet().size == 1)

190
191
192
        val result = OperationType.values().associate {
            Pair(it, 0.0)
        }.toMutableMap()
193

194
        val siteToCycles = mutableMapOf<OperationType, Int>()
195

196
        val featureNumber = PAPICounter("PAPI_TOT_CYC").toHexa()
197

198
        // We need to total number of cycles for each alloc site / operation type
199
200
201
202
203
204
205
        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
206
207
208
209
                }
            }
        }

210
211
212
213
214
215
216
        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
217
                }
218
219
220
221
222
            }
        }

        return result
    }
223
224
225
226
}

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

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

236
237
        Tracer.stopAll()

238
        return syntheticBenchmark.trace.flatMap { method ->
239
            val methodOtherFormat = methodNameTable2.inverseBidiMap()[method]
240
241
242
            counters.map { counter ->
                TraceRecord(
                    "alloc1", counter.toHexa(), 1000,
243
                    syntheticBenchmark.dataStructureName,
244
245
246
                    methodOtherFormat ?: method
                )
            }
247
248
249
        }
    }
}