brunch

You can make anything
by writing

C.S.Lewis

by 유윤식 Jun 04. 2024

Python: Go with Proto

#python #GoLang #ProtoBuf #Performance

Python vs. GoLang ? ! ? !

한번쯤 파이썬을 사용하면서 느끼는 목마름... Performance!


이미 좋은 라이브러리가 있기 때문에 거의 모든 것들이 해결되는 요즘이지만,

뭔가 파이썬은 느리다는 대중의 시선(?)은 사라지지 않는듯 하다.


그래서!

ProtoBuf 를 활용해서 Go 언어의 성능을 파이썬에서 사용해보면 어떨까?

라는 질문에서 시작한 토이 프로젝트를 남겨본다.


전체적인 디렉토리 구조

뭔가 복잡해보이지만... 하나씩 해보면 금방 적응이 가능하다.


0. fib_proto_dir 생성 및 이동


1. fib.proto 파일 생성


syntax = "proto3";


package fibonacci;


option go_package = "david/fibpackage";


service FibonacciService {

    rpc CalculateSumOfFibonacci (FibonacciRequest) returns (FibonacciResponse) {}

}


message FibonacciRequest {

    int32 repeats = 1;

    int32 n = 2;

}


message FibonacciResponse {

    string result = 1;

}



2. pb 파일 생성하기 --> .go & .py 파일들이 생성된다.


>> protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative fib.proto

>> python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. fib.proto



3. fib.py 파일 생성


import grpc

import time

import fib_pb2

import fib_pb2_grpc


def run():

    with grpc.insecure_channel('localhost:50051') as channel:

        stub = fib_pb2_grpc.FibonacciServiceStub(channel)

        request = fib_pb2.FibonacciRequest(repeats=10000, n=10000)

        start_time = time.time()

        response = stub.CalculateSumOfFibonacci(request)

        end_time = time.time()

        print("Sum of Fibonacci repeats (gRPC):", response.result)

        print("Execution time (gRPC):", end_time - start_time, "seconds")


if __name__ == '__main__':

    run()



4. go.mod 생성


>> go mod init david/fibpackage

>> go mod tidy



현재까지 fib_proto_dir 에서 작업을 진행했고, 이제 상위 디렉토리로 이동 후에 나머지 작업을 마무리한다.


5. fib.go 파일 생성


package main


import (

    "context"

    "fmt"

    "log"

    "math/big"

    "net"


    pb "david/fibpackage"

    "google.golang.org/grpc"


)


type server struct {

    pb.UnimplementedFibonacciServiceServer

}


func fibonacci(n int) *big.Int {

    a, b := big.NewInt(0), big.NewInt(1)

    for i := 0; i < n; i++ {

        a.Add(a, b)

        a, b = b, a

    }

    return a

}


func calculateSumOfFibonacciRepeats(repeats int, n int) *big.Int {

    total := big.NewInt(0)

    for i := 0; i < repeats; i++ {

        total.Add(total, fibonacci(n))

    }

    return total

}


func (s *server) CalculateSumOfFibonacci(ctx context.Context, in *pb.FibonacciRequest) (*pb.FibonacciResponse, error) {

    total := calculateSumOfFibonacciRepeats(int(in.Repeats), int(in.N))

    return &pb.FibonacciResponse{Result: total.String()}, nil

}


func main() {

    lis, err := net.Listen("tcp", ":50051")

    if err != nil {

        log.Fatalf("failed to listen: %v", err)

    }

    s := grpc.NewServer()


    pb.RegisterFibonacciServiceServer(s, &server{})

    fmt.Println("Server is running on port 50051")

    if err := s.Serve(lis); err != nil {

        log.Fatalf("failed to serve: %v", err)

    }

}


GoLang 은 데충 코드 적어도 fmt 잘 되더라...

여기서! big int 를 사용하는데 이유는 fibonacci 결과가 너무 커서 오버플로우가 발생할 수 있어서!!

그래서! proto 파일에서도 return 결과의 타입을 string 으로 지정해 두었음!!


6. 4번 스텝에서 생성한 david/fibpackage 사용할 수 있도록 mod 생성


>> go mod init david/fibproject

>> go mod edit -replace david/fibpackage=./fib_proto_dir

>> go mod tidy



이게 끝!


성능비교를 해보면,

시나리오는 10_000 번의 피보나치 합을 구하면 되는데... 숫자가 엄청 크다...


먼저,

파이썬에서 실행하면

나름 극한의 테스트...

10_000번의 피보나치를 10_000회 실행...


결과는 약 33초!


이걸 grpc 통신을 통해서 GoLang 으로 돌려보면(분명 grpc 통신에서 생성되는 로드가 존재)


실행방법은


>> go run fib.go

>> python fib_proto_dir/fib.py


5초?!


(함정) 실제로 작은 변수로 돌렸을 때는 순수 파이썬이 더 빠를 수 있다.


이걸로 뭘 기대할 수 있을까?!...


ProtoBuf 도 배워야 하고,

GoLang 도 배워야 하고,

관련된 Python 도 배워야 하고...


그냥 numpy, pandas, torch 라이브러리를 열심히 공부해서 사용하는게 더 이득일 수 있겠다... ㅜ


즐거웠으면 그걸로 끝이다.

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari