Profiling calculation¶
It can be interesting to know where a calculation is spending its time. To this end, featomic includes self-profiling code that can record and display which part of the calculation takes time, and which function called long-running functions. All the example should output something similar to the table below.
╔════╦══════════════════════════════╦════════════╦═══════════╦══════════╦═════════════╗
║ id ║ span name ║ call count ║ called by ║ total ║ mean ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 2 ║ Full calculation ║ 1 ║ — ║ 660.58ms ║ 660.58ms ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 3 ║ SoapPowerSpectrum::compute ║ 1 ║ 2 ║ 584.02ms ║ 584.02ms ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 1 ║ Calculator::prepare ║ 2 ║ 3, 2 ║ 148.15ms ║ 74.08ms ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 0 ║ NeighborsList ║ 20 ║ 1 ║ 20.82ms ║ 1.04ms ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 5 ║ SphericalExpansion::compute ║ 1 ║ 3 ║ 196.38ms ║ 196.38ms ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 4 ║ GtoRadialIntegral::compute ║ 74448 ║ 5 ║ 117.04ms ║ 1.57µs ║
╠════╬══════════════════════════════╬════════════╬═══════════╬══════════╬═════════════╣
║ 6 ║ SphericalHarmonics::compute ║ 74448 ║ 5 ║ 9.95ms ║ 133.00ns ⚠️ ║
╚════╩══════════════════════════════╩════════════╩═══════════╩══════════╩═════════════╝
In this table, the first columns assign a unique numeric identifier to each section of the code. The second one displays the name of the section. Then come the number of time this section of the code have been executed, which other function/section called the current one, and finally the total and mean time spent in this function.
The ⚠️ symbol is added when the mean cost of the function is close to the profiling overhead (30 to 80ns per function call), and thus the measurement might not be very reliable.
Some of the most important sections are:
Calculator::prepare
: building the list of samples/properties that will be in the descriptorXXX::compute
: building blocks for the overall calculationNeighborsList
: construction of the list of neighbors
You can obtain a dataset for profiling from our website
.
import chemfiles
import featomic
from featomic import SoapPowerSpectrum
def compute_soap(path):
"""Compute SOAP power spectrum.
This is the same code as the 'compute-soap' example
"""
with chemfiles.Trajectory(path) as trajectory:
frames = [f for f in trajectory]
HYPER_PARAMETERS = {
"cutoff": {
"radius": 5.0,
"smoothing": {"type": "ShiftedCosine", "width": 0.5},
},
"density": {
"type": "Gaussian",
"width": 0.3,
},
"basis": {
"type": "TensorProduct",
"max_angular": 4,
"radial": {"type": "Gto", "max_radial": 6},
},
}
calculator = SoapPowerSpectrum(**HYPER_PARAMETERS)
descriptor = calculator.compute(frames, gradients=["positions"])
descriptor = descriptor.keys_to_samples("center_type")
descriptor = descriptor.keys_to_properties(["neighbor_1_type", "neighbor_2_type"])
return descriptor
Run the calculation with profiling enabled.
with featomic.Profiler() as profiler:
descriptor = compute_soap("dataset.xyz")
Display the recorded profiling data as table.
print(profiler.as_short_table())
╔════╦══════════════════════════════════════════════╦════════════╦═══════════╦══════════╦══════════════╗
║ id ║ span name ║ call count ║ called by ║ total ║ mean ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 4 ║ SoapPowerSpectrum::compute ║ 1 ║ — ║ 755.87ms ║ 755.87ms ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 5 ║ SphericalExpansion::compute ║ 1 ║ 4 ║ 305.89ms ║ 305.89ms ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 1 ║ SoapRadialIntegralSpline::with_accuracy ║ 30 ║ 5 ║ 134.86ms ║ 4.50ms ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 0 ║ GtoRadialIntegral::compute ║ 19230 ║ 1 ║ 126.93ms ║ 6.60µs ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 3 ║ Calculator::prepare ║ 2 ║ 4 ║ 55.33ms ║ 27.67ms ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 2 ║ NeighborsList ║ 40 ║ 3 ║ 12.05ms ║ 301.26µs ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 6 ║ SplinedRadialIntegral::compute ║ 176531 ║ 5 ║ 44.87ms ║ 254.00ns ⚠️ ║
╠════╬══════════════════════════════════════════════╬════════════╬═══════════╬══════════╬══════════════╣
║ 7 ║ SphericalHarmonics::compute ║ 35307 ║ 5 ║ 21.27ms ║ 602.00ns ⚠️ ║
╚════╩══════════════════════════════════════════════╩════════════╩═══════════╩══════════╩══════════════╝
You can also save this data as json for future usage
print(profiler.as_json())
{"timings":{"featomic::calculators::soap::radial_integral::gto::GtoRadialIntegral::compute":{"id":0,"elapsed":"126.934515ms","called":19230},"featomic::calculators::soap::radial_integral::spline::SoapRadialIntegralSpline::with_accuracy":{"id":1,"elapsed":"134.861249ms","called":30},"featomic::systems::neighbors::NeighborsList":{"id":2,"elapsed":"12.050559ms","called":40},"featomic::calculator::Calculator::prepare":{"id":3,"elapsed":"55.333432ms","called":2},"featomic::calculators::soap::power_spectrum::SoapPowerSpectrum::compute":{"id":4,"elapsed":"755.865221ms","called":1},"featomic::calculators::soap::spherical_expansion::SphericalExpansion::compute":{"id":5,"elapsed":"305.889944ms","called":1},"featomic::calculators::soap::radial_integral::spline::SplinedRadialIntegral::compute":{"id":6,"elapsed":"44.873864ms","called":176531},"featomic::math::spherical_harmonics::SphericalHarmonics::compute":{"id":7,"elapsed":"21.267505ms","called":35307}},"calls":[{"caller":0,"callee":1,"count":19230},{"caller":2,"callee":3,"count":40},{"caller":3,"callee":4,"count":1},{"caller":1,"callee":5,"count":5},{"caller":6,"callee":5,"count":1},{"caller":7,"callee":5,"count":1},{"caller":5,"callee":4,"count":1}]}
use metatensor::{TensorMap, Labels};
use featomic::{Calculator, System, CalculationOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).expect("expected a command line argument");
// enable collection of profiling data
time_graph::enable_data_collection(true);
// clear any existing collected data
time_graph::clear_collected_data();
// run the calculation
let _descriptor = compute_soap(&path)?;
// get the call graph and display it
let graph = time_graph::get_full_graph();
// (this requires the "table" feature for the time_graph crate)
println!("{}", graph.as_short_table());
// also available for saving profiling data to the disk & future analysis
// (this requires the "json" feature for the time_graph crate)
println!("{}", graph.as_json());
Ok(())
}
/// Compute SOAP power spectrum, this is the same code as the 'compute-soap'
/// example
fn compute_soap(path: &str) -> Result<TensorMap, Box<dyn std::error::Error>> {
let systems = featomic::systems::read_from_file(path)?;
let mut systems = systems.into_iter()
.map(|s| Box::new(s) as Box<dyn System>)
.collect::<Vec<_>>();
let parameters = r#"{
"cutoff": {
"radius": 5.0,
"smoothing": {
"type": "ShiftedCosine",
"width": 0.5
}
},
"density": {
"type": "Gaussian",
"width": 0.3
},
"basis": {
"type": "TensorProduct",
"max_angular": 4,
"radial": {"type": "Gto", "max_radial": 6}
}
}"#;
let descriptor = time_graph::spanned!("Full calculation", {
let mut calculator = Calculator::new("soap_power_spectrum", parameters.to_owned())?;
let options = CalculationOptions {
gradients: &["positions"],
..Default::default()
};
calculator.compute(&mut systems, options)?
});
let keys_to_move = Labels::empty(vec!["center_type"]);
let descriptor = descriptor.keys_to_samples(&keys_to_move, /* sort_samples */ true)?;
let keys_to_move = Labels::empty(vec!["neighbor_1_type", "neighbor_2_type"]);
let descriptor = descriptor.keys_to_properties(&keys_to_move, /* sort_samples */ true)?;
Ok(descriptor)
}
#include <iostream>
#include <featomic.hpp>
/// Compute SOAP power spectrum, this is the same code as the 'compute-soap'
/// example
static metatensor::TensorMap compute_soap(const std::string& path);
int main(int argc, char* argv[]) {
if (argc < 2) {
std::cout << "error: expected a command line argument" << std::endl;
return 1;
}
// enable collection of profiling data
featomic::Profiler::enable(true);
// clear any existing collected data
featomic::Profiler::clear();
auto descriptor = compute_soap(argv[1]);
// Get the profiling data as a table to display it directly
std::cout << featomic::Profiler::get("short_table") << std::endl;
// Or save this data as json for future usage
std::cout << featomic::Profiler::get("json") << std::endl;
return 0;
}
metatensor::TensorMap compute_soap(const std::string& path) {
auto systems = featomic::BasicSystems(path);
const char* parameters = R"({
"cutoff": {
"radius": 5.0,
"smoothing": {"type": "ShiftedCosine", "width": 0.5}
},
"density": {
"type": "Gaussian",
"width": 0.3
},
"basis": {
"type": "TensorProduct",
"max_angular": 6,
"radial": {"type": "Gto", "max_radial": 6}
}
})";
auto calculator = featomic::Calculator("soap_power_spectrum", parameters);
auto descriptor = calculator.compute(systems);
descriptor.keys_to_samples("center_type");
descriptor.keys_to_properties(std::vector<std::string>{"neighbor_1_type", "neighbor_2_type"});
return descriptor;
}
#include <stdbool.h>
#include <stdio.h>
#include <metatensor.h>
#include <featomic.h>
/// Compute SOAP power spectrum, this is the same code as the 'compute-soap'
/// example
static mts_tensormap_t* compute_soap(const char* path);
int main(int argc, char* argv[]) {
featomic_status_t status = FEATOMIC_SUCCESS;
char* buffer = NULL;
size_t buffer_size = 8192;
bool got_error = true;
if (argc < 2) {
printf("error: expected a command line argument");
goto cleanup;
}
// enable collection of profiling data
status = featomic_profiling_enable(true);
if (status != FEATOMIC_SUCCESS) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
// clear any existing collected data
status = featomic_profiling_clear();
if (status != FEATOMIC_SUCCESS) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
mts_tensormap_t* descriptor = compute_soap(argv[1]);
if (descriptor == NULL) {
goto cleanup;
}
buffer = calloc(buffer_size, sizeof(char));
if (buffer == NULL) {
printf("Error: failed to allocate memory\n");
goto cleanup;
}
// Get the profiling data as a table to display it directly
status = featomic_profiling_get("short_table", buffer, buffer_size);
if (status != FEATOMIC_SUCCESS) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
printf("%s\n", buffer);
// Or save this data as json for future usage
status = featomic_profiling_get("json", buffer, buffer_size);
if (status != FEATOMIC_SUCCESS) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
printf("%s\n", buffer);
got_error = false;
cleanup:
free(buffer);
mts_tensormap_free(descriptor);
if (got_error) {
return 1;
} else {
return 0;
}
}
static mts_tensormap_t* move_keys_to_samples(mts_tensormap_t* descriptor, const char* keys_to_move[], size_t keys_to_move_len);
static mts_tensormap_t* move_keys_to_properties(mts_tensormap_t* descriptor, const char* keys_to_move[], size_t keys_to_move_len);
// this is the same function as in the compute-soap.c example
mts_tensormap_t* compute_soap(const char* path) {
int status = FEATOMIC_SUCCESS;
featomic_calculator_t* calculator = NULL;
featomic_system_t* systems = NULL;
uintptr_t n_systems = 0;
const double* values = NULL;
const uintptr_t* shape = NULL;
uintptr_t shape_count = 0;
bool got_error = true;
const char* keys_to_samples[] = {"center_type"};
const char* keys_to_properties[] = {"neighbor_1_type", "neighbor_2_type"};
// use the default set of options, computing all samples and all features
featomic_calculation_options_t options = {0};
const char* gradients_list[] = {"positions"};
options.gradients = gradients_list;
options.gradients_count = 1;
mts_tensormap_t* descriptor = NULL;
const mts_block_t* block = NULL;
mts_array_t data = {0};
mts_labels_t keys_to_move = {0};
const char* parameters = "{\n"
"\"cutoff\": {\n"
" \"radius\": 5.0,\n"
" \"smoothing\": {\"type\": \"ShiftedCosine\", \"width\": 0.5}\n"
"},\n"
"\"density\": {\n"
" \"type\": \"Gaussian\",\n"
" \"width\": 0.3\n"
"},\n"
"\"basis\": {\n"
" \"type\": \"TensorProduct\",\n"
" \"max_angular\": 6,\n"
" \"radial\": {\"type\": \"Gto\", \"max_radial\": 6}\n"
"}\n"
"}";
status = featomic_basic_systems_read(path, &systems, &n_systems);
if (status != FEATOMIC_SUCCESS) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
calculator = featomic_calculator("soap_power_spectrum", parameters);
if (calculator == NULL) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
status = featomic_calculator_compute(
calculator, &descriptor, systems, n_systems, options
);
if (status != FEATOMIC_SUCCESS) {
printf("Error: %s\n", featomic_last_error());
goto cleanup;
}
descriptor = move_keys_to_samples(descriptor, keys_to_samples, 1);
if (descriptor == NULL) {
printf("Error: %s\n", mts_last_error());
goto cleanup;
}
descriptor = move_keys_to_properties(descriptor, keys_to_properties, 2);
if (descriptor == NULL) {
printf("Error: %s\n", mts_last_error());
goto cleanup;
}
cleanup:
featomic_calculator_free(calculator);
featomic_basic_systems_free(systems, n_systems);
return descriptor;
}
mts_tensormap_t* move_keys_to_samples(mts_tensormap_t* descriptor, const char* keys_to_move[], size_t keys_to_move_len) {
mts_labels_t keys = {0};
mts_tensormap_t* moved_descriptor = NULL;
keys.names = keys_to_move;
keys.size = keys_to_move_len;
keys.values = NULL;
keys.count = 0;
moved_descriptor = mts_tensormap_keys_to_samples(descriptor, keys, true);
mts_tensormap_free(descriptor);
return moved_descriptor;
}
mts_tensormap_t* move_keys_to_properties(mts_tensormap_t* descriptor, const char* keys_to_move[], size_t keys_to_move_len) {
mts_labels_t keys = {0};
mts_tensormap_t* moved_descriptor = NULL;
keys.names = keys_to_move;
keys.size = keys_to_move_len;
keys.values = NULL;
keys.count = 0;
moved_descriptor = mts_tensormap_keys_to_properties(descriptor, keys, true);
mts_tensormap_free(descriptor);
return moved_descriptor;
}