C 확장을 사용하는 이유
Ruby C 확장은 다음과 같은 주요 이점을 제공합니다.
-
기존 C 라이브러리 활용: Ruby 생태계에는
Nokogiri와 같이libxml2와 같은 C 라이브러리 위에 구축된 Gem이 많습니다. C 확장을 통해 이러한 기존 C 라이브러리와 직접 상호작용하여 재구현 없이 Ruby의 생산성과 검증된 C 코드의 안정성을 결합할 수 있습니다. -
Ruby 내부 동작 이해: Ruby 자체가 C로 구현되었기 때문에 C 확장 작성법을 익히는 것은 Ruby의 내부 작동 방식을 이해하는 데 도움이 됩니다.
-
성능 최적화: Ruby의 추상화는 개발 편의성을 높이지만, C와 같은 저수준 언어에 비해 알고리즘 구현 속도가 느릴 수 있습니다. 성능에 중요한 코드를 C 확장으로 옮기면 컴파일러의 최적화 이점을 활용하여 Ruby 코드에서 원활하게 사용할 수 있으며, 특히 대규모 배열이나 복잡한 계산에 유용합니다.
성능 차이를 보여주기 위해 10,000개의 요소를 가진 배열을 정렬하는 퀵정렬 벤치마크 결과, C 확장이 순수 Ruby 구현보다 8배 이상 빠른 성능을 보였습니다.
확장 컴파일 및 링킹
C 확장은 Ruby 인터프리터에 의해 실행되기 전에 먼저 컴파일되어야 합니다. 다음 단계를 통해 C 코드를 컴파일하고 Ruby 프로세스에 링킹할 수 있습니다.
-
ext/디렉토리 생성: C 파일을 보관할ext/디렉토리를 만듭니다. -
ext/extconf.rb파일 생성:require 'mkmf'와create_makefile 'qsort'를 포함하여 C 코드를 컴파일할 Makefile을 생성합니다. -
C 파일 (
qsort.c) 작성: C 함수를 구현할 파일입니다. 기본적으로#include "ruby/ruby.h"와void Init_qsort(void)함수를 포함합니다. -
컴파일:
ext/디렉토리에서ruby extconf.rb를 실행하여 Makefile을 생성하고, 이어서make를 실행하여 C 코드를 공유 라이브러리(qsort.so또는qsort.bundle)로 컴파일합니다. -
Rake 태스크 활용: 컴파일 단계를 자동화하기 위해
rake태스크를 생성할 수 있습니다. -
확장 로드: Ruby 코드에서
require_relative 'ext/qsort'를 통해 컴파일된 확장을 로드합니다.
C 코드 작성
Ruby의 C API는 C에서 모듈, 클래스, 메서드를 정의하고 VALUE 타입을 통해 Ruby 객체를 다룰 수 있게 합니다.
-
메서드 정의: 모든 C 확장에는
Init_<extension_name>형태의 초기화 함수가 필요합니다.rb_define_method(rb_cObject, "qsort_c!", rb_qsort, 1)와 같이rb_define_method를 사용하여qsort_c!와 같은 전역 메서드를 정의할 수 있습니다. -
Ruby 클래스 및 모듈 생성:
rb_define_class또는rb_define_class_under를 사용하여 Ruby 클래스나 모듈을 C에서 정의할 수 있습니다. -
인자 처리 및
VALUE타입: Ruby의 모든 것은 객체이며, C에서는VALUE타입으로 표현됩니다. C 메서드는 항상 첫 번째 인자로VALUE self를 받고, 그 후에 추가 인자들이VALUE타입으로 전달됩니다.Check_Type(ary, T_ARRAY)매크로를 사용하여 인자 타입을 검사하고,rb_ary_modify(ary)로 배열을 수정할 수 있습니다.RARRAY_LEN(ary)는 배열의 길이를,rb_ary_entry(ary, i)는 특정 인덱스의 요소를 반환합니다.
퀵정렬 구현
위에서 설명한 C API를 활용하여 Ruby의 퀵정렬 알고리즘을 C로 구현할 수 있습니다. rb_ary_entry와 rb_ary_store를 사용하여 swap 함수를 구현하고, partition 및 재귀적인 quick_sort 함수를 작성한 후, 이를 rb_qsort 메서드에 연결하여 Ruby 코드에서 호출할 수 있도록 합니다.