최근 Ralf Jung의 블로그 포스트에서 더 많은 맥락 제공함 링크
보너스: Rust의 실행 의미론을 Rust의 방언으로 명확히 지정하려는 연구 그룹의 최근 발표도 추천함 유튜브
컴파일러 입장에서 포인터 에일리어싱 관련 타입 시스템의 강력한 보장을 활용해 강력한 최적화를 할 수 있다는 주장이 있는데, 실제론 얼마나 효과가 있는지 궁금함
Linus Torvalds는 C의 strict aliasing 규칙은 별 효용이 적고 오히려 문제를 일으킨다고 오랫동안 주장해왔음
그의 예시 글도 흥미로움
Rust가 본질적으로 C와 근본적으로 다른지 궁금한데, 개인적 경험으로 보면, 특히 unsafe가 연관되면 별 차이 없게 느껴짐
C의 strict aliasing 규칙은 정말 별로라고 생각함
Rust에서 제안하는 규칙은 훨씬 다르고, 컴파일러 입장에서도 더 유용하고, 프로그래머 입장에서도 부담이 덜하다고 봄
in-language opt-out로 raw pointer를 사용할 수 있고, 코드를 체크할 수 있는 툴도 있음
결국 모든 언어 설계와 마찬가지로 타협임
Rust가 이 최적화 영역의 새로운 밸런스를 찾은 것 같고, 그 판단의 결과는 시간이 말해줄 것임
Rust의 에일리어싱 규칙은 C와 아주 다름
C에서는 restrict 키워드가 거의 함수 인자에만 의미가 있고, 타입 기반 에일리어싱(type-based aliasing)은 실제로는 별로 안 쓰이거나 쓰기 불편함
Rust에서는 라이프타임, 가변성을 정교하게 표현하고 타입 자체와 무관하게 다양한 방식으로 메모리를 안전하게 다룰 수 있음
중첩되는 &mut 참조만 안 생기고, 여러 non-overlapping &mut로 쪼개는 식으로도 이용 가능한 점이 큼
실제로 이게 얼마나 성능에 영향을 주는지 더 광범위한 분석이 궁금함
간단히 에일리어싱 정보를 LLVM에 전달하는 부분을 compiler에서 전부 빼고 성능을 비교해보면 바로 알 수 있음
'noalias' 어노테이션이 런타임에서 약 5% 성능을 개선시켜준다는 주장도 있는데, 관련 코멘트가 있음(데이터가 오래되긴 했지만)
Linus가 컴파일러 관련해 말하는 것은 감안하는 것이 좋음
OS 커널과 컴파일러는 완전히 다른 영역임
요즘엔 에일리어스 분석이 정말 강력한 성능 개선의 핵심임
가장 큰 효과는 단순한 휴리스틱에서 나오고, 복잡한 에일리어스 쿼리는 그 자체로 활용도가 낮음
이론적으로 완벽한 에일리어스 분석이 성능을 얼마나 올릴지 실험해보고 싶은데, 일반 코드에서도 약 20% 정도가 한계일 것 같음
물론 아주 고급 최적화(예: 데이터 레이아웃 변환)는 에일리어스 분석이 없으면 시도조차 안 하게 되는 한계가 있음
C의 strict aliasing과 Rust의 aliasing은 개념이 다름
C는 타입 기반 분석(TBAA)이 핵심이고, Rust는 이를 의도적으로 채택하지 않았음
Tree Borrows 사양을 Nevin의 웹사이트에서 몇 년 전 읽었는데, 복잡한 문제도 우아하게 해결해 인상 깊었음
실제 경험상 Tree Borrows는 Stacked Borrows에서는 불가능한 합리적인 코드를 가능하게 함
Rust 표준 라이브러리의 예제 코드도 참고할 만함
Rust 또는 차세대 PL이 특성과 목적(컴파일 속도, 런타임 속도, 알고리즘 유연성 등)이 다른 여러 borrow checker 구현 중에서 선택할 수 있게 발전할 수 있을지 궁금함
Rust는 이미 borrow checker 구현 전환을 지원함
스코프 기반에서 비-렉시컬로 바뀌었고, Polonius라는 실험적 구현도 옵션임
새 구현이 준비되면 구버전은 굳이 남기지 않음
런타임 체크가 필요한 Rc, RefCell 등으로 더 유연하게 쓸 수도 있음
affine type(Rust 사용), linear type, 효과 시스템, dependent type, 형식적 증명 등 다양한 방법이 이미 존재함
각 방식마다 구현 비용, 성능, 개발 경험 등 특성이 달라짐
Rust 외에도 생산성 높은 자원 자동 관리와 타입 시스템의 조합이 추구되는 경향이 있음
실제로 필요한 것은 함수의 precondition을 정밀하게 명시하고 중간 조건 증명까지 할 수 있는 separation logic임
Rust의 접근법은 사람들이 실제로 원하는 보통의 불변조건을 시스템화해 강력한 최적화를 보장하는 것임
borrow checker의 결과가 false negative만 있지 false positive는 없는 게 맞는지 궁금함
만약 그렇다면 여러 구현을 쓰레드에서 병렬로 돌려서 빠른 승자 결과를 사용하는 것도 가능한지 의문임
여러 borrow checker 구현을 동시에 허용하면 생태계가 분열되기 쉽기 때문에 바람직하지 못함
논문에 나온 Rust 코드를 실제로 테스트해봤는데, 최신 안정화 컴파일러에서는 거부되지 않음을 확인함
예시 코드:
fn write(x: &mut i32) {*x = 10}
fn main() {
let x = &mut 0;
let y = x as *mut i32;
//write(x);
*x = 10;
unsafe {*y = 15 };
}
Stacked Borrows는 miri의 런타임 모델임
miri에서 위 코드를 실행해보면 *x = 10;에서 에러를 보고하지만, write(x);에서는 에러가 발생하지 않음
rustc는 타입시스템 관점에서 y가 *mut이기 때문에 두 버전 모두 거부할 이유가 없음
논문에서 unsafe 코드의 문제로 아래 예시를 들고 있음:
fn main() {
let mut x = 42;
let ptr = &mut x as *mut i32;
let val = unsafe { write_both(&mut *ptr, &mut *ptr) };
println!("{val}");
}
이것이 실제로 가능한지 의문임
동일 변수에 여러 mutable 참조를 포인터로 사용하는 것은 분명 UB인데, 내가 뭔가 오해하는 부분이 있는지 궁금함
이 연구의 핵심은 UB(정의되지 않은 동작)의 경계를 정확히 명시하는 것임
위 코드는 Rust 컴파일러가 받아들이더라도 규칙을 위반함
어떤 규칙이냐?
borrow checker에 통과하는 코드는 합법임
unsafe는 불법/UB도 표현 가능함
borrow checker 범위보다 넓지만 여전히 합법인 규칙 집합이 있음
이 연구는 그 경계를 엄밀히 지정하는 게 목적임
Stacked Borrows 논문은 더 단순하지만 실제 unsafe 코드에 한계를 가졌고, Tree Borrows는 더 넓은 안전 범위를 인정함
"여러 mutable 참조 포인터가 동시에 존재할 수 없다"는 점은 분명하지만, 그걸 정확히 어느 규칙이 위반한다고 명확히 언급된 부분이 없음
Tree Borrows는 바로 그러한 정의를 제안함
"코드가 이런 짓을 할 수 있다"는 표현은, 실제로 해당 코드를 만들고 실행하면 뭔가가 동작하지만, Tree Borrows 같은 정의 없이는 왜 이게 잘못됐다는 근거 마련이 어렵다는 뜻임
스스로도 Tree Borrows 같은 명확한 규칙 필요성을 이미 받아들이고 있는 것 같음
unsafe 코드는 저런 식으로 실제 가능하고, 그것이 UB라는 점임
예시: playground 링크
관련 맥락을 알고 싶으면 논문에 바로 다음 문단의 시작 부분이 의도를 잘 드러냄
Rust 컴파일러 개발자들이 에일리어싱 최적화를 원한다면, 위와 같은 반례 코드들을 배제할 방법이 필요함
역시 이게 바로 포인트임
여러 mutable 참조 불허 규칙을 지키기 어렵고, unsafe는 rust의 lifetime 시스템에서 보장된 것보다 훨씬 더 많은 걸 허용할 수 있음을 반영함
저자 중 한 명인 Neven Villani는 Fields medal 2010 수상자인 Cédric Villani의 아들임
사과는 멀리 떨어지지 않는다는 비유가 떠오름
그리고 "qualities도 tree에서 borrow 했다고 할 수 있음"이라고 재치있게 말해보고 싶었음
나는 아버지(Fields medal 수상자)와 사무실이 가까웠던 적도 있음
정계 진출 전 이야기임
Hacker News 의견
최근 Ralf Jung의 블로그 포스트에서 더 많은 맥락 제공함 링크
보너스: Rust의 실행 의미론을 Rust의 방언으로 명확히 지정하려는 연구 그룹의 최근 발표도 추천함 유튜브
컴파일러 입장에서 포인터 에일리어싱 관련 타입 시스템의 강력한 보장을 활용해 강력한 최적화를 할 수 있다는 주장이 있는데, 실제론 얼마나 효과가 있는지 궁금함
Linus Torvalds는 C의 strict aliasing 규칙은 별 효용이 적고 오히려 문제를 일으킨다고 오랫동안 주장해왔음
그의 예시 글도 흥미로움
Rust가 본질적으로 C와 근본적으로 다른지 궁금한데, 개인적 경험으로 보면, 특히 unsafe가 연관되면 별 차이 없게 느껴짐
C의 strict aliasing 규칙은 정말 별로라고 생각함
Rust에서 제안하는 규칙은 훨씬 다르고, 컴파일러 입장에서도 더 유용하고, 프로그래머 입장에서도 부담이 덜하다고 봄
in-language opt-out로 raw pointer를 사용할 수 있고, 코드를 체크할 수 있는 툴도 있음
결국 모든 언어 설계와 마찬가지로 타협임
Rust가 이 최적화 영역의 새로운 밸런스를 찾은 것 같고, 그 판단의 결과는 시간이 말해줄 것임
Rust의 에일리어싱 규칙은 C와 아주 다름
C에서는 restrict 키워드가 거의 함수 인자에만 의미가 있고, 타입 기반 에일리어싱(type-based aliasing)은 실제로는 별로 안 쓰이거나 쓰기 불편함
Rust에서는 라이프타임, 가변성을 정교하게 표현하고 타입 자체와 무관하게 다양한 방식으로 메모리를 안전하게 다룰 수 있음
중첩되는 &mut 참조만 안 생기고, 여러 non-overlapping &mut로 쪼개는 식으로도 이용 가능한 점이 큼
실제로 이게 얼마나 성능에 영향을 주는지 더 광범위한 분석이 궁금함
간단히 에일리어싱 정보를 LLVM에 전달하는 부분을 compiler에서 전부 빼고 성능을 비교해보면 바로 알 수 있음
'noalias' 어노테이션이 런타임에서 약 5% 성능을 개선시켜준다는 주장도 있는데, 관련 코멘트가 있음(데이터가 오래되긴 했지만)
Linus가 컴파일러 관련해 말하는 것은 감안하는 것이 좋음
OS 커널과 컴파일러는 완전히 다른 영역임
요즘엔 에일리어스 분석이 정말 강력한 성능 개선의 핵심임
가장 큰 효과는 단순한 휴리스틱에서 나오고, 복잡한 에일리어스 쿼리는 그 자체로 활용도가 낮음
이론적으로 완벽한 에일리어스 분석이 성능을 얼마나 올릴지 실험해보고 싶은데, 일반 코드에서도 약 20% 정도가 한계일 것 같음
물론 아주 고급 최적화(예: 데이터 레이아웃 변환)는 에일리어스 분석이 없으면 시도조차 안 하게 되는 한계가 있음
C의 strict aliasing과 Rust의 aliasing은 개념이 다름
C는 타입 기반 분석(TBAA)이 핵심이고, Rust는 이를 의도적으로 채택하지 않았음
Stacked Borrows 관련 과거 스레드들이 2020년, 2018년에 있었음
2020 스레드
2018 스레드
Tree Borrows 사양을 Nevin의 웹사이트에서 몇 년 전 읽었는데, 복잡한 문제도 우아하게 해결해 인상 깊었음
실제 경험상 Tree Borrows는 Stacked Borrows에서는 불가능한 합리적인 코드를 가능하게 함
Rust 표준 라이브러리의 예제 코드도 참고할 만함
Rust 또는 차세대 PL이 특성과 목적(컴파일 속도, 런타임 속도, 알고리즘 유연성 등)이 다른 여러 borrow checker 구현 중에서 선택할 수 있게 발전할 수 있을지 궁금함
Rust는 이미 borrow checker 구현 전환을 지원함
스코프 기반에서 비-렉시컬로 바뀌었고, Polonius라는 실험적 구현도 옵션임
새 구현이 준비되면 구버전은 굳이 남기지 않음
런타임 체크가 필요한 Rc, RefCell 등으로 더 유연하게 쓸 수도 있음
affine type(Rust 사용), linear type, 효과 시스템, dependent type, 형식적 증명 등 다양한 방법이 이미 존재함
각 방식마다 구현 비용, 성능, 개발 경험 등 특성이 달라짐
Rust 외에도 생산성 높은 자원 자동 관리와 타입 시스템의 조합이 추구되는 경향이 있음
실제로 필요한 것은 함수의 precondition을 정밀하게 명시하고 중간 조건 증명까지 할 수 있는 separation logic임
Rust의 접근법은 사람들이 실제로 원하는 보통의 불변조건을 시스템화해 강력한 최적화를 보장하는 것임
borrow checker의 결과가 false negative만 있지 false positive는 없는 게 맞는지 궁금함
만약 그렇다면 여러 구현을 쓰레드에서 병렬로 돌려서 빠른 승자 결과를 사용하는 것도 가능한지 의문임
여러 borrow checker 구현을 동시에 허용하면 생태계가 분열되기 쉽기 때문에 바람직하지 못함
논문에 나온 Rust 코드를 실제로 테스트해봤는데, 최신 안정화 컴파일러에서는 거부되지 않음을 확인함
예시 코드:
miri에서 위 코드를 실행해보면
*x = 10;에서 에러를 보고하지만,write(x);에서는 에러가 발생하지 않음rustc는 타입시스템 관점에서 y가 *mut이기 때문에 두 버전 모두 거부할 이유가 없음
논문에서 unsafe 코드의 문제로 아래 예시를 들고 있음:
이것이 실제로 가능한지 의문임
동일 변수에 여러 mutable 참조를 포인터로 사용하는 것은 분명 UB인데, 내가 뭔가 오해하는 부분이 있는지 궁금함
위 코드는 Rust 컴파일러가 받아들이더라도 규칙을 위반함
어떤 규칙이냐?
borrow checker에 통과하는 코드는 합법임
unsafe는 불법/UB도 표현 가능함
borrow checker 범위보다 넓지만 여전히 합법인 규칙 집합이 있음
이 연구는 그 경계를 엄밀히 지정하는 게 목적임
Stacked Borrows 논문은 더 단순하지만 실제 unsafe 코드에 한계를 가졌고, Tree Borrows는 더 넓은 안전 범위를 인정함
"여러 mutable 참조 포인터가 동시에 존재할 수 없다"는 점은 분명하지만, 그걸 정확히 어느 규칙이 위반한다고 명확히 언급된 부분이 없음
Tree Borrows는 바로 그러한 정의를 제안함
"코드가 이런 짓을 할 수 있다"는 표현은, 실제로 해당 코드를 만들고 실행하면 뭔가가 동작하지만, Tree Borrows 같은 정의 없이는 왜 이게 잘못됐다는 근거 마련이 어렵다는 뜻임
스스로도 Tree Borrows 같은 명확한 규칙 필요성을 이미 받아들이고 있는 것 같음
unsafe 코드는 저런 식으로 실제 가능하고, 그것이 UB라는 점임
예시: playground 링크
관련 맥락을 알고 싶으면 논문에 바로 다음 문단의 시작 부분이 의도를 잘 드러냄
역시 이게 바로 포인트임
여러 mutable 참조 불허 규칙을 지키기 어렵고, unsafe는 rust의 lifetime 시스템에서 보장된 것보다 훨씬 더 많은 걸 허용할 수 있음을 반영함
저자 중 한 명인 Neven Villani는 Fields medal 2010 수상자인 Cédric Villani의 아들임
사과는 멀리 떨어지지 않는다는 비유가 떠오름
그리고 "qualities도 tree에서 borrow 했다고 할 수 있음"이라고 재치있게 말해보고 싶었음
나는 아버지(Fields medal 수상자)와 사무실이 가까웠던 적도 있음
정계 진출 전 이야기임
이 모델 정말 훌륭함
내가 만드는 언어에도 구현해볼 예정임
데자뷰일 리가 없음
이 포스트를 2~3개월마다 계속 보는 느낌임