Windows 10: Fastest way to move millions of tiny files from one directory to another
I have 16 subdirectories which all contain somewhere between 1m-1.5m files each (roughly 18m files in total), but I need all the files to be in a single directory. Each file is tiny (35-100 bytes each). The total combined size of the files is relatively small - around 600mb - but it appears to be the sheer amount of them that's causing the issues.
So far I've tried:
Windows move: Didn't even get started. It said it would take 'about a day' to calculate the move. Gave up after 2 hours of calculating
.
DOS move
: This works great for the first 500-600k files (moving around 10k files per second), but starts to slow down noticeably as it drags towards the million mark, doing about 100 files every 2 seconds.
7Zip: I've read suggestions that zipping up the entire folder and then extracting it in the destination would be WAY quicker; however using the GUI it just crashed explorer after a few minutes; using the command line was incredibly slow (100 files every few seconds)
DOS robocopy
: Having already moved ~1m files yesterday, I ran robocopy src_folder dest_folder *.log
just to shift the last of what was in the first directory. It took 27 minutes to move ~12k files.
No matter what method I choose, it seems that the number of files in the destination folder is what causes the issue. If there are more than a million files in the destination, the move/copy slows to an absolute crawl regardless of the method.
Any ideas on how to achieve this that won't take days/weeks? For reference it's on a single SSD on a single machine: 64-bit, 16gb RAM, 8 threads.
Solution 1:
This PowerShell script, which has been tested with many positive responses, invokes Robocopy
and is much faster; simply change a few parameters [destination, etc.] and you're good to go:
$max_jobs = 10
$tstart = get-date
$log = "C:\Robo\Logs"
$src = Read-Host -Prompt 'Source path'
if(! ($src.EndsWith("\") )){$src=$src + "\"}
$dest = Read-Host -Prompt 'Destination path'
if(! ($dest.EndsWith("\") )){$dest=$dest + "\"}
if((Test-Path -Path $src ))
{
if(!(Test-Path -Path $log )){New-Item -ItemType directory -Path $log}
if((Test-Path -Path $dest)){
robocopy $src $dest
$files = ls $src
$files | %{
$ScriptBlock = {
param($name, $src, $dest, $log)
$log += "\$name-$(get-date -f yyyy-MM-dd-mm-ss).log"
robocopy $src$name $dest$name /E /nfl /np /mt:16 /ndl > $log
Write-Host $src$name " completed"
}
$j = Get-Job -State "Running"
while ($j.count -ge $max_jobs)
{
Start-Sleep -Milliseconds 500
$j = Get-Job -State "Running"
}
Get-job -State "Completed" | Receive-job
Remove-job -State "Completed"
tart-Job $ScriptBlock -ArgumentList $_,$src,$dest,$log
}
While (Get-Job -State "Running") { Start-Sleep 2 }
Remove-Job -State "Completed"
Get-Job | Write-host
$tend = get-date
Cls
Echo 'Completed copy'
Echo 'From: $src'
Echo 'To: $Dest'
new-timespan -start $tstart -end $tend
} else {echo 'invalid Destination'}
} else {echo 'invalid Source'}