Why does 7zip mmt work better with PowerShell's new Parallel?

379 Views Asked by At

I have a PowerShell (v7.3.7) script that iterates through files and archives them using 7-Zip. When using 7-Zip's multi-threading option -mmt, the script takes 180 seconds to execute in my case. However, if I utilize ForEach-Object -Parallel with -ThrottleLimit 1, the execution time drops down to 50 seconds. On an interesting sidenote, altering the throttle limit to other numbers doesn't improve the execution time further, and in fact, increasing it beyond 50% of my logical cores leads to a longer execution time.

I would have thought that 7-Zip's own multi-threading would be sufficient to fully utilize my cores, but it seems only with -Parallel (even at -ThrottleLimit 1), 7-Zip starts using my cores efficiently. I have verified this by disabling -mmt (using -mmt=off), which brings the execution time back to its original 180 seconds.

I'm curious as to how PowerShell's -Parallel parameter affects 7-Zip's -mmt capability. I've been at this for a while with ChatGPT4 trying to rule things out, but we're both (heh) stumped. I suspect this has to be related to how -Parallel spins up a new runspace and somehow the underlying architecture has an effect on how cpus are made available for the script executed in this namespace. But it shouldn't.

Has anyone encountered a similar situation or have insights into why this might be happening?

Below is my script for reference:

param(
    [Parameter(Mandatory=$true)]
    [string]$name,
    [Parameter(Mandatory=$true)]
    [string]$password
)

# The 7-Zip executable path (modify if necessary)
$7zipPath = "C:\Program Files\7-Zip\7z.exe"

# Ensure 7-Zip is installed
if (!(Test-Path $7zipPath)) {
    throw "7-Zip not found at path $7zipPath"
}

$items = Get-ChildItem -Path .\ | Where-Object { $_.Extension -ne ".ps1" }

$timer = Measure-Command {
    $items | ForEach-Object -Parallel {
        $item = $_
        $name = $using:name
        $password = $using:password
        $7zipPath = $using:7zipPath

        $guid = [guid]::NewGuid().ToString()
        $zipFileWithoutPassword = "${name}_${guid}.zip"
        $zipFileWithPassword = "${name}${guid}.zip"

        # Create a non-password protected zip with no compression
        $argListNoPassword = "a -tzip ""$zipFileWithoutPassword"" ""$item"" -mx=0 -mmt"
        Start-Process -Wait -FilePath "$7zipPath" -ArgumentList $argListNoPassword -NoNewWindow

        # Add password protection to the zip using 7-Zip with AES256
        $argListWithPassword = "a -tzip ""$zipFileWithPassword"" ""$zipFileWithoutPassword"" -p$password -mem=AES256 -mx=0 -mmt"
        Start-Process -Wait -FilePath "$7zipPath" -ArgumentList $argListWithPassword -NoNewWindow

        # Remove non-password protected zip
        Remove-Item -Path $zipFileWithoutPassword
    } -ThrottleLimit 1
}

Write-Host "Total execution time: $($timer.TotalSeconds) seconds"

(Just save this to a .ps1 file and it will zip everything in the current folder when executed, excluding .ps1 files. Run it with: ./the_script.ps1 archive-entry somepassword.)

0

There are 0 best solutions below