E-Graph는 Equivalence Relation (동등 관계)를 표현하는 자료구조 이다. 이는 어떠한 두 원소가 같다라는 것을 보일 때 효과적인 자료 구조이다. 최근에는 PL 학계에서도 많은 관심을 가져 EGRAPHS 워크숍도 꾸준히 진행되고 있다. 최근에는 이 E-graph를 활용하여 컴파일러 최적화를 더 똑똑하게 하려는 연구가 많이 진행되고 있으며 Wasm 엔진 Wasmtime의 JIT-Compiler인 Cranelift에서도 이런 E-graph를 활용하고 있다. 이 cranelift에 대한 설명은 다음 글에서 설명할 예정이다.
e-graph로 프로그램을 표현하게 되면 각 노드 (e-node)는 그 노드가 root node인 sub expression을 나타내게 된다.
a + 0
이라는 프로그램을 나타내게 되면 +
노드는 a + 0
을 나타내고, a
노드는 a
라는 프로그램을 나타내게 된다. 그리고 동등성을 표현하는 점선으로 묶인 e-class는 e-node들을 묶으며, 묶인 e-node가 동등하다라는 것을 표현한다.
만약 a + 0
과 a
라는 프로그램이 같다라는 것을 표현하고자 한다면 아래 그림의 오른쪽 그래프처럼 a
와 +
가 e-class로 묶여서 나타나게 된다. 이렇게 e-graph로 프로그램의 동등성을 나타내게 된다. 일반적인 컴파일러 최적화에서 최적화는 프로그램을 최적화된 프로그램으로 rewrite 하는 rewrite rule 형식으로 작성되기 때문에, e-graph를 컴파일러에 적용하려는 노력이 이어지고 있다.
E-graph는 위에서 설명한 것처럼, 최적화 전 프로그램을 e-graph로 표현하고, rewrite rule 형식으로 최적화를 모두 적용한 다음 가장 비용이 적은 프로그램을 선택하면 최적화가 되는 구조로 되어있다. 실제 예시로 보자 아래와 같은 최적화 전 프로그램이 있다고 하자.
int foo(int a) {
return (a*2)/2;
}
그러면 이 프로그램은 아래와 같이 표현될 것이다.
그렇다면 이제 컴파일러에 존재하는 최적화 중, 적용될 수 있는 것들을 하나씩 적용하면서 e-graph를 업데이트한다. 만약 (X * Y) / Z -> X * (Y / Z)
라는 재작성 규칙을 표현하면 아래와 같을 것이다.
만약 X / X -> 1
, X * 1 -> X
까지 모두 적용 하면 아래와 같게 될 것이다.
최종 E-graph를 해석해보면 (a * 2) / 2
, a * (2 / 2)
, a * 1
, a
라는 프로그램이 모두 같게 된다. 그 중 가장 비용이 적은 a라는 프로그램을 선택하면 최적화된 프로그램은 결국 아래처럼 return a;
가 된다.
int foo(int a) {
return a;
}
E-graph를 통해 컴파일러 최적화를 구현하면 장점은 다음과 같다.
아직 실제 복잡한 현대 컴파일러들에 바로 적용되기에는 해결해야 하는 문제들이 존재하지만 e-graphs를 최적화 일부에 적용하는 것은 지금도 가능하고, 많은 사람들이 시도하려는 문제이다.