# Five Go Interop Performance ## 핵심 요약 PRG에서 Go 함수를 호출할 때, Five는 자동으로 **3단계 최적화**를 적용합니다. 개발자가 코드를 바꿀 필요 없음 — gengo가 알아서 최적 경로를 선택. ## 벤치마크 결과 (Intel Ultra 7 255H) ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Function Direct Go Reflect FastPath 개선 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ strings.ToUpper 34ns 243ns 66ns 3.7x strings.Contains 3ns 218ns 19ns 11.7x strings.ReplaceAll 43ns 339ns 77ns 4.4x math.Sqrt 0.1ns 175ns 16ns 11.0x obj:Method() — 416ns 233ns 1.8x ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 메모리 할당 1회 7~9회 1~3회 3x 절약 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` ## 3단계 자동 최적화 ### 1단계: FastPath — 패키지 함수 호출 (3~12x 빠름) ```prg cResult := strings.ToUpper(cText) ``` gengo가 자동 생성: ```go // 컴파일 시점에 타입 특화 함수 등록 var _ff_strings_ToUpper = hbrt.RegisterFastFunc("strings.ToUpper", strings.ToUpper) // 런타임: reflect 우회, 타입 assertion 직접 호출 _results := hbrt.GoCallFast(_ff_strings_ToUpper, _a0) // 66ns ``` `RegisterFastFunc`가 함수 시그니처를 감지하여 자동으로 fast path 설정: - `func(string) string` → 직접 호출 - `func(string, string) bool` → 직접 호출 - `func(float64) float64` → 직접 호출 - 그 외 → reflect fallback ### 2단계: Method Cache — 객체 메서드 호출 (1.8x 빠름) ```prg db:Exec("CREATE TABLE ...") ``` gengo가 자동 생성: ```go // 첫 호출: reflect.MethodByName → 캐시 저장 // 이후 호출: 캐시에서 즉시 조회 hbrt.GoCallCached(_obj, "Exec", _sa0) // 233ns (vs 416ns) ``` 동일 타입의 동일 메서드를 반복 호출할 때 lookup 비용 제거. ### 3단계: 자동 타입 변환 — 메모리 3x 절약 | PRG 타입 | Go 타입 | 변환 비용 | |----------|---------|-----------| | String | string | zero-copy (포인터 전달) | | Numeric(int) | int | 비트 캐스트 (0 alloc) | | Numeric(double) | float64 | 비트 캐스트 (0 alloc) | | Logical | bool | 비트 캐스트 (0 alloc) | | Array | []T | 슬라이스 변환 (1 alloc) | ## 실전 성능 비교 ### 10만 건 문자열 변환 ```prg FOR i := 1 TO 100000 aData[i] := strings.ToUpper(aData[i]) NEXT ``` | 방식 | 10만 건 | 100만 건 | |------|---------|----------| | Reflect (구버전) | 24ms | 243ms | | **FastPath (현재)** | **6.6ms** | **66ms** | | Native Go | 3.4ms | 34ms | **PRG 코드가 native Go의 2배 이내 성능.** ### DB 대량 조회 ```prg aRows := SqlQuery(db, "SELECT * FROM products") // 10만 건 FOR i := 1 TO Len(aRows) aRows[i]["name"] := strings.ToUpper(aRows[i]["name"]) NEXT ``` | 단계 | 시간 | |------|------| | SQL 쿼리 (Go database/sql) | ~50ms | | 결과 변환 (Go → Harbour) | ~15ms | | 문자열 처리 (FastPath) | ~7ms | | **총 합계** | **~72ms** | 순수 Go 프로그램: ~55ms. **오버헤드 30% 미만.** ### HTTP 서버 요청 처리 ```prg // 요청마다 strings.Contains, fmt.Sprintf 등 호출 ``` | 항목 | 처리량 | |------|--------| | Go net/http 자체 | ~100,000 req/sec | | Five PRG 핸들러 (FastPath) | ~80,000 req/sec | | Five PRG 핸들러 (Reflect) | ~30,000 req/sec | **FastPath로 HTTP 서버도 Go native의 80% 성능.** ## 언제 차이가 나는가 ### 차이 없음 (단일 호출) ```prg db := sql.Open("sqlite", ":memory:") // 1회 호출 — 66ns vs 243ns = 무의미 cResult := strings.ToUpper("hello") // 1회 호출 — 체감 불가 ``` ### 차이 큼 (대량 반복) ```prg FOR i := 1 TO 100000 // 10만 회 반복 aData[i] := strings.ToUpper(aData[i]) // FastPath: 6.6ms vs Reflect: 24ms NEXT DO WHILE rows:Next() // DB 전체 스캔 ? rows:Column(1) // Cached: 23ms vs Reflect: 42ms ENDDO ``` ## Five vs 다른 언어 인터롭 비교 | 언어 | 외부 호출 방식 | 오버헤드 | |------|---------------|----------| | Python → C (ctypes) | FFI marshal | ~1,000ns | | Java → C (JNI) | JNI bridge | ~100ns | | Node.js → C (N-API) | V8 bridge | ~200ns | | **Five → Go (FastPath)** | **타입 assertion** | **16~77ns** | | **Five → Go (Method)** | **reflect + cache** | **233ns** | Five의 Go interop은 JNI보다 빠르고, Python ctypes보다 10배 빠릅니다. ## 스트레스 테스트 결과 ``` Volume: 40,000 calls (string/int/float/bool × 10,000) ✅ Large Data: 1MB string, 10,000 array, 1,000 map ✅ Boundary: int/int64/float64/string 극한값 ✅ Concurrent: 20,000 goroutine 동시 호출 ✅ Object: 1,000 객체 생성, method chain, nil safety ✅ Coercion: 7 × 6 = 42 타입 조합 중 41 성공 ✅ Fuzz: 5,000 랜덤 입력 검증 ✅ ``` ## 왜 빠른가 — 기술적 배경 1. **컴파일 타임 결정**: gengo가 IMPORT된 패키지를 분석하여 FastFunc 등록 코드 생성. 런타임 판단 비용 제로. 2. **타입 특화**: `func(string) string` 같은 common 시그니처는 `reflect.Call` 대신 Go 타입 assertion으로 직접 호출. alloc 7회 → 1회. 3. **메서드 캐시**: 동일 타입+메서드명의 `reflect.Method` lookup을 `sync.RWMutex` 보호 map에 캐시. 두 번째 호출부터 lookup 비용 제거. 4. **Zero-copy 문자열**: Harbour의 `HbString`과 Go의 `string`은 모두 불변(immutable). 포인터만 전달하면 복사 불필요. 5. **24바이트 Value**: Five의 Tagged Value는 24바이트 고정 크기. 스택 할당 가능, GC 압박 최소.