PowerShell 配列への追加・要素変更のスピード比較 Collections・固定配列

## 概要
PowerShellでサーバのDBからとってきた大量データを加工してCSV出力するときに、配列にデータ追加や要素変更をする必要があったので、PowerShellで使える配列のスピードを比較検証してみました。文字列で検証します。

```PowerShell:やること
# 追加(System.Collections.ArrayList、System.Collections.Generic.List)
$array = New-Object System.Collections.ArrayList
$array = New-Object System.Collections.Generic.List
$array.Add("test1")

# 追加(固定配列)
$array = @()
$array += "test1"

# 変更(共通)
$array[$i] += ",test2,test3"
```

## 結果
| 追加 | ArrayList | Generic.List | 固定配列 |
|:-:|:-:|:-:|:-:|
| 5,000回 |  0.001秒 |  0.001秒 |  0.55秒 |
| 10,000回 |  0.001秒 |  0.001秒 |  2.37秒 |
| 30,000回 | 0.007秒  |  0.003秒 |  27.75秒 |
| 500,000回 |  0.16秒 |  0.11秒 |  --- |
| 2,000,000回 |  3.59秒 |  3.74秒 |  --- |

| 変更 | ArrayList | Generic.List | 固定配列 |
|:-:|:-:|:-:|:-:|
| 5,000回 | 0.003秒  | 0.0016秒  | 0.0016秒  |
| 10,000回 | 0.0016秒  |  0.0104秒 | 0.0014秒  |
| 30,000回 |  0.0072秒 |  0.011秒 | 0.0108秒  |
| 500,000回 |  0.14秒 | 0.23秒  | ---  |
| 2,000,000回 |  4.95秒 |  5.22秒 |  --- |

追加に関しては、固定配列は回数が増えると指数関数的に所要時間が増えていきました。500,000回以降は固定配列の所要時間を検証していません。それに対してCollectionsのArrayList、Generic.Listは追加する回数が増えても、指数関数的にはならず、かなり早いと思いました。

変更に関しては、すべての配列に大差なさそうです。

## 検証内容
* 対象の配列(3種類)
固定配列、System.Collections.ArrayList、System.Collections.Generic.List
* 追加・要素変更回数(5種類)
5,000回、10,000回、30,000回、500,000回、2,000,000回

* 平均をとるために繰り返す回数
5回

## 環境
Windows 10 21H2
CPU Corei7-3770S Memory 16GB

## コード
```test.ps1
# ArrayList(addで追加)
function ArrayListFunc([int]$count, [ref][long]$addTime, [ref][long]$changeTime) {
    $array = New-Object System.Collections.ArrayList
    $p1 = Get-Date
    # 追加
    for ($i=0; $i -lt $count; $i++){
        # $null = $array.Add("test1")
        $array.Add("test1")
    }
    $p2 = Get-Date
    # 変更
    for ($i=0; $i -lt $count; $i++){
        # $null = $array[$i] += ",test2,test3"
        $array[$i] += ",test2,test3"
    }
    $p3 = Get-Date
    # 計算
    $temp = $p2 - $p1
    $addTime.Value = [math]::Floor($temp.TotalMilliseconds)
    $temp = $p3 - $p2
    $changeTime.Value = [math]::Floor($temp.TotalMilliseconds)
}

# Generic.List(addで追加)
function GenericListFunc([int]$count, [ref][long]$addTime, [ref][long]$changeTime) {
    $array = New-Object System.Collections.Generic.List[string]
    $p1 = Get-Date
    # 追加
    for ($i=0; $i -lt $count; $i++){
        # $null = $array.Add("test1")
        $array.Add("test1")
    }
    $p2 = Get-Date
    # 変更
    for ($i=0; $i -lt $count; $i++){
        # $null = $array[$i] += ",test2,test3"
        $array[$i] += ",test2,test3"
    }
    $p3 = Get-Date
    # 計算
    $temp = $p2 - $p1
    $addTime.Value = [math]::Floor($temp.TotalMilliseconds)
    $temp = $p3 - $p2
    $changeTime.Value = [math]::Floor($temp.TotalMilliseconds)
}

# 固定配列(+=で追加)
function ArrayFunc([int]$count, [ref][long]$addTime, [ref][long]$changeTime) {
    $array = @()
    $p1 = Get-Date
    # 追加
    for ($i=0; $i -lt $count; $i++){
        # $null = $array += "test1"
        $array += "test1"
    }
    $p2 = Get-Date
    # 変更
    for ($i=0; $i -lt $count; $i++){
        # $null = $array[$i] += ",test2,test3"
        $array[$i] += ",test2,test3"
    }
    $p3 = Get-Date
    # 計算
    $temp = $p2 - $p1
    $addTime.Value = [math]::Floor($temp.TotalMilliseconds)
    $temp = $p3 - $p2
    $changeTime.Value = [math]::Floor($temp.TotalMilliseconds)
}

# 検証用関数
function TestFunc([int]$count, [int]$repeat, [ref]$addTimeArray, [ref]$changeTimeArray, `
                $isTestArrayList, $isTestGenericList, $isTestArray) {

    $addTime = 0
    $addTimeTotal_ArrayList = 0
    $addTimeTotal_GenericList = 0
    $addTimeTotal_Array = 0
    $changeTiem = 0
    $changeTimeTotal_ArrayList = 0
    $changeTimeTotal_GenericList = 0
    $changeTimeTotal_Array = 0

    for ($i=0; $i -lt $repeat; $i++) {
        if ($isTestArrayList) {
            ArrayListFunc $count ([ref]$addTime) ([ref]$changeTiem)
            $addTimeTotal_ArrayList += $addTime
            $changeTimeTotal_ArrayList += $changeTiem
        }
        
        if ($isTestGenericList) {
            GenericListFunc $count ([ref]$addTime) ([ref]$changeTiem)
            $addTimeTotal_GenericList += $addTime
            $changeTimeTotal_GenericList += $changeTiem
        }
       
        if ($isTestArray) {
            ArrayFunc $count ([ref]$addTime) ([ref]$changeTiem)
            $addTimeTotal_Array += $addTime
            $changeTimeTotal_Array += $changeTiem
        }
    }

    $addTimeArray.Value += 
    [String]($addTimeTotal_ArrayList / 1000 / $repeat) + "`t" `
    + [String]($addTimeTotal_GenericList / 1000 / $repeat) + "`t" `
    + [String]($addTimeTotal_Array / 1000 / $repeat)

    $changeTimeArray.Value += 
    [String]($changeTimeTotal_ArrayList / 1000 / $repeat) + "`t" `
    + [String]($changeTimeTotal_GenericList / 1000 / $repeat) + "`t" `
    + [String]($changeTimeTotal_Array / 1000 / $repeat)
}

# ここから検証
$addTimeArray = @()
$changeTimeArray = @()
$repeat = 5

# 5,000回
TestFunc 5000 $repeat ([ref]$addTimeArray) ([ref]$changeTimeArray) $true $true $true

# 10,000回
TestFunc 10000 $repeat ([ref]$addTimeArray) ([ref]$changeTimeArray) $true $true $true

# 30,000回
TestFunc 30000 $repeat ([ref]$addTimeArray) ([ref]$changeTimeArray) $true $true $true

# 50,0000回
TestFunc 500000 $repeat ([ref]$addTimeArray) ([ref]$changeTimeArray) $true $true $false

# 500,000回
TestFunc 500000 $repeat ([ref]$addTimeArray) ([ref]$changeTimeArray) $true $true $false

# 2,000,000回
TestFunc 2000000 $repeat ([ref]$addTimeArray) ([ref]$changeTimeArray) $true $true $false

# 結果
Write-Host "追加"
foreach($output in $addTimeArray) {
    Write-Host $output
}
Write-Host "変更"
foreach($output in $changeTimeArray) {
    Write-Host $output
}
```