[Rust] 프로파일링: perf

통계에 따르면 Rust 사용자의 70%가 넘는 인원이 Rust에서 프로파일러를 사용하지 않는다고 한다.
통합된 프로파일러를 지원하지 않는 것도 있긴 한데, 왜일까?

아무튼 프로파일링은 성능 최적화에 사용할 수 있는 중요한 지표 중 하나이고, 가끔은 쓸 일이 있을 것이다.
여기서는 perf를 사용해서 리눅스 환경에서 프로파일링을 수행하는 방법을 다룬다.

어셈블리 수준의 프로파일링이고, 리눅스에서만 사용가능하다.
매우 low-한 수준에서 접근하기는 좋다.

범용 툴이라서 Rust가 아니라도 바이너리를 내뱉는 언어라면 다 사용가능하다.
단, 디버그 정보는 포함시키도록 해야한다.




설치

데비안 계열의 경우에는 아래와 같이 설치를 할 수 있다.

sudo apt-get install linux-perf

저게 없다면 linux-tools-common으로 설치가 되더라

sudo apt install linux-tools-common -y

그래서 이렇게 실행되면 된다.




사용법

테스트에 사용한 코드는 이렇다.

fn slow_function() -> i128 {
    let mut total: i128 = 0;

    for i in 1000..20000555 {
        for j in 1..10 {
            let some_value = i << j;
            total += some_value;
        }
    }

    return total;
}

fn fast_function() -> i128 {
    return 1000;
}

fn main() {
    let result = fast_function() + slow_function();
    println!("result: {}", result);
}

당연히 느려보이는 함수와 빠른 함수를 2개 구현해서 호출하는 간단한 코드다.

만약, 릴리즈 빌드에서도 테스트를 원한다면 디버그 정보를 활성화해준다.

[profile.release]
debug = 1

그리고 바이너리를 빌드하고

그걸 perf를 통해 실행, 기록이 되도록 한다.

sudo perf record -g 실행파일

CPU가 아니라 메모리를 측정하고 싶다면 perf 대신 perf mem을 사용해야 한다.

아무튼 그러면 perf report를 통해서 어느 함수나 어느 명령이 얼마나 시간을 소비했는지 확인을 할 수 있다.

sudo perf report

보면 slow_function이 45% 넘게 차지한 것을 볼 수 있다.
저 퍼센트는 CPU 실행시간 기준으로 매겨진다.

보통은 함수를 기준으로 보는게 편리하고 직관적이기 때문에, 소스코드에서도 함수 단위로 모듈화를 잘 해주면 프로파일링을 하기에도 용이하다.

안에 들어가면

해당 어셈블리 부분도 까볼 수 있다.
이런거 보면서 최적화포인트 찾으면 된다.



참조
https://rust-lang.github.io/packed_simd/perf-guide/prof/linux.html
https://nnethercote.github.io/perf-book/profiling.html
https://www.worthe-it.co.za/blog/2018-09-23-performance-tuning-in-rust.html