#!/usr/bin/env python3
# SPDX-License-Identifier: BSD-3-Clause-Clear
# Copyright (c) 2019, The Numerical Algorithms Group, Ltd. All rights reserved.
"""Hybrid metrics
"""
import numpy
import pandas
from .metricset import MetricSet, Metric
__all__ = ['MPI_OpenMP_Metrics', 'MPI_OpenMP_Multiplicative_Metrics']
[docs]class MPI_OpenMP_Metrics(MetricSet):
"""Proposed Hybrid MPI+OpenMP Metrics.
"""
_metric_list = [
Metric("Global Efficiency", 0),
Metric("Parallel Efficiency", 1),
Metric("Process Level Efficiency", 2),
Metric("MPI Load balance", 3, "Load balance"),
Metric("MPI Communication Efficiency", 3),
Metric("MPI Transfer Efficiency", 4),
Metric("MPI Serialisation Efficiency", 4),
Metric("Thread Level Efficiency", 2),
Metric("OpenMP Region Efficiency", 3, "OpenMP Region Efficiency"),
Metric("Serial Region Efficiency", 3),
Metric("Computational Scaling", 1),
Metric("Instruction Scaling", 2),
Metric("IPC Scaling", 2, "IPC Scaling"),
]
def _calculate_metrics(self, ref_key=None, sort_keys=True):
if not ref_key:
ref_key = min(self._stats_dict.keys())
metrics_by_key = {}
if sort_keys:
keys = sorted(self._stats_dict.keys())
else:
key = self._stats_dict.keys()
for key in keys:
metadata = self._stats_dict[key].metadata
stats = self._stats_dict[key].stats
try:
nthreads = metadata.application_layout.rank_threads[0][0]
metrics = {"Number of Processes": sum(metadata.procs_per_node)}
metrics["OpenMP Region Efficiency"] = 1 - (
(
(
stats["OpenMP Total Runtime"].loc[:, 1]
- stats["OpenMP Useful Computation"].mean(level="rank")
).mean()
)
/ stats["Total Runtime"].max()
)
metrics["Serial Region Efficiency"] = 1 - (
stats["Serial Useful Computation"].loc[:, 1].mean()
/ stats["Total Runtime"].max()
* (1 - 1 / nthreads)
)
metrics["Thread Level Efficiency"] = 1 - (
(
stats["OpenMP Total Runtime"].loc[:, 1].mean()
- stats["OpenMP Useful Computation"].mean()
+ stats["Serial Useful Computation"].loc[:, 1].mean()
* (1 - 1 / nthreads)
)
/ stats["Total Runtime"].max()
)
metrics["MPI Communication Efficiency"] = (
stats["Total Non-MPI Runtime"].loc[:, 1].max()
/ stats["Total Runtime"].max()
)
try:
metrics["MPI Serialisation Efficiency"] = (
stats["Total Non-MPI Runtime"].loc[:, 1].max()
/ stats["Ideal Runtime"].loc[:, 1].max()
)
except KeyError:
metrics["MPI Serialisation Efficiency"] = numpy.nan
try:
metrics["MPI Transfer Efficiency"] = (
stats["Ideal Runtime"].loc[:, 1].max()
/ stats["Total Runtime"].loc[:, 1].max()
)
except KeyError:
metrics["MPI Transfer Efficiency"] = numpy.nan
metrics["MPI Load balance"] = 1 - (
(
stats["Total Non-MPI Runtime"].loc[:, 1].max()
- stats["Total Non-MPI Runtime"].loc[:, 1].mean()
)
/ stats["Total Runtime"].max()
)
metrics["Process Level Efficiency"] = (
stats["Total Non-MPI Runtime"].loc[:, 1].mean()
) / stats["Total Runtime"].max()
metrics["Parallel Efficiency"] = (
stats["Total Useful Computation"].mean()
/ stats["Total Runtime"].max() # avg all threads to include Amdahl
)
metrics["IPC Scaling"] = (
stats["IPC"].mean() / self._stats_dict[ref_key].stats["IPC"].mean()
)
metrics["Instruction Scaling"] = (
self._stats_dict[ref_key].stats["Useful Instructions"].sum()
/ stats["Useful Instructions"].sum()
)
metrics["Frequency Scaling"] = (
stats["Frequency"].mean()
/ self._stats_dict[ref_key].stats["Frequency"].mean()
)
metrics["Computational Scaling"] = (
self._stats_dict[ref_key].stats["Total Useful Computation"].sum()
/ stats["Total Useful Computation"].sum()
)
metrics["Global Efficiency"] = (
metrics["Computational Scaling"] * metrics["Parallel Efficiency"]
)
metrics["Speedup"] = (
self._stats_dict[ref_key].stats["Total Runtime"].max()
/ stats["Total Runtime"].max()
)
metrics["Runtime"] = stats["Total Runtime"].max()
except KeyError as err:
raise ValueError(
"No '{}' statistic. (Wrong analysis type?)" "".format(err.args[0])
)
metrics_by_key[key] = metrics
self._metric_data = pandas.DataFrame(metrics_by_key).T
[docs]class MPI_OpenMP_Multiplicative_Metrics(MetricSet):
"""Proposed Hybrid MPI+OpenMP Metrics (multiplicative version).
"""
_metric_list = [
Metric("Global Efficiency", 0),
Metric("Parallel Efficiency", 1),
Metric("Process Level Efficiency", 2),
Metric("MPI Load balance", 3, "Load balance"),
Metric("MPI Communication Efficiency", 3),
Metric("MPI Transfer Efficiency", 4),
Metric("MPI Serialisation Efficiency", 4),
Metric("Thread Level Efficiency", 2),
Metric("OpenMP Region Efficiency", 3, "OpenMP Region Efficiency"),
Metric("Serial Region Efficiency", 3),
Metric("Computational Scaling", 1),
Metric("Instruction Scaling", 2),
Metric("IPC Scaling", 2, "IPC Scaling"),
]
def _calculate_metrics(self, ref_key=None, sort_keys=True):
if not ref_key:
ref_key = min(self._stats_dict.keys())
metrics_by_key = {}
if sort_keys:
keys = sorted(self._stats_dict.keys())
else:
key = self._stats_dict.keys()
for key in keys:
metadata = self._stats_dict[key].metadata
stats = self._stats_dict[key].stats
try:
metrics = {"Number of Processes": sum(metadata.procs_per_node)}
metrics["OpenMP Region Efficiency"] = (
stats["OpenMP Useful Computation"].mean()
+ stats["Serial Useful Computation"].loc[:, 1].mean()
) / (
stats["OpenMP Total Runtime"].loc[:, 1].mean()
+ stats["Serial Useful Computation"].loc[:, 1].mean()
)
metrics["Serial Region Efficiency"] = (
stats["Total Useful Computation"].mean()
) / (
stats["OpenMP Useful Computation"].mean()
+ stats["Serial Useful Computation"].loc[:, 1].mean()
)
metrics["Thread Level Efficiency"] = (
stats["Total Useful Computation"].mean()
) / (
stats["OpenMP Total Runtime"].loc[:, 1].mean()
+ stats["Serial Useful Computation"].loc[:, 1].mean()
)
metrics["MPI Communication Efficiency"] = (
stats["Total Non-MPI Runtime"].loc[:, 1].max()
/ stats["Total Runtime"].max()
)
try:
metrics["MPI Serialisation Efficiency"] = (
stats["Total Non-MPI Runtime"].loc[:, 1].max()
/ stats["Ideal Runtime"].loc[:, 1].max()
)
except KeyError:
metrics["MPI Serialisation Efficiency"] = numpy.nan
try:
metrics["MPI Transfer Efficiency"] = (
stats["Ideal Runtime"].loc[:, 1].max()
/ stats["Total Runtime"].loc[:, 1].max()
)
except KeyError:
metrics["MPI Transfer Efficiency"] = numpy.nan
metrics["MPI Load balance"] = (
stats["OpenMP Total Runtime"].loc[:, 1].mean()
+ stats["Serial Useful Computation"].loc[:, 1].mean()
) / stats["Total Non-MPI Runtime"].loc[:, 1].max()
metrics["Process Level Efficiency"] = (
stats["Total Non-MPI Runtime"].loc[:, 1].mean()
) / stats["Total Runtime"].max()
metrics["Parallel Efficiency"] = (
stats["Total Useful Computation"].mean()
/ stats["Total Runtime"].max() # avg all threads to include Amdahl
)
metrics["IPC Scaling"] = (
stats["IPC"].mean() / self._stats_dict[ref_key].stats["IPC"].mean()
)
metrics["Instruction Scaling"] = (
self._stats_dict[ref_key].stats["Useful Instructions"].sum()
/ stats["Useful Instructions"].sum()
)
metrics["Frequency Scaling"] = (
stats["Frequency"].mean()
/ self._stats_dict[ref_key].stats["Frequency"].mean()
)
metrics["Computational Scaling"] = (
self._stats_dict[ref_key].stats["Total Useful Computation"].sum()
/ stats["Total Useful Computation"].sum()
)
metrics["Global Efficiency"] = (
metrics["Computational Scaling"] * metrics["Parallel Efficiency"]
)
metrics["Speedup"] = (
self._stats_dict[ref_key].stats["Total Runtime"].max()
/ stats["Total Runtime"].max()
)
metrics["Runtime"] = stats["Total Runtime"].max()
except KeyError as err:
raise ValueError(
"No '{}' statistic. (Wrong analysis type?)" "".format(err.args[0])
)
metrics_by_key[key] = metrics
self._metric_data = pandas.DataFrame(metrics_by_key).T