Вывод графа вызовов Perf выглядит не так, как я ожидал бы от тестовой программы с циклом задержки, который должен занимаLinux

Ответить
Anonymous
 Вывод графа вызовов Perf выглядит не так, как я ожидал бы от тестовой программы с циклом задержки, который должен занима

Сообщение Anonymous »

Я экспериментирую с записью perf --control для профилирования выбранных разделов программы.
Вот программа на Rust, которая использует perf для профилирования вызова функции Waste_time():

Код: Выделить всё

use libc;
use log::info;
use simple_logger::SimpleLogger;
use std::ffi::CString;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader, Read, Write};
use std::process::Command;

const CTRL_FIFO_PATH: &str = "/tmp/ctrlfifo";
const ACK_FIFO_PATH: &str = "/tmp/ackfifo";

#[inline(never)]
fn waste_time() {
info!("start wasting time...");
let mut res = 0;
for i in 0..500000000 {
res += i % 7;
res = res % 100;
}
info!("finished wasting time res={res}");
}

fn read_ack(ack_reader: &mut BufReader) {
let mut ack_buf = Vec::new();
ack_reader.read_until(0, &mut ack_buf).unwrap();
assert_eq!(ack_buf, [97, 99, 107, 10, 0]); // "ack\n\0"
}

fn main() {
SimpleLogger::new().init().unwrap();
info!("program starts");

// make the control fifo
std::fs::remove_file(CTRL_FIFO_PATH).ok();
let fifo_path_c = CString::new(CTRL_FIFO_PATH).unwrap();
if unsafe { libc::mkfifo(fifo_path_c.as_ptr(), 0o777) } != 0 {
panic!("mkfifo failed");
}

let mut ctrl_file = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(CTRL_FIFO_PATH)
.unwrap();

// make the ack fifo
std::fs::remove_file(ACK_FIFO_PATH).ok();
let ack_path_c = CString::new(ACK_FIFO_PATH).unwrap();
if unsafe { libc::mkfifo(ack_path_c.as_ptr(), 0o777) } != 0 {
panic!("mkfifo failed");
}

let ack_file = OpenOptions::new()
.read(true)
.write(true)
.create(false)
.open(ACK_FIFO_PATH)
.unwrap();
let mut ack_reader = BufReader::new(ack_file);

// Spawn perf.
let control_arg = format!("fifo:{CTRL_FIFO_PATH},{ACK_FIFO_PATH}");
let tid = format!("{}", unsafe { libc::gettid() });
let mut cmd = Command::new("perf");
cmd.args([
"record",
"-F",
"999",
"--call-graph",
"dwarf",
"-D",
"-1",
"--tid",
&tid,
"--control",
&control_arg,
]);
let mut jh = cmd.spawn().unwrap();

write!(ctrl_file, "enable").unwrap();
read_ack(&mut ack_reader);
info!("perf turned on");

let ts = std::time::Instant::now();
use std::hint::black_box;
black_box(waste_time());
info!("wasted time for {}ms", ts.elapsed().as_millis());

write!(ctrl_file, "disable").unwrap();
read_ack(&mut ack_reader);
info!("perf turned off");

write!(ctrl_file, "stop").unwrap();
read_ack(&mut ack_reader);

jh.wait().unwrap();
info!("program ends");
}
Я создаю эту программу, используя профиль отладки Rust, который содержит информацию DWARF (проверенную с помощью dwarfdump), поэтому программа создает запись производительности с --call-graph=dwarf.

Код: Выделить всё

waste_time()
доминирует во время выполнения, пока включена производительность, что можно проверить по выводу программы (с метками времени):

Код: Выделить всё

2025-10-31T14:53:47.703Z INFO  [perfctl] program starts
Events disabled
Events enabled
2025-10-31T14:53:48.171Z INFO  [perfctl] perf turned on
2025-10-31T14:53:48.171Z INFO  [perfctl] start wasting time...
2025-10-31T14:53:53.278Z INFO  [perfctl] finished wasting time res=94
2025-10-31T14:53:53.278Z INFO  [perfctl] wasted time for 5106ms
Events disabled
2025-10-31T14:53:53.291Z INFO  [perfctl] perf turned off
[ perf record: Woken up 167 times to write data ]
[ perf record: Captured and wrote 41.175 MB perf.data (5108 samples) ]
2025-10-31T14:53:54.087Z INFO  [perfctl] program ends
Для начала я хочу увидеть граф вызовов, в котором, когда вы разворачиваете запись в отчете о производительности TUI, вы видите вызываемых. Я считаю, что для этого я делаю perf report --call-graph=graph,0.5,0,callee.
После этого и расширения записи для Waste_time() я вижу:
Изображение

Я не понимаю вывод.
  • Почему метод Waste_time() встречается только в ~85 % образцов? Оно должно быть очень близко к 100%.
  • Что показывают подзаписи в Waste_time()? Показывает ли это, что отходов_время() вызывает main?
На случай, если у меня дерево перевернуто, я попробовал представление на основе вызывающего абонента с отчетом о производительности --call-graph=graph,0.5,0,caller, но я тоже не уверен, что это показывает:
Изображение

Затем я загрузил тот же профиль в профилировщик Firefox и снова получил другой профиль, в котором 66% выборок относятся к Waste_time():
Изображение

Затем я повторил эксперимент, используя размотку на основе указателя кадра (убедившись, что грузу нужно принудительно заставить FP, и запустил perf с помощью --call-graph=fp). Это дает:
Изображение

По крайней мере, Waste_time() соответствует почти 100% времени выполнения, но я до сих пор не понимаю здесь дочерние записи. Почему метод main() является дочерним элементом метода Waste_time()?
Может ли кто-нибудь помочь мне разобраться в этом? Надеюсь, это просто путаница с моей стороны.

Подробнее здесь: https://stackoverflow.com/questions/798 ... m-with-a-d
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Linux»