4kb Markdown Files Benchmark

Time to build 4096 markdown files

Last built at 2026-02-11T03:49:29 running @ 2800 Mhz

Deploy

build.with.powershell.fast (0.54s)
Build Script

param(
[Alias('Input')]
[string]
$InputPath = "$psScriptRoot/TestMarkdown",

[string]
$OutputPath = "$psScriptRoot/PowerShellFast"
)


$mdPipelineBuilder = [Markdig.MarkdownPipelineBuilder]::new()
$mdPipeline = [Markdig.MarkdownExtensions]::UsePipeTables($mdPipelineBuilder).Build()

foreach ($fullPath in [IO.Directory]::EnumerateFileSystemEntries($InputPath)) {
    $fileHtml = [Markdig.Markdown]::ToHtml(
        [IO.File]::ReadAllText($fullPath), $mdPipeline
    )
    
    $fileName = @($fullPath -split '[\\/]')[-1]
    
    $directoryPath = $OutputPath, ($fileName -replace '\.md$') -join '/'

    if (-not [IO.Directory]::Exists($directoryPath)) {
        $null = [IO.Directory]::CreateDirectory($directoryPath)
    }

    $fileOutputPath = $OutputPath, ($fileName -replace '\.md$'), 'index.html' -join '/'
    
    [IO.File]::WriteAllText($fileOutputPath, $fileHtml)    
}

build.with.powershell.ConvertFrom-Markdown (2.49s)
Build Script

param(
[Alias('Input')]
[string]
$InputPath = "$psScriptRoot/TestMarkdown",

[string]
$OutputPath = "$psScriptRoot/PowerShellConvertFromMarkdown"
)


foreach ($file in Get-ChildItem -Path $InputPath -File) {
    
    $fileHtml = (ConvertFrom-Markdown -LiteralPath $file.FullName).Html
    
    $fileName = $file.Name
    
    $directoryPath = $OutputPath, ($fileName -replace '\.md$') -join '/'

    if (-not [IO.Directory]::Exists($directoryPath)) {
        $null = [IO.Directory]::CreateDirectory($directoryPath)
    }

    $fileOutputPath = $OutputPath, ($fileName -replace '\.md$'), 'index.html' -join '/'
    
    [IO.File]::WriteAllText($fileOutputPath, $fileHtml)    
}

build.with.powershell.New-Item (2.54s)
Build Script

param(
[Alias('Input')]
[string]
$InputPath = "$psScriptRoot/TestMarkdown",

[string]
$OutputPath = "$psScriptRoot/PowerShellSlowest"
)


foreach ($file in Get-ChildItem -Path $InputPath -File) {
    
    $fileHtml = (ConvertFrom-Markdown -LiteralPath $file.FullName).Html
    
    $fileName = $file.Name
        
    $fileOutputPath = 
        Join-Path $OutputPath ($fileName -replace '\.md$') | 
            Join-Path -ChildPath ('index.html')

    New-Item -ItemType File -Path $fileOutputPath -Value $fileHtml -Force    
}

build.with.powershell.Foreach-Object (2.82s)
Build Script

param(
[Alias('Input')]
[string]
$InputPath = "$psScriptRoot/TestMarkdown",

[string]
$OutputPath = "$psScriptRoot/PowerShellForeachObject"
)


Get-ChildItem -Path $InputPath -File |
    Foreach-Object {
        $in = $_
        New-Item -ItemType File -Path (
            Join-Path $OutputPath ($in.Name -replace '\.md$') | 
            Join-Path -ChildPath ./index.html
        ) -Value (
            ($in | 
                ConvertFrom-Markdown | 
                Select-Object -ExpandProperty html) 
        ) -Force
    }

build.with.powershell.markx (6.92s)
Build Script

param(
[Alias('Input')]
[string]
$InputPath = "$psScriptRoot/TestMarkdown",

[string]
$OutputPath = "$psScriptRoot/PowerShellMarkX"
)


foreach ($fullPath in [IO.Directory]::EnumerateFileSystemEntries($InputPath)) {
    $markx = markx ([IO.File]::ReadAllText($fullPath))
    
    $fileName = @($fullPath -split '[\\/]')[-1]
    
    $directoryPath = $OutputPath, ($fileName -replace '\.md$') -join '/'

    if (-not [IO.Directory]::Exists($directoryPath)) {
        $null = [IO.Directory]::CreateDirectory($directoryPath)
    }

    $fileOutputPath = $OutputPath, ($fileName -replace '\.md$'), 'index.html' -join '/'
    
    [IO.File]::WriteAllText($fileOutputPath, $markx.html)    
}

build.with.11ty (7.81s)
Build Script

<#
.SYNOPSIS
    Builds 4kb markdown files with 11ty
.DESCRIPTION
    Builds 4kb markdown files with 11ty.
    
#>
Push-Location $PSScriptRoot

npx @11ty/eleventy --input=./TestMarkdown/*.md --output ./11ty/

Pop-Location


build.with.astro (92.08s)
Build Script

<#
.SYNOPSIS
    Builds 4kb markdown files with astro
.DESCRIPTION
    Builds 4kb markdown files with astro.    
#>
param(
[Alias('Input')]
[string]
$InputPath = "$psScriptRoot/TestMarkdown",

[string]
$OutputPath = "$psScriptRoot/astro",

[string]
$AstroProjectRoot = "$psScriptRoot/astrodev"
)


if (-not (Test-path $AstroProjectRoot)) {
    $null = New-Item -ItemType Directory -Path $AstroProjectRoot -Force
}

Push-Location $AstroProjectRoot

@"
// @ts-check
import { defineConfig } from 'astro/config';

// https://astro.build/config
export default defineConfig($(
    [ordered]@{
        outDir = $OutputPath
    } | ConvertTo-Json
));
"@ > ./astro.config.mjs

npm run build

Pop-Location


The Numbers

TechniqueTimeRelativeSpeed
build.with.powershell.fast00:00:00.54054250.0058701662307210035
build.with.powershell.ConvertFrom-Markdown00:00:02.48896660.027029600234417223
build.with.powershell.New-Item00:00:02.54116750.027596489906169662
build.with.powershell.Foreach-Object00:00:02.82348220.030662362096378813
build.with.powershell.markx00:00:06.91652510.0751118590600262
build.with.11ty00:00:07.80554450.08476640363392343
build.with.astro00:01:32.08299711
View Source

<#
.SYNOPSIS
    Builds 4kb Markdown files
.DESCRIPTION
    Builds 4kb Markdown files N different ways, in order to test performance.
.NOTES
    In the interests of fair play, any prerequisities should be installed before builds are timed.    
#>
param(
[uri]
$BuildTimeHistoryUrl = "https://4kb.powershellweb.com/history.json"
)

# Make sure we're in the right place.
Push-Location $PSScriptRoot

$initStart = [DateTime]::Now

#region Install Prereqs
if ($env:GITHUB_WORKFLOW) {
    # Install 11ty to reduce 11ty build time
    $null = sudo npm install -g '@11ty/eleventy'    

    #region Astro Initialization
    # Install Astro to reduce astro build time
    $null = sudo npm install -g 'astro'

    $astroDevRoot = Join-Path $PSScriptRoot "astrodev"
    New-Item -ItemType File -Path (
        Join-Path $astroDevRoot package.json
    ) -Force -Value (    
        [Ordered]@{
            name='astro-test'
            type='module'
            version='0.0.1'
            scripts = [Ordered]@{
                "build" = "astro build"
            }
            dependencies = @{
                "astro" = 'latest'
            }
        } | ConvertTo-Json 
    )

    Push-Location $astroDevRoot

    npm install | Out-Host

    $pagesRoot = Join-Path $astroDevRoot "src/pages"
    if (-not (Test-Path $pagesRoot)) {
        New-Item -ItemType Directory -Path $pagesRoot
    }

    Copy-Item ../TestMarkdown/* $pagesRoot  

    Pop-Location

    #endregion Astro Initialization

    Install-Module MarkX -Force

    Import-Module MarkX -Global
}
$initEnd = [DateTime]::Now
#endregion Install Prereqs

#region Get Clock Speed
$cpuSpeed = 
    if ($executionContext.SessionState.PSVariable.Get('IsLinux').Value) {
        Get-Content /proc/cpuinfo -Raw -ErrorAction SilentlyContinue | 
            Select-String "(?<Unit>Mhz|MIPS)\s+\:\s+(?<Value>[\d\.]+)" | 
            Select-Object -First 1 -ExpandProperty Matches |
            ForEach-Object {
                $_.Groups["Value"].Value -as [int]
            }
    } elseif ($executionContext.SessionState.PSVariable.Get('IsMacOS').Value) {
        (sysctl -n hw.cpufrequency) / 1e6 -as [int]
    } else {
        $getCimInstance = $ExecutionContext.SessionState.InvokeCommand.GetCommand('Get-CimInstance','Cmdlet')
        if ($getCimInstance) {
            & $getCimInstance -Class Win32_Processor |
                Select-Object -ExpandProperty MaxClockSpeed
        }
    }
#endregion

$mySelf = $MyInvocation.MyCommand.ScriptBlock
$StartTime = [DateTime]::Now

& {
    foreach ($file in Get-ChildItem -filter build.with.*.ps1) {
        $techniqueName = $file.Name -replace '\.build\.with' -replace '\.ps1$'
        $script = (Get-Command $file.FullName -CommandType ExternalScript).ScriptBlock
        $time = Measure-Command { . $file.FullName } 
        [PSCustomObject]@{
            Technique = $techniqueName
            Time = $time
            Script = $script
        }
    }
} | Tee-Object -Variable buildTimes


$buildTimes = $buildTimes | Sort-Object Time

foreach ($buildTime in $buildTimes) {
    $relativeSpeed = $buildTime.Time.TotalMilliseconds / $buildTimes[-1].Time.TotalMilliseconds
    Add-Member NoteProperty -InputObject $buildTime -Name RelativeSpeed $relativeSpeed -Force
    Add-Member NoteProperty -InputObject $buildTime -Name DateTime $StartTime -Force
}

$history = @(try {
    Invoke-RestMethod -Uri $BuildTimeHistoryUrl -ErrorAction Ignore
} catch {}) -ne $null

$buildTimes | ConvertTo-Html -Title BuildTimes > ./times.html


$descriptionMessage = @(foreach ($buildtime in $buildTimes) {    
    ($buildTime.Technique, $buildTime.Time -join ':')    
}) -join [Environment]::NewLine

@(
    "<html>"
    
    "<head>"    
    
    "<title>4kb Markdown Files</title>"
    
    "<meta name='viewport' content='width=device-width, initial-scale=1, minimum-scale=1.0' />"

    "<meta name='og:title' content='4kb Markdown Files Benchmark' />"
    
    "<meta name='og:description' content='
4kb Markdown Files Benchmark. 
The fastest framework is no framework.
$([Web.HttpUtility]::HtmlAttributeEncode($descriptionMessage))
' />"

    "<meta name='article:published_time' content='$($StartTime.ToString('s'))' />"

    "<style>"
    
    "
    
    body { height: 100vh; max-width: 100vw; margin:0 } 
    
    svg { height: 5%; }
    h1,h2, h3,h4 { text-align: center; }
    .techniqueSummary { font-size: 2rem; }

    "
    "</style>"
    "</head>"
    "<body>"
    "<h1>4kb Markdown Files Benchmark</h1>"
    "<h2>Time to build 4096 markdown files</h2>"    
    "<h3>Last built at $([DateTime]::UtcNow.ToString("s")) running @ $cpuSpeed Mhz</h3>"
    "<h4><a href='https://github.com/PowerShellWeb/4kbMarkdownFiles/'><button>Github Repo</button></a></h4>"
    
    "<h4>"    
    "[![Deploy](https://github.com/PowerShellWeb/4kbMarkdownFiles/actions/workflows/deploy.yml/badge.svg)](https://github.com/PowerShellWeb/4kbMarkdownFiles/actions/workflows/deploy.yml)" |
        ConvertFrom-Markdown |
        Select-Object -ExpandProperty Html
    "</h4>"
    
    # Make a really basic heatmap (yes, this is that easy)
    # We want the fastest item to be the most green, so start by getting its relative speed
    $fastestRelativeSpeed = $buildTimes[0].RelativeSpeed
    foreach ($buildTime in $buildTimes) {
        # We only neeed two colors for the heatmap: green and red.
        # Green is calculated by:
        $green = [byte][Math]::Floor(
            # Starting at one
            (1 -             
                # Subtracting the relative speed
                $buildTime.RelativeSpeed + 
                # and adding the fastest relative speed.
                $fastestRelativeSpeed                
            ) * 255  # (multiply by 255 and floor it to make a byte)
        )
        # Red is even easier, it's just the relative speed as a byte
        $red = [byte][Math]::Floor(
            $buildTime.RelativeSpeed * 255  
        )

        # Use a bit of C# string formatting to make a color (blue is always blank)
        $color = "#{0:x2}{1:x2}00" -f $red, $green

        "<details open>"
            "<summary class='techniqueSummary'>$($buildTime.Technique) ($([Math]::Round(
                $buildTime.Time.TotalSeconds, 2
            ))s)</summary>"
            "<details>"
            "<summary>Build Script</summary>"
            "<pre><code class='langauge-PowerShell'>"
            [Web.HttpUtility]::HtmlEncode($buildTime.Script)
            "</code></pre>"
            "</details>"
            "<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'>"
                "<rect x='0%' width='1%' height='100%' fill='$color' stroke='currentColor'>"
                    "<animate attributeName='width' from='1%' to='100%' dur='$($buildTime.Time.TotalSeconds)s' fill='freeze' />"
                "</rect>"
            "</svg>"
        "</details>"
    }
    
    "<h3>The Numbers</h3>"
    $buildTimes | 
        Select-Object Technique, Time, RelativeSpeed | 
        ConvertTo-Html -Fragment
    "<details>"
        "<summary>View Source</summary>"
        "<pre><code class='language-PowerShell'>"
        [Web.HttpUtility]::HtmlEncode("$mySelf")
        "</code></pre>"
    "</details>"
    "</body>"
    "</html>"
) > ./index.html

foreach ($buildTime in $buildTimes) {
    Add-Member NoteProperty -InputObject $buildTime -Name Time "$($buildTime.Time)" -Force
}

$history = @(try {
    Invoke-RestMethod -Uri $BuildTimeHistoryUrl -ErrorAction Ignore
} catch {}) -ne $null

$history += $buildTimes | 
    Select-Object Technique, Time, RelativeSpeed, DateTime

ConvertTo-Json -InputObject $history > ./history.json -Depth 2

Remove-Item -Recurse -Force ./astrodev

Pop-Location