elsa in mac

Asahi Linux에서 Apple Silicon 성능을 100% 끌어내는 빌드(Build) 방법. 본문

Linux/Asahi Linux

Asahi Linux에서 Apple Silicon 성능을 100% 끌어내는 빌드(Build) 방법.

elsa in mac 2026. 3. 13. 20:19

 

본 포스트는 Fedora Asahi Linux를 기준으로 작성되어 있습니다. 

 

이번 포스트에서는 Linux 특히, Apple Silicon M1, M2 Mac에서 bare metal로 돌릴 수 있는 Fedora Asahi Linux에서 사용자가 직접 GUI, CLI, TUI 앱들의 소스 코드를 다운로드받아 빌드할 때 고려해야 할 최적화와 관련된 내용을 살펴봅니다.

Apple Silicon은 Arm processor

Apple Silicon SoC는 ARM 아키텍처를 기반으로 설계되어 있습니다. 좀 더 정확히 표현하자면, Arm으로부터 ARM 명령어 세트(ISA, Instruction Set Architecture)에 대한 라이선스를 얻고, 그 위에 애플이 직접 커스텀한 CPU, Apple이 설계한 GPU, 통합 메모리(Unified Memory) 그리고 NPU 등이 통합된 칩입니다.

Linux에서는 aarch64(ARM 64비트)라고 부르는데, 64비트 ARM 아키텍처를 부르는 공식 명칭입니다. 일반적으로 Linux에서 이미 빌드되어 공식 리포지토리(Repository)를 통해 배포되거나 GitHub에서 빌드하여 배포하는 앱들의 경우에는 Apple Silicon SoC를 위한 최적화 빌드가 되어 있지 않습니다.

ARM 칩은 Raspberry Pi부터 서버용 고성능 CPU까지 굉장히 다양합니다. 따라서 어느 특정 CPU에 최적화된 바이너리를 만들지 않고, 어떤 ARM 프로세서에서도 실행될 수 있는 최적화되지 않은 일반적인 ARM64 명령어만을 사용하여 빌드됩니다. 대부분의 일반 사용자들은 이미 빌드된 이러한 aarch64 바이너리를 사용하고 있으며, 특별히 불편함을 느끼지는 못합니다.

하지만 자신이 사용하는 Mac에서 보다 최적화된 Linux 앱을 직접 빌드하여 사용한다면, 보다 빠르고 리소스를 효율적으로 사용할 수 있게 만들 수 있습니다. 이번 포스트에서는 몇몇 대표적인 프로그래밍 언어와 빌드 환경에서 어떻게 Apple Silicon 칩에 최적화된 바이너리를 만들 수 있는지 그 방법들을 정리합니다.

* Rust project
Rust 언어로 작성된 프로젝트는 대부분 cargo로 빌드를 합니다. 엄밀히 말하면, Rust의 컴파일러는 rustc입니다. cargo는 빌드 도구이자 패키지 매니저입니다. 외부 의존성을 관리하고 빌드 순서(building order)를 정하여 빌드하며 내부적으로 rustc를 사용합니다. Rust 컴파일러인 rustc는 LLVM을 기반으로 하고 있습니다. 이 경우는 다음과 같이 최적화 빌드를 할 수 있습니다.

# 일반적인 빌드 
cargo build --release 

# 최적화 빌드 
RUSTFLAGS="-C target-cpu=native" cargo build --release

rustc가 지원하는 target-cpu의 종류들을 확인하는 방법은 rustc --print target-cpus 명령을 사용하면 됩니다.

 

지원하는 target cpu들 중 Apple SoC의 종류들을 확인해 보면, 최신 버전의 Rust(1.59+)와 LLVM 환경에서는 `apple-m1`, `apple-m2` 등의 타겟이 명시적으로 포함되어 있습니다. 만약 목록에 보이지 않더라도 `native`를 사용할 경우 컴파일러가 현재 실행 중인 Apple Silicon 칩을 자동으로 인식하여 최적화합니다. 특히 M1, M2, M3는 마이크로 아키텍처의 많은 특성을 공유하고 있으므로, `native` 옵션을 사용하는 것이 가장 안전하고 확실한 최적화 방법입니다.

 

* Go project 
다음으로 go 언어 프로젝트에 대해 알아 봅니다.

Go의 경우에는 환경 변수를 수정하여 Apple Silicon에 맞는 ARM 아키텍처 세대를 설정할 수 있습니다. Apple Silicon SoC는 세대별로 기반이 되는 ARM 아키텍처 버전이 다릅니다. M1과 M2는 ARMv8.5-A 기반이며, M3는 ARMv8.6-A(일부 v9 기능 포함), 그리고 최신 M4는 ARMv9.4-A 기반으로 알려져 있습니다.
 

go env 를 명령하면, 위의 스샷과 같이 go와 관련된 환경변수들과 그 설정값을 확인할 수 있는데, GOARM64='v8.0' 으로 되어 있는 것을 알 수 있습니다. 이는 generic ARM 아키텍처로 일반적인 ARM cpu 들을 모두 지원하기 위한 base 아키텍처입니다. 따라서, apple silicon m1, m2 에서 최적화 빌드를 하려면 v8.5로 변경해야 합니다. 

변경하는 방법은 다음과 같습니다  이 방법은 Go 1.23 버전 이상부터 공식적으로 지원됩니다. 

go env -w GOARM64=v8.5

이렇게 설정을 변경한 경우라면 그냥 `go build` 명령으로 빌드하면 됩니다. 하지만 배포용 프로젝트를 개발하고 있다면, 환경 설정을 영구적으로 바꾸는 것보다는 빌드할 때 일회성(one-time) 설정을 사용하는 것이 더 좋습니다.

GOARM64=v8.5 go build

만약 Go 프로젝트 내에서 C 코드를 호출(CGO)한다면, Rust에서 했던 것처럼 C 컴파일러 플래그를 함께 넘겨줘야 최적화 효과를 볼 수 있습니다.

CGO_ENABLED=1 CGO_CFLAGS="-march=native" go build

 

* zig project
zig의 경우에는 -Dcpu 와 -Dtarget 옵션을 사용합니다. 

zig build -Dcpu=apple_m1 -Dtarget=native -Doptimize=ReleaseFast

zig는 LLVM 기반을 적극적으로 활용하는 언어로써, 기본적으로는 -Dcpu 와 -Doptimize 를 사용합니다.  -Dcpu 에서는 apple_m1 처럼 명시적으로 설정할 수도 있고, native를 사용할 수도 있습니다. 원칙적으로는 native 사용을 권장합니다.  native를 사용하게 되면, 컴파일러가 현재 CPU의 캐시라인 크기나 파이프라인 깊이 등을 직접 측정하여 가장 정밀한 바이너리를 생성하게 됩니다. 

 

* C/C++ project
여전히 많은 프로젝트들은 C/C++로 작성됩니다. C나 C++ 프로젝트의 경우에는 일반적으로 Meson, Ninja를 사용하여 빌드하는 경우가 주류인데, 이 경우 GCC보다는 Clang 사용을 추천합니다. LLVM/Clang은 Apple이 개발에 깊이 참여하고 있으며, macOS의 표준 컴파일러이기 때문에 Apple Silicon SoC에 대한 최적화 수준이 매우 높습니다.

기존의 `meson.build`를 수정하는 대신, 다음과 같이 설정 시 플래그를 전달하여 최적화 빌드를 수행할 수 있습니다.

# 빌드 디렉토리를 처음 설정할 때 Clang으로 지정
CC=clang CXX=clang++ meson setup build

# 최적화 플래그를 포함하여 빌드 (c 프로젝트)
CC=clang meson setup build -Dbuildtype=release -Dc_args="-mcpu=apple-m1 -mtune=apple-m1"

# c++ 프로젝트라면
CXX=clang++ meson setup build -Dbuildtype=release -Dcpp_args="-mcpu=apple-m1 -mtune=apple-m1"

# c++에 c가 포함된 경우라면 
CC-clang CXX=clang++ meson setup build -Dbuildtype=release -Dc_args="-mcpu=apple-m1 -mtune=apple-m1" -Dcpp_args="-mcpu=apple-m1 -mtune=apple-m1"

# 빌드 실행
ninja -C build

이렇게 설정하면 M1이 지원하는 명령어 세트(ISA)를 활성화하고, M1의 파이프라인 구조에 맞춰 명령어를 재배열하여 실행 속도를 극대화할 수 있습니다. 

-Rpass=loop-vectorize
빌드 시 `-Dc_args`나 `-Dcpp_args`에 이 옵션을 추가하면, 컴파일러가 루프 코드를 SIMD(Single Instruction Multiple Data) 명령어로 얼마나 잘 최적화했는지 상세 로그를 출력해 줍니다. (참고: 실제 최적화는 `-Dbuildtype=release` 시 적용되는 `-O2` 이상의 단계에서 자동으로 수행됩니다.)

위의 스샷을 보면, for-loop 에 대해서 vectorized loop (vectorization width:4, interleaved count: 4) 와 같이 표시를 하고 있는데, 이는 오류나 경고 문구가 아니고, 해당 for-loop를 다음과 같이 처리한다는 의미입니다. 

- vecterization width : 4 
  첫 번째 데이터를 처리하는 동안 i+1, i+2, i+3 번째 데이터를 미리 준비하거나 동시에 처리하여 CPU 유휴 시간을 없앤다는 의미입니다. 
- interleaved count:4 
  이것은 한 번의 루프 회전에서 4회 분량의 작업을 묶어서 처리한다는 의미입니다. 

SIMD는 apple silicon 에 포함된 NEON 이라는 유닛이 담당하는 것으로 M1의 NEON은 128비트 레지스터를 사용합니다. 따라서, 32비트 정수나 float 데이터 4개를 동시에 처리할 수 있습니다.  8bit char 라면, 16개를 한 번에 처리합니다.  따라서, SIMD를 사용하게 되면 명령어를 한번에 처리하기 때문에, CPU 부담이 줄고 처리속도는 배가 되며 전력효율이 좋아지게 됩니다. 

-Db_lto=true 옵션
Meson 설정 시 `-Db_lto=true` 옵션을 사용하면 링크 타임 최적화를 활성화할 수 있습니다. 일반적인 컴파일러는 파일 단위로 최적화를 수행하지만, LTO를 적용하면 전체 프로젝트 프로젝트 단위로 코드를 분석합니다.

일반적으로 프로그램을 개발할 때는 여러 개의 c 파일로 구분되어 있습니다. 일반적인 compiler는 a.c , b.c, c.c 파일을 각각 a.o, b.o, c.o로 각각 컴파일합니다. 만일 a.c 에서 b.c에 있는 함수를 call 한다면 그 내부가 어떻게 생겼는지 알 수 없기 때문에 최적화에 한계가 있습니다. 즉, 그냥 이어 붙여서 최종 binalry를 만들게 됩니다. LTO를 적용하게 되면, Link 단계에서 모든 파일을 모아 놓고 파일 경계를 넘나들며 함수를 합치며(inlinig), 안 쓰는 코드는 제거하는 등 전체 프로젝트 단위로 최적화를 수행하게 됩니다.  이 과정을 거치면 함수 안에서 다른 함수를 call 하는 부분을 호출 오버해드를 없애고 코드를 하나로 합치고, 프로그램 전체 흐름을 분석하기 때문에 M1에 포함된 많은 수의 레지스터를 어떻게 효율적으로 나눠 사용할지를 최적화 결정하게 되며, 실행되지 않는 코드들이 있다면 해당 코드를 찾아내어 제거하기 때문에 파일크기를 줄일 수 있습니다. 

단, 이를 위해 전체 코드를 분석하는 과정이 필요하므로 빌드 시간이 늘어나고, 빌드 과정에서 메모리 소모가 늘어나며. gdb 나 lldb 등으로 디버깅할 때 소스코드의 위치가 정확히 안 맞을 수 있습니다.  따라서 디버깅 과정에 있다면 LTO 옵션을 사용하지 않고 최종 release 단계에서만 적용하는 것을 추천합니다. 

LTO 옵션은 모든 프로젝트에서 사용할 수 있는 옵션은 아닙니다. 라이브러리 프로젝트의 경우 LTO로 빌드하면 해당 라이브러리를 사용하는 다른 프로젝트와 컴파일러 도구 체인 버전이 다를 때 링크 오류가 발생할 수 있으므로 주의가 필요합니다. 따라서 디버깅 단계보다는 최종 릴리스 단계에서 적용하는 것을 권장합니다. 일부 프로젝트에서는 meson.build 에서 해당 옵션을 사용하지 말라고 경고 메시지를 내는 경우도 있습니다. 

- - - - - - - 

앱을 빌드할 때 Apple Silicon SoC에 맞춰 최적화를 한다는 것은 하드웨어의 잠재력을 100% 끌어내는 과정입니다. 특화된 레지스터 세트를 효율적으로 사용하고, 비순차적 실행(Out-of-Order execution) 특성에 맞춰 명령어를 재배열하며, 가속기 기능을 적극적으로 활용하게 됩니다.

macOS 앱들이 부드럽고 빠르게 동작하는 비결 중 하나는 이러한 하드웨어 최적화 빌드에 있습니다. Asahi Linux 사용자라면 직접 빌드하는 수고를 통해 이러한 성능 이점을 온전히 누려보시기 바랍니다.

공유하기 링크
Comments