성격 급한 사람을 위한 ‘빨리빨리 FileCopy, Move’ (GoLang을 곁들인)
개요
평소 윈도우에서 File들을 옮기거나 복사할 때, 속도가 느려서 답답할 때가 많았다.
그래서 불편함을 개선하고자 GoLang으로 `빨리빨리FileCopy,Move` 를 짜봤다.
- GoLang 선정 이유
- 고루틴, 채널이 있어서 병렬 처리가 간단하다.
- 동기화 문제
스레드를 명시적으로 주지 않고 고루틴을 생성하면 알아서 스레드를 생성해주고 적절한 스레드에 고루틴을 할당합니다. goroutine 사이의 커뮤니케이션을 채널이란 것을 통해서 소통하기 때문에, 따로 동기화 문제도 신경쓰지 않아도 된다. - 쓰레드 관리
고루틴은 멀티스레드 메커니즘이지만 자체적인 스케줄러에 의해 관리되는 경량 스레드이며 OS에서 관리하는 경량 스레드보다 더 경량이다.
따라서 고루틴은 CPU 코어수와 무관하게 수백, 수천의 고루틴을 작성해도 성능에 문제가 생기지 않는다.
- 동기화 문제
- 고루틴, 채널이 있어서 병렬 처리가 간단하다.
- 속도가 빠르다
- C++, C를 보완하여 나왔기 때문에 빠르다.
- 대신 규칙이 좀 엄격하다. (선언하고 사용되지 않으면 에러표시가 뜬다)
- ❣캐릭터가 귀여움
개발 과정
특정 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 는 이동이라 인식한다.)
-
-
- 시간 측정변덕쟁이 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서버를 만들어서 요청받아 작업을 수행했다.
- 서버를 킨다.
- 요청을 날려본다.
- http://localhost:8080/copyDir : 파일 카피
- http://localhost:8080/moveDir : 파일 무브
결과
파일 Move | 파일 Copy | |
---|---|---|
걸린 시간 | 46초 | 45초 |
무의미한 결과가 나왔다. 테스트 파일 Set이 잘못된듯하다.
10GB 파일 2개
의 속도만큼 걸리는 것을 확인했다.
결론
노력을 한다고 항상 최선의 결과가 나오지는 않는다는 것을 배웠다.
테스트셋을 더 늘려서 실험해보면 좋을 것 같다.
소박한 장점
- 장점으로는
빨리빨리FileCopy,Move
는 병렬처리로 모든 파일이 동시에 작업에 들어간다고 보면 된다. - 그래서 가장 오래 걸리는 파일만큼의 시간만 들이면 그 외 나머지 파일들은 이미 작업이 완료.
병렬처리라 순서는 고루틴 맘대로다.
기존 Window 탐색기
의 기본 기능은 중간에 하나 걸리면 차례대로 작업을 수행한다.
23프로나 완료됐는데 고작 1,049KB 하나 넘어온 모습. 큰 파일에 막혀있어 병목
전투
빨리빨리FileCopy,Move
vsFastCopy
이대로 끝내기는 아쉬워서 유명한 복사 프로그램인 FastCopy
와 한판 붙어보기로 했다.
구식의 UI를 보니 이건 질 수가 없겠다는 생각이 든다.
결과
Fast라는 이름이 붙은 것들에는 이유가 있다고 생각한다.
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)
-
- 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 를 붙여서 병렬처리를 한다고 선언
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