TECH 으로 돌아가기
TECH HACKER NEWS 7분 읽기 36 READS

C 대신 C#로 VLC 영상 필터를? 가우시안 블러로 본 성능 격차의 실체

C 대신 C#로 VLC 영상 필터를? 가우시안 블러로 본 성능 격차의 실체
SOURCE IMAGE · HACKER NEWS

프로그래밍 언어를 고를 때 'C가 당연히 빠르다'는 믿음은 거의 반사적이다. 특히 영상 처리처럼 프레임의 모든 픽셀을 건드리는 작업이라면, 가비지 컬렉터를 가진 관리형 언어는 불리할 수밖에 없다고들 말한다. 한 개발자가 이 통념을 실제 코드로 검증했다. 그는 미디어 플레이어 VLC에 실제로 들어 있는 가우시안 블러 필터를 C#으로 한 줄씩 그대로 옮긴 뒤, 같은 VLC 4 릴리스 빌드 안에서 원본 C 필터와 같은 프레임을 두고 경주시켰다. 두 버전은 처음부터 끝까지 바이트 단위로 동일한 결과물을 냈고, 양쪽을 번갈아 최적화하며 세 차례에 걸쳐 속도를 비교했다.

가우시안 블러는 각 픽셀을 주변 픽셀의 가중 평균으로 바꾸는 효과다. 가까운 픽셀일수록 비중이 크고, 가우스가 1809년 측정 오차를 설명하며 쓴 종 모양 곡선을 따라 가중치가 부드럽게 줄어든다. 이 매끄러운 감쇠 덕분에 네모난 번짐이 아니라 초점이 나간 렌즈처럼 보인다. 시그마라는 숫자 하나가 강도를 조절하며, VLC는 3×시그마 픽셀 거리까지 이웃을 참조한다. 시그마 4라면 25픽셀 창이다. 포토샵 1.0이 1990년에 이미 탑재했을 만큼 오래된 효과이고, VLC도 2007년부터 '공간 블러'라는 이름으로 제공해 왔다.

핵심 최적화 기법은 '분리 가능성'이다. 가로로 한 번, 세로로 한 번 흐리면 전체 2차원 창을 적용한 것과 정확히 같은 결과가 나온다. 덕분에 25×25 창에서 픽셀당 625번 곱하던 연산이 25번짜리 두 번의 패스로 줄어든다. VLC의 gaussianblur.c가 바로 이 방식을 정수 연산으로 구현하고, C# 포팅판 역시 같은 루프, 같은 경계 처리, 같은 정수 산술, C가 생 포인터를 쓰는 곳에서는 똑같이 생 포인터를 쓰는 의도적인 1:1 복제였다. 이 필터는 Native AOT로 컴파일된 네이티브 DLL로 동작하며, 프레임마다 관리형 힙에 아무것도 할당하지 않아 실행 중 가비지 컬렉터가 수거할 것이 없다.

격차의 정체는 언어가 아니라 코드 생성기였다

1라운드에서 C가 예상대로 이겼다. C# 포팅판은 프레임당 약 23% 더 오래 걸렸다. 재앙은 아니지만 분명한 차이였다. 그런데 기계어를 뜯어보니 원인은 '언어' 자체가 아니었다. clang은 누적합을 레지스터에 유지하고 루프 경계를 한 번만 계산한 반면, .NET의 코드 생성기 RyuJIT은 매 단계마다 Math.Min을 다시 계산하고 합을 메모리에 두었다. 같은 산술에 군더더기 작업만 두 배로 붙은 것이다.

저자는 발표 후 두 가지 작은 수정으로 이 격차의 대부분을 메웠다. 루프 경계를 사전에 지역 변수로 계산해 RyuJIT이 매번 Math.Min을 재평가하지 않게 하고, 인덱스에 nint 타입을 써서 메모리 접근마다 32비트에서 64비트로 변환하는 과정을 없앴다. 둘 다 clang은 알아서 하는 일이다. 코드는 같고 결과도 바이트 단위로 동일한데, 이 두 줄의 조정만으로 C# 포팅판은 C와의 격차를 8% 이내로 좁혔고 시그마 4에서는 오히려 앞섰다. 격차의 일부는 컴파일러 탓이었고, 일부는 컴파일러에게 약간의 힌트가 필요했던 코드 작성 방식 탓이었다는 뜻이다.

알고리즘이 언어보다 먼저다

2라운드에서는 SIMD에 손대기 전에 알고리즘 자체를 다듬었다. 네 가지 개선을 양쪽에 똑같이 적용하자, 여전히 순수 스칼라 코드인 C# 버전이 현재 VLC 코드보다 2.5배 빨라졌다. 다만 C는 여전히 앞섰다. C#이 약 24% 더 걸렸는데, 여기에는 clang이 이 단순해진 루프를 알아서 SIMD 코드로 바꾼다는 이점이 작용했다. 비교의 공정성을 위해 그 기능을 껐지만, 켜면 C는 소스에 SIMD 한 줄 없이도 34fps에 도달했다.

마지막 라운드는 양쪽 모두 가진 모든 무기를 허용했다. 단일 스레드에서 AVX2 SIMD를 쓰되 결과는 여전히 바이트 단위로 동일하게 유지했다. C는 immintrin.h의 인트린식을, C#은 System.Runtime.Intrinsics를 사용했고 설계는 동일했다. 저자는 C#이 오히려 더 읽기 좋다고 평한다. 타입이 각 레지스터에 무엇이 들었는지 말해주는 반면, C는 모든 것을 하나의 __m256i로 뭉뚱그리기 때문이다. 첫 SIMD 버전에서는 C#이 14%, 가장 강한 블러에서는 30% 더 걸렸는데, 같은 벡터 명령어라도 clang이 주변 루프를 풀고 포인터를 레지스터에 유지한 차이였다. 양쪽을 계속 손본 끝에, 또 C# 쪽에서는 핫 루프를 별도 메서드로 떼어내고 가중치를 메모리에서 바로 브로드캐스트하도록 코드를 다듬어 .NET이 clang과 같은 결과를 내게 만들었다. 결국 시그마 2와 4에서는 측정 오차 범위 안에서 대등해졌고, 가장 강한 블러에서만 C#이 9% 더 걸렸다. 두 버전 모두 현재 VLC 코드보다 30배 빨랐다.

실무자 입장에서 이 실험이 주는 함의는 분명하다. 마지막 10%의 성능이 정말 중요하다면 C가 여전히 정답이다. 하지만 그 외의 거의 모든 경우에는, 가장 생산적으로 다룰 수 있는 언어에 좋은 알고리즘을 더하는 편이 더 멀리 간다. 한계도 솔직히 짚어둘 만하다. 이는 같은 CPU 코드로 두 언어를 비교한 실험일 뿐, 영상을 흐리는 최선의 방법은 아니다. 이런 필터는 본래 GPU에 올리는 것이 맞고, 일부 VLC 필터의 GPU 버전이 이미 개발 중이다. 또 저자는 실험 과정에서 속도와 무관한 VLC 버그 두 개도 발견해 머지 리퀘스트를 냈다. 하드웨어 디코딩에서 CPU 필터가 화면 절반을 녹색으로 물들이는 문제와, 블러가 각 라인의 오른쪽 끝을 2바이트 넘어 읽는 문제였다. 참고로 실험 설계와 규칙은 저자가 정했고 대부분의 코드와 벤치마크 하네스는 Claude가 작성했다는 점도 그가 밝혀두었다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://martinfinkel.com/blog/gaussian-blur-c-vs-csharp/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기 →
처리 중...