일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- 티스토리챌린지
- 머신러닝
- supervised learning
- 오블완
- AI
- PCA
- LG Aimers 4th
- LG
- ChatGPT
- OpenAI
- 분류
- Machine Learning
- 지도학습
- gpt
- Classification
- LLM
- 해커톤
- LG Aimers
- 딥러닝
- GPT-4
- deep learning
- regression
- 회귀
Archives
- Today
- Total
SYDev
[백엔드를 위한 GO 프로그래밍] Chapter 6. 상호 호환성 본문
6.1. 상호 호환성이 중요한 이유
- Go code로 미리 작성된 library를 이용 -> 새로 code를 짜지 않고, 해당 기능을 간편하게 사용할 수 있음
- 그렇다면 Go language로 작성되지 않은 코드는 어떻게 사용할까?
- Types of Programming Languages
Interpreter Language
- program을 실행할 때, source code를 사전에 machine language로 compile하지 않고 한 줄 한 줄 즉각적으로 실행하는 언어
- ex) python
Just In Time(JIT) Compile Language
- ex) Java
- source code를 Java 바이트코드(cpu native 보다는 high level)로 compile
- 해당 바이트코드를 실행할 때, JVM(Java Virtual Machine)은 python과 유사한 방식으로 해당 코드를 interpret -> interpreted code가 즉시 native CPU code로 compile될 수 있고, 나중에 함수가 호출될 때를 위해 cache될 수 있기 때문에, 일반적 interpreter language보다 성능이 좋음
Ahead Of Time(AOT) Compile Language
- ex) Go, C, C++, Switft
- Program이 실행되기 전에, soruce code를 미리 native CPU code로 compile
- program이 최적의 성능을 발휘하도록 user code를 최적화 가능
- 실행 가능한 binary 형태로 생성되는 구조 -> 별도의 software 없이 os에서 실행
- os는 main 함수(Entry Point)를 호출 -> 이 함수를 시작으로 필요한 모든 codes가 실행되고, main 함수 종료 -> program도 종료
Entry Point: 제어가 os에서 computer program으로 이동하는 것을 의미
- processor는 program이나 code에 진입해서 execution을 시작
참고자료:
https://ko.wikipedia.org/wiki/%EC%97%94%ED%8A%B8%EB%A6%AC_%ED%8F%AC%EC%9D%B8%ED%8A%B8
- Shared Library
Shared Library
- AOT Compile Language에서, 사용자는 이미 compile된 다른 code를 호출 가능
- ex) malloc이라는 함수의 정의가 code 내에 없어도 호출 가능 -> 해당 함수를 os의 C library에서 제공, compiler가 생성하는 binary에는 사용자 code가 접근해야 하는 library의 link 정보가 포함되어 있기 때문
- Shared Library: runtime에 로드되면서 compile time에 존재하기로 약속된 library -> 여러 program이 공유 가능
- Linux에서는 .so(Shared Object), macos에서는 .dylib(Dynamic Library), windows에서는 .dll(Dynamically Linked Library) 사용 -> 모두 같은 목적의 파일, but 처리 속도는 시스템 별로 차이가 발생
Programming Language 간의 code 공유
- shared library를 이용하면 programming language 간의 code 공유도 가능
- AOT compile language는 high level code를 모두 machine language로 변환 -> compiled code를 shared library에 연결하면 서로 다른 programming language 간의 code 공유 가능
- interpreter, JIT compile language의 경우 방식이 다를 수 있음
제한 사항
- 모든 AOT compile language가 똑같은 기계어 format을 따르는 것은 아님
- 각 language가 어떤 특수한 목적을 달성하기 위하여 binary에 고유한 트릭을 추가 -> 이렇게 추가된 코드가 "Runtime"
- 사용자가 작성한 코드 이외의 코드들이 compiler에 추가된 형태 -> Runtime이 많을수록 다른 language에서 code를 호출하는 것이 복잡해짐
- Go에서의 코드 공유
- Go는 C나 Swift, Rust보다 훨씬 더 큰 Runtime을 가짐 -> Go가 여러 개의 Gorutine을 다루는 데 필요한 모든 업무를 처리해야 하기 때문
- Go program의 entry point -> Go runtime backend 대부분을 포함하는 연결된 공유 라이브러리에 존재 -> 해당 shared library는 runtime을 초기화하고, 일부 내부적인 설정 및 준비를 마치고 나서 main 함수를 호출 -> program의 종료도 해당 library를 통해 진행
- Go에서 shared library를 호출하면, 다른 언어와 비교할 때 상대적으로 큰 성능 저하가 발생
- 그럼에도, 다른 언어의 코드를 호출하는 것은 매우 유용 -> 다른 언어로 구현하는 것이 유리한 작업을 해당 언어로 작업하고, 그 결과를 Go code에서 접근하고 싶은 경우
6.2. C code와 상호 호환하기
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
- #include <stdio.h> -> os에서 "stdio.h" 파일을 가져와서, 해당 내용을 이 파일에 붙여넣을 것
- stdio.h에는 입력과 출력을 처리하는 함수 선언이 많이 포함됨
- 그러나, 이런 헤더 파일에는 호출할 수 있는 함수만 알려주는 역할 -> 실제 동작을 위해서 이 코드를 해당 함수가 있는 library에 연결해야 함
- 일반적인 binary에는 standard library에 default로 연결되어 있기 때문에, stdio를 쓸 때 연결하지 않아도 됨
- printf("Hello World\n") -> printf 함수를 호출하고 "Hello, World!\n" 문자열을 포함하는 캐릭터 버퍼에 대한 포인터를 전달
- Go에서 C 호출하기
- Program의 entry point(진입점)가 C가 아닌 Go가 되어야 함 -> C code에서 main 함수를 정의할 수 없다는 의미
- print만 수행하고, 아무것도 반환하지 않음 -> return type은 void
#inclde <stdio.h>
void printHelloWorld() {
printf("Hello, World!\n");
}
-> main 함수가 없으므로, C compiler로는 compile or execution 불가
-> 다음 flag를 사용하여 compile하면 shared library format의 machine language로 컴파일된, 위 코드를 포함한 "libhelloworld.so"라는 파일을 만들 수 있음
-shared -fPIC -o -libhelloworld.so
-> 이 공유 라이브러리는 Go에서 호출 불가능
cgo library
C를 Go에서 호출하기 위해서, cgo라고 하는 별도의 Go library 이용
-> C code와 header를 포함하는 Go code를 compile할 수 있고, compile된 C code와 header를 연결할 수 있어 C 원시 코드를 compile하여 실행 가능 형태의 최종 파일을 Go에서 생성 가능
package main
//#include <stdio.h>
//void printHelloWorld() {
// printf("Hello, World!\n");
//}
import "C"
func main() {
C.printHelloWorld()
}
- 주석 처리된 C code와 함께 "C" 패키지를 import함으로써 cgo 사용
- Go에서 C로 변수 전달
- cgo module에는 C.int(32 bit signed integer), C.unit(32 bit unsigned integer), C.long(64 bit signed integer) 등 다양한 type 포함 -> 가장 변환이 어려운 것이 문자열
- 문자열은 character라는 독립적 데이터 조각의 배열 -> 기술적으로 하나의 데이터 X -> 문자열의 종료를 나타내는 값인 null terminator를 통해서 문자열의 종료 확인(모든 bit가 OFF(0))
- character 하나로 생각되는 imoji나 일부 unicode character처럼 1byte로 표현되지 않을 수 있음
- cgo의 CString function -> Go 문자열을 C 문자열로 쉽게 변환 -> 문자열에 필요한 memory 사본이 만들어지므로, 성능에 민감한 경우 바람직 X
- C 문자열 사용하는 경우 -> Memory Leak(메모리 누수)이 발생하지 않도록 해야함 -> Go의 Garbage Collector는 사용자가 생성한 pointer를 처리할 수 없음 -> ptr가 가리키는 memory buffer를 해제하는 책임은 사용자에게
CString 함수의 시그니처
func C.CString(string) *C.char
package main
//#include <stdio.h>
//#include <stdlib.h>
//
//void printString(char* str) {
// printf("%s\n", str);
//}
import "C"
import "unsafe"
func main() {
a:= C.Cstring("This is from Golang")
C.printString(a)
C.free(unsafe.Pointer(a))
}
- C free function -> type 정보를 포함하지 않는 Generic pointer를 가짐 -> unsafe module을 이용, 먼저 C 문자열과 관련된 type 삭제 후 전달
- free function을 사용하기 위해서 stdlib.h과 stdio.h를 함께 추가해야 함
- C 함수의 반환 값 Go로 받아오기
CFLAGS param
- CFLAGS parameter -> 주석에 추가한 C code를 원하는 대로 compile하도록 C compiler에 지시할 수 있음
package main
//#include <stdio.h>
//
//char *getName(int idx) {
// if(idx == 1)
// return "Tanmay Bakshi";
// if(idx == 2)
// return "Baheer Kamal";
// return "Invalid index";
//}
import "C"
import (
"fmt"
)
func main() {
cstr := C.getName(C.int(2))
fmt.Println(C.GoString(cstr))
}
- CString을 사용하지 않았기 때문에, 사본 생성 X -> heap에 할당되지 않고, 스택에만 저장 -> character pointer를 해제할 필요 X
- 주석에 추가한 코드를 원하는 대로 compile하도록 C compiler에 지시할 수 있음
- ex) 특정 컴파일 flag를 전달하는 경우 CFLAGS parameter 사용 가능 -> ugly number를 구하는 코드
package main
//#cgo CFLAGS: -O0
// #include <stdio.h>
// int numberIsUgly(int x) {
// while (x > 1) {
// int y = x;
// while (y % 2 == 0)
// y /= 2;
// while (y % 3 == 0)
// y /= 3;
// while (y % 5 == 0)
// y /= 5;
// if (x == y)
// return 0;
// x = y;
// }
// return 1;
// }
// =
// void getNthUglyNumber(int n) {
// int i = 0;
// int j = 0;
// while (j < n) {
// i++;
// if (numberIsUgly(i)) {
// j++;
// }
// }
// printf("%d\n", i);
// }
import "C"
func main() {
C.getNthUglyNumber(C.int(1000))
}
- C compiler에 "-O0" flag 전달하도록 cgo에 지시
- "-O0" flag: 코드를 최적화하지 말고, assembly 변환을 위한 다이렉트 코드를 가능한 한 많이 사용할 것
- Intel i9 macbook pro에서 수행 시간 1.7s
- "-Ofast" flag
- 같은 조건에서 0.4s
Linker
- 위와 같은 로직으로 linker에 flag 전달 가능
- Linker: 실행 파일이 코드를 호출해야 하는 shared library를 os에 알리는 역할
파일명: factorial.c
int factorial(int x) {
if(x == 1)
return x;
return factorial(x - 1) * x;
}
clang factorial.c -shared -fPIC -o libcfactorial.dylib
-> compile 명령
package main
//#cgo LDFLAGS: -L. -lcfactorial
//int factorial(int);
import "C"
import "fmt"
func main() {
fmt.Println(C.factorial(C.int(5)))
}
- //#cgo LDFLAGS: -L. -lcfactorial
- linker에게 "코드가 컴파일되는 directory에서 shared library를 찾고, 특히 cfactorial이라는 library에 대해 링크할 것"이라 전달
- 원래 파일명은 libcfactorial.dylib -> 링커는 시작 부분의 "lib"과 끝부분의 ".dylib"을 무시
- //int factorial(int) -> program 어딘가에 존재하는 함수 factorial을 호출(32비트 signed int 반환, 32비트 signed int 인자)
- factorial 함수의 실제 machine language code는 이전 단계에서 구축한 library에 이미 compile되어 있음 -> Go code를 compile할 때는 factorial 함수를 compile하지 않음 -> 미리 컴파일된 factorial 함수 버전을 링크하도록 Go에 지시
6.3. Switft와 상호 호환하기
- Go에서는 tail call optimization과 같은 몇몇 최적화를 지원하지 않음
- 이런 종류의 구현이 C와 같은 언어로 작성될 수 있지만, C가 제공하는 제어 수준 만큼은 필요하지 않을 때 -> Switft 혹은 이와 유사한 언어 사용
- Switft에서 테트리스를 자동으로 플레이하고, 게이므이 결과를 커맨드 라인에 시각화하는 application 구현
- Go에는 존재하지 않는 CMA-ES(Convariance Matrix Adaptation Evolutionary Strategy) method를 통해서, 구현
- CMA 최적화 method는 python에 구현된 상태, 하지만 python은 interpret 언어
- Switft 표준 라이브러리에 구축된 Python과의 상호 호환 계층을 이용하여 해당 method 이용
@_cdecl("nextBestMoves")
public func nextBestMoves() -> UnsafeMutablePointer<Int> {
var nextMoves = game.nextBestMoves()!.0
nextMoves.insert(nextMoves.count, at: 0)
return nextMoves.withUnsafeBufferPointer { ptrToMoves -> UnsafeMutablePointer<Int> in
let newMemory = UnsafeMutablePointer<Int>.allocate(capacity: nextMoves.count)
memcpy(newMemory, nextMoves, nextMoves.count * MemoryLayout<Int>.size)
return newMemory
}
}
@_cdecl("playMove")
public func playMove(move: Int) {
switch move {
case -1:
game.swapHold()
case 0:
game.attemptSpin(clockwise: true)
case 2:
game.attemptSpin(clockwise: false)
case 4:
game.horizontalMove(left: false)
case 5:
game.horizontalMove(left: true)
case 6:
game.down()
default:
fatalError()
}
}
@_cdecl("renderFrame")
public func renderFrame() -> UnsafeMutablePointer<Int> {
var x = [24, 10] + game.render().reduce([], +)
x.insert(x.count, at: 0)
return x.withUnsafeBufferPointer { ptrToMoves -> UnsafeMutablePointer<Int> in
let newMemory = UnsafeMutablePointer<Int>.allocate(capacity: x.count)
memcpy(newMemory, x, x.count * MemoryLayout<Int>.size)
return newMemory
}
}
@_cdecl("lockGame")
public func lockGame() -> Bool {
return game.lock()
}
@_cdecl("resetGame")
public func resetGame() {
game = Tetris(width: 10, height: 24)
}
- @_cdecl function decorator: 해당 함수가 binary machine language에서 C 함수처럼 보이고 동작해야 한다는 의미 -> 이를 통해서, Go에서 이것을 C 함수인 것처럼 호출할 수 있게 됨
swiftc TWAI.swift -O -emit-library -o libTWAI.so
- -O: 컴파일러 최적화를 완전히 사용하여 code를 최대한 빠르게 만듦, but 안전하지 않은 기능은 사용 X
- -emit-library: 바이너리 실행 파일로 빌드X -> 다른 코드가 동적으로 link할 수 있는 라이브러리로 빌드
- -o libTWAI.so: 출력되는 파일 이름은 "libTWAI.so"
package main
// #cgo LDFLAGS: -L. -lTWAI
// #include <stdbool.h>
// long *nextBestMoves();
// void playMove(long move);
// long *renderFrame();
// void lockGame();
// bool resetGame();
import "C"
import (
"fmt"
"os"
"reflect"
"time"
"unsafe"
"github.com/gdamore/tcell/v2"
)
- 주석으로 표현되는 C code를 파악할 수 있도록 cgo import와 다른 import가 구분됨
- // #cgo LDFLAGS: -L. -lTWAI -> linker에게 TWAI라는 library를 링크하라 전달
- // long *nextBestMoves();
// void playMove(long move);
// long *renderFrame();
// void lockGame();
// bool resetGame();- Go code에서 호출할 shared library에 있는 함수를 선언
- 이외 Go 코드에서 Swift 코드를 더 쉽게 호출할 수 있도록 도움을 줄 수 있는 몇 가지 함수를 구현하여 최종적으로 swift의 code를 Go에서 이용
참고자료
'Google Developer Groups on KHU 1th' 카테고리의 다른 글
gRPC란? (2) | 2024.11.16 |
---|---|
[Go 언어로 배우는 웹 애플리케이션 개발] Chapter 18. RDBMS를 사용한 데이터베이스 처리 구현하기 (0) | 2024.11.14 |
[Go 언어로 배우는 웹 애플리케이션 개발] Chapter 17. 엔드포인트 추가하기 (4) | 2024.11.13 |