May 22, 2025

[Project-ttrade] #3 Cpython Docker 빌드하기

[Project-ttrade] #3  Cpython Docker 빌드하기

C와 비교하면 Python은 매우 느립니다.
아주 희귀하게 C보다 빠르게 할 수 있다고 하긴 하는데, 잘 모르겠습니다.

http://niklas-heer.github.io/speed-comparison/

시계열 데이터를 처리할 때 대부분 numpy의 벡터화 연산을 사용하여 진행합니다.
벡터화 연산은 아주 큰 데이터도 견딜만한 시간을 사용하여 for문에 비해서 아주 효율적인 연산을 제공합니다.

하지만 가끔 어쩔 수 없이 for문을 사용해야 하는 경우가 있습니다. (아니면 제 머리가 좀 부족해서 벡터화를 못하는 걸 수도 있지만...)
그렇게 벡터화를 쓰다가 for문을 한번 쓰면 속도가 매우 차이가 납니다. 그 때, python에서 c를 사용하면 빠르게 진행할 수 있습니다.

C File

그러면 원하는 연산을 가진 C 파일을 만들어 줍니다.
x의 현재 값에서 특정 %만큼 오르고 내릴 때의 걸리는 기간을 y에 넣습니다.
(현재부터 5개 뒤에 현재가의 ±per%만큼 변동이 있다 -> 5 O(n²))

c는 배열의 길이를 저장해놓지 않기에 따로 길이도 변환해서 넘겨줘야 합니다.

#include <stdio.h>

void pred_period(const int len, int*x, int*y, const float per)
{
    unsigned long upper,lower;
    printf("len: %d\n",len);
    printf("x first/last: %d / %d\n",x[0],x[len-1]);
    printf("y first/last: %d / %d\n",y[0],y[len-1]);
    printf("per: %f\n",per);
    for (int i=0;i<len;i++){
        upper = (unsigned long)(x[i] * (1+per));
        lower = (unsigned long)(x[i] * (1-per));

        for (int j=i+1;j<len;j++){
            if (upper < x[j]){
                y[i] = j-i;
                break;
            }
            else if (lower > x[j]){
                y[i] = i-j;
                break;
            }
        }
    }
}

app.c

이 c 파일을 compile해야 python에서 사용할 수 있습니다만, Dockerfile로 컴파일을 진행하겠습니다.

Python ctypes

python과 c의 데이터가 다르기 때문에, ctypes의 c 변수로 변환해줘야 합니다.

import ctypes
import pandas as pd
import numpy as np

_dll = ctypes.cdll.LoadLibrary('app.so')
func = getattr(_dll,'pred_period')                       # c 함수

x = pd.read_csv(file).to_numpy(dtype=np.int32,copy=True).flatten()
x_len = len(x)

per = ctypes.c_float(0.0025)                             # per
c_len = ctypes.c_int(x_len)                              # 배열 길이
c_x = x.ctypes.data_as(ctypes.POINTER(ctypes.c_int32))   # x array
y = np.zeros(x_len,dtype=np.float32)
c_y = y.ctypes.data_as(ctypes.POINTER(ctypes.c_int32))   # y array

func(c_len,c_x,c_y,per)                                  # c함수 실행


y_np = np.ctypeslib.as_array(c_y,shape=(x_len,))         # numpy 변환
y_df = pd.DataFrame({'period':y_np})                     # pandas 변환
y_df.index = df.index

so는 gcc로 로컬에서 변환 후에 넣어줘도 되지만, docker를 사용하면 docker에서 같이 해줘야죠.

Dockerfile c build

gcc 이미지를 사용해서 so 파일을 생성하고, so 파일만 이미지로 복사합니다.
그 후 python으로 생성합니다.

FROM gcc:latest
WORKDIR /app
COPY target.c .
RUN gcc -shared -o libtarget.so -fPIC target.c

FROM python:3.9-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /source
COPY --from=0 /app/libtarget.so .
COPY requirements.txt .
RUN pip --no-cache-dir install -r ./requirements.txt
COPY app.py .
CMD ["python","app.py"]

마무리

내용은 간단하고 cpython에 args를 동적으로 생성해서 넘겨줄 수도 있어서 생각보다 편하지만, 최대한 python에서 하려고 하는 게 맞다고 생각합니다. c 로 변수를 변환하면서 생기는 문제도 많고, 원인 찾기도 힘들어요.

k8s-TorchTrade/train/target_cpython at main · DogRing/k8s-TorchTrade
Contribute to DogRing/k8s-TorchTrade development by creating an account on GitHub.

Comments