brunch

Python: Go with Proto

#python #GoLang #ProtoBuf #Performance

by 유윤식

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


스크린샷 2024-06-04 22.48.15.png

5초?!


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


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


ProtoBuf 도 배워야 하고,

GoLang 도 배워야 하고,

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


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


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

keyword
작가의 이전글Python: Walrus 연산자