개발언어/GO

성격 급한 사람을 위한 ‘빨리빨리 FileCopy, Move’ 제작기 (GoLang을 곁들인)

nomoreFt 2022. 6. 24. 17:51

성격 급한 사람을 위한 ‘빨리빨리 FileCopy, Move’ (GoLang을 곁들인)

개요

평소 윈도우에서 File들을 옮기거나 복사할 때, 속도가 느려서 답답할 때가 많았다.

그래서 불편함을 개선하고자 GoLang으로 `빨리빨리FileCopy,Move` 를 짜봤다.

  • GoLang 선정 이유
    • 고루틴, 채널이 있어서 병렬 처리가 간단하다.
      • 동기화 문제
        스레드를 명시적으로 주지 않고 고루틴을 생성하면 알아서 스레드를 생성해주고 적절한 스레드에 고루틴을 할당합니다. goroutine 사이의 커뮤니케이션을 채널이란 것을 통해서 소통하기 때문에, 따로 동기화 문제도 신경쓰지 않아도 된다.
      • 쓰레드 관리
        고루틴은 멀티스레드 메커니즘이지만 자체적인 스케줄러에 의해 관리되는 경량 스레드이며 OS에서 관리하는 경량 스레드보다 더 경량이다.
        따라서 고루틴은 CPU 코어수와 무관하게 수백, 수천의 고루틴을 작성해도 성능에 문제가 생기지 않는다.

Untitled

  • 속도가 빠르다
    • C++, C를 보완하여 나왔기 때문에 빠르다.
    • 대신 규칙이 좀 엄격하다. (선언하고 사용되지 않으면 에러표시가 뜬다)

Untitled 1Untitled 2

  • 캐릭터가 귀여움

Untitled 3


개발 과정

특정 Dir경로에서 다른 Dir 경로로 File을 옮기거나 복사하는 기능을 개발하기 위해, Go의 기본 Package중 하나인 os 를 사용했다.

Go는 기본 패키지의 기능이 강력한 편에 속해, 가져다 쓰면 생각보다 많은 것들을 할 수 있다.

os 패키지로 Dir도 만들고, 사용자 시간도 알고 Process도 찾고 여러가지 할 수 있지만, 여기서는 주로 파일을 다룬다.


💡 `os`의 의미가 운영체제를 의미하는지는 잘 모른다. 로버트 그리즈머, 롭 파이크, 케네스 톰슨 (**GoLang을 디자인한 세 명**)의 절대 권력으로, 지금도 패키지에 무엇을 포함할지는 이 세 사람이 만장일치로 합의해야 이뤄진다고 한다.

1. window 기본 기능 속도 측정

생각보다 윈도우의 기본 성능이 뛰어나서, 최대한 많은 파일로 테스트를 하려고 했다.

실험 조건

  • 테스트 파일
    • 10GB 파일 2개
    • 1GB 파일 7개
    • 1,049KB 파일 102개
  • 테스트 경로
    • 로컬 D:\test\srcDir
    • 로컬 D:\test\destDir

위의 파일을 복사, 이동하는데 어느정도 걸리는가 관건이었다.

  • 이동/복사 측정 기준
    • 윈도우가 지정해준 방법 (Ctrl + C 는 복사, Ctrl + X 는 이동이라 인식한다.)
    • Untitled 5
    • Untitled 4
    • 시간 측정변덕쟁이 Window 탐색기에게 시간을 맡길 수 없었다.
    • 변덕쟁이 Window 탐색기에게 시간을 맡길 수 없었다.

결과

  • 실패다운 실패
  파일 Move 파일 Copy
걸린 시간 0 46.36초

파일 Move가 0초가 걸리기에 뭔가 싶었다. (어리석음 + 1)

같은 디스크라 옮길 시간이 안걸리나 싶어 경로를 수정해줬다.

  • 수정된 테스트 경로
    • D:\test\srcDir
    • C:\test\destDir
  • 성공적인 성공
  파일 Move 파일 Copy
걸린 시간 47.73 46.36초

빨리빨리FileCopy,Move 성능 테스트

그냥 main()에서 실행시켜서 속도만 체크하려다가, 하다보니 api서버를 만들어서 요청받아 작업을 수행했다.

  • 서버를 킨다.

Untitled 7

결과

  파일 Move 파일 Copy
걸린 시간 46초 45초

무의미한 결과가 나왔다. 테스트 파일 Set이 잘못된듯하다.

10GB 파일 2개 의 속도만큼 걸리는 것을 확인했다.


결론

노력을 한다고 항상 최선의 결과가 나오지는 않는다는 것을 배웠다.

테스트셋을 더 늘려서 실험해보면 좋을 것 같다.

소박한 장점

  • 장점으로는 빨리빨리FileCopy,Move 는 병렬처리로 모든 파일이 동시에 작업에 들어간다고 보면 된다.
  • 그래서 가장 오래 걸리는 파일만큼의 시간만 들이면 그 외 나머지 파일들은 이미 작업이 완료.

병렬처리라 순서는 고루틴 맘대로다.

병렬처리라 순서는 고루틴 맘대로다.

  • 기존 Window 탐색기의 기본 기능은 중간에 하나 걸리면 차례대로 작업을 수행한다.

23프로나 완료됐는데 고작 1,049KB 하나 넘어온 모습. 큰 파일에 막혀있어 병목

23프로나 완료됐는데 고작 1,049KB 하나 넘어온 모습. 큰 파일에 막혀있어 병목

전투 빨리빨리FileCopy,Move vs FastCopy

이대로 끝내기는 아쉬워서 유명한 복사 프로그램인 FastCopy와 한판 붙어보기로 했다.

Untitled 10

  • 구식의 UI를 보니 이건 질 수가 없겠다는 생각이 든다.

Untitled 11

결과

Fast라는 이름이 붙은 것들에는 이유가 있다고 생각한다.

Untitled 12

39.8초. 그렇지만 병렬 처리가 아닌 순차처리방식이다. 시간에는 졌지만, 일방적인 패배는 아니다.


전체 소스코드

main.go

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"

    "github.com/julienschmidt/httprouter"
    customFileExplorer "github.com/nomoreFt/woodieFileControl"
)

func main() {
    router := httprouter.New()
    //outer.GET("/", Index)
    router.GET("/copyDir", copyFile)
    router.GET("/moveDir", Index)

    log.Fatal(http.ListenAndServe(":8080", router))

    nouse := "test"

    fmt.Print("응 못해" + nouse)

}
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    srcDir := "D://test/srcDir"
    destDir := "C://test/destDir"

    fmt.Fprintf(w, srcDir+"에서 "+destDir+"으로 파일 Copy가 진행중입니다.")
    c := make(chan string)

    files, err := ioutil.ReadDir(srcDir)
    if err != nil {
        fmt.Println(w, err.Error())
    }
    for _, file := range files {
        go customFileExplorer.MoveFile(file.Name(), srcDir, destDir, c)
        file.Name()
        //go customFileExplorer.CopyFile(srcDir+"/"+file.Name(), destDir+"/"+file.Name(), c)

    }
    for i := 0; i < len(files); i++ {
        result := <-c
        fmt.Println(w, result)
    }
}
func copyFile(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    srcDir := "D://test/srcDir"
    destDir := "D://test/destDir"

    fmt.Fprintf(w, srcDir+"에서 "+destDir+"으로 파일 Copy가 진행중입니다.")
    c := make(chan string)

    files, err := ioutil.ReadDir(srcDir)
    if err != nil {
        fmt.Println(w, err.Error())
    }
    for _, file := range files {
        //go customFileExplorer.MoveFile(file.Name(), srcDir, destDir, c)
        file.Name()
        go customFileExplorer.CopyFile(srcDir+"/"+file.Name(), destDir+"/"+file.Name(), c)

    }
    for i := 0; i < len(files); i++ {
        result := <-c
        fmt.Println(w, result)
    }
}
  • 확장성 부분 (GitHub Import)
  • Untitled 13
  • Go는 go get URL 명령어를 이용하여 손쉽게 라이브러리를 받아, 사용할 수 있다.
  • import ( "fmt" "io/ioutil" "log" "net/http" "github.com/julienschmidt/httprouter" customFileExplorer "github.com/nomoreFt/woodieFileControl" )
  • router 서버
func main() {
    router := httprouter.New()
    //outer.GET("/", Index)
    router.GET("/copyDir", copyFile)
    router.GET("/moveDir", Index)

    log.Fatal(http.ListenAndServe(":8080", router))

앞에 말한 gitHub Import로 julienschmidt 줄리엔슈미츠씨가 만든 router를 낼름 써서 서버를 구현

  • go routin, channel
c := make(chan string)

for _, file := range files {
        **go** customFileExplorer.MoveFile(file.Name(), srcDir, destDir, c)
        file.Name()
        //go customFileExplorer.CopyFile(srcDir+"/"+file.Name(), destDir+"/"+file.Name(), c)

    }
    for i := 0; i < len(files); i++ {
        result := <-c
        fmt.Println(w, result)
    }

앞에 go만 붙이면, 앞서 언급한 병렬성을 손쉽게 얻을 수 있다.

c := make(chan string) : go루틴끼리 소통하는 통로 생성

go customFileExplorer.MoveFile(file.Name(), srcDir, destDir, c) : go 를 붙여서 병렬처리를 한다고 선언

github.com/nomoreFt/woodieFileControl

package woodieFileExplorer

import (
    "io"
    "log"
    "os"
)

func CopyFile(srcFileNm, destFileNm string, c chan string) {

    fin, err := os.Open(srcFileNm)
    if err != nil {
        log.Fatal(err)
    }
    defer fin.Close()

    fout, err := os.Create(destFileNm)
    if err != nil {
        log.Fatal(err)
    }
    defer fout.Close()

    _, err = io.Copy(fout, fin)

    if err != nil {
        log.Fatal(err)
    }
    c <- srcFileNm + " is copied"
}
func MoveFile(fileNm string, srcDir string, destDir string, c chan string) {

    oldLocation := srcDir + "/" + fileNm
    newLocation := destDir + "/" + fileNm
    err := os.Rename(oldLocation, newLocation)

    if err != nil {
        log.Fatal(err)
    }
    c <- fileNm + " is moved"
}
  • Go는 main, main package가 아니면 다 가져다 쓸 수 있게 하라는줄 안다. ( public인 경우에만)
    • Go는 private, public의 기준을 맨 앞 글자가 대문자인지, 소문자인지로 구분한다.
    func MoveFile(fileNm string, srcDir string, destDir string, c chan string) : MoveFile M이 대문자라 가져다 쓰라는 public