【问题标题】:io.Copy is a lot slower than calling the mv commandio.Copy 比调用 mv 命令慢很多
【发布时间】:2018-07-14 03:29:13
【问题描述】:

看来 io.Copy 对我来说很慢:

   _,err = io.Copy(destf,srcf)

io.Copy 需要更长的时间,大约 2 分钟才能将 1GB 文件复制到网络共享。注意到 mv.exe 最多在约 25 秒内完成工作 - 所以我开始为我的工作唤起 mv 。

    output, err := exec.Command("mv", src, dest_folder).CombinedOutput()

这种缓慢在我身上始终可以重现,任何有关如何加快速度的提示将不胜感激!

更新:

感谢使用io.CopyBuffer() 的建议,但mv.exe 仍然以可观的优势成为唯一的胜利者。

详情:

PS C:\temp> .\move_files.exe .\testfile.data "\\somehost\somefolder\bleh13.txt"
2018/07/14 19:04:54 Created C:\Temp\2\deleteME__913153343, copy of .\testfile.data, Size: 1073745920 bytes
2018/07/14 19:05:55 Transfer with io.Copy() took us 60.836702 seconds
2018/07/14 19:06:47 Transfer with io.CopyBuffer() took us 50.729625 seconds
2018/07/14 19:06:59 Transfer with mv command took us 11.470456 seconds
PS C:\temp>

欢迎您亲自尝试:https://play.golang.org/p/2_lR83A4BXe

【问题讨论】:

  • 很可能mv.exe 使用了一些本机 NT 系统调用(应该知道 SMB{2|3} 协议)来简化上传。我会使用procmon 来跟踪它用于复制文件的 Win32 API 调用,然后考虑在你的 Go 程序中为 in 创建一个简单的包装器。
  • 我认为是CopyFileCopyFileEx

标签: windows go


【解决方案1】:

你比较了复制和移动,它们是不同的东西。移动文件并不假定将所有数据的副本写入新的物理内存目标,只是重写文件系统中的一些标头/描述符。所以为了适当的比较基准

【讨论】:

  • mv 相当于 cp + rm,当在不同的磁盘上完成时。在同一分区 mv 几乎是即时的。在 OP 示例中,mv 需要 26 秒,因此不太可能在同一个分区上。
【解决方案2】:

继续我的评论建议尝试CopyFile Win32 API 函数,这是一个包装它的工作程序:

package main

import (
    "log"
    "os"
    "syscall"
    "unsafe"
)

var (
    kernel32     = syscall.MustLoadDLL("kernel32.dll")
    copyFileProc = kernel32.MustFindProc("CopyFileW")
)

func CopyFile(src, dst string, overwrite bool) error {
    srcW := syscall.StringToUTF16(src)
    dstW := syscall.StringToUTF16(dst)

    var failIfExists uintptr
    if overwrite {
        failIfExists = 0
    } else {
        failIfExists = 1
    }

    rc, _, err := copyFileProc.Call(
        uintptr(unsafe.Pointer(&srcW[0])),
        uintptr(unsafe.Pointer(&dstW[0])),
        failIfExists)
    if rc == 0 {
        return &os.PathError{
            Op:   "CopyFile",
            Path: src,
            Err:  err,
        }
    }
    return nil
}

func main() {
    log.SetFlags(0)

    if len(os.Args) != 3 {
        log.Fatalf("Wrong # args.\nUsage: %s SOURCE DEST\n", os.Args[0])
    }

    err := CopyFile(os.Args[1], os.Args[2], false)
    if err != nil {
        log.Fatal("error copying file: ", err)
    }
}

请尝试一下,看看它是否比io.Copy*有所改进。

【讨论】:

  • 有趣的建议,可能比我的回答更合适。 +1
  • @VonC,谢谢!我的推理实际上是从考虑 SMB2+ 支持服务器端文件复制开始的——相对现代的 Windows (Win8+) 版本使 透明 使用它(即它适用于 Windows 资源管理器 shell 操作)。因此,这些 API 调用理论上可能有一些专门的逻辑,适用于一侧也是 SMB 端点的情况。
  • 哇!这太棒了!大约12秒完成!太棒了!
【解决方案3】:

也许试试io.CopyBufferio.Copy 使用buffer of 32*1024 bytes

检查更大或更小的缓冲区大小是否会产生影响。

【讨论】:

  • 请查看有问题的更新,io.CopyBuffer() 稍微好一点,但原生 mv 表现最好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-15
  • 1970-01-01
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 2014-12-26
相关资源
最近更新 更多