Powershell:如何禁用整数数学上的自动 int32 类型转换?

Powershell: How can I disable automatic int32 type conversions on math with integral numbers?

这是一个例子,真实的是一个很大的New-Object 'object[,]'二维数组:

PS C:\> $a=[int16]10
PS C:\> $a.GetType().Name
Int16
PS C:\> $a++
PS C:\> $a.GetType().Name
Int32

PS C:\> $a=[int16]10
PS C:\> $a.GetType().Name
Int16
PS C:\> $a=$a+[int16]1
PS C:\> $a.GetType().Name
Int32

我可以在这些事情中强制类型,但这实际上是数学完成后的转换,并且会使完整代码减慢 ~ 10%,所以它是无用的:

PS C:\> $a=[int16]10
PS C:\> $a.GetType().Name
Int16
PS C:\> $a=[int16]($a+1)
PS C:\> $a.GetType().Name
Int16

在使用任何数学运算时,有没有办法阻止这种从 byte 和 int16 到它心爱的 int32 的自动转换?

至于@mklement0 的评论,我将示例扩展到用例。请注意,该板上任何地方的值都不超过“8”:

# Init board
$BoardXSize = [int]100
$BoardYSize = [int]100
$Board = New-Object 'object[,]' $BoardXSize,$BoardYSize
for ($y=0;$y -lt $BoardYSize;$y++) {
    for ($x=0;$x -lt $BoardXSize;$x++) {
        [int16]$Board[$x,$y] = 0
    }
}

测试:

PS C:\> ($Board[49,49]).GetType().Name
Int16
PS C:\> $Board[49,49]++
PS C:\> ($Board[49,49]).GetType().Name
Int32

PS C:\> [int16]$Board[49,49]=0
PS C:\> ($Board[49,49]).GetType().Name
Int16
PS C:\> [int16]$Board[49,49]++
0
PS C:\> ($Board[49,49]).GetType().Name
Int32

PS C:\> [int16]$Board[49,49]=0
PS C:\> ($Board[49,49]).GetType().Name
Int16
PS C:\> $Board[49,49] += [int16]1
PS C:\> ($Board[49,49]).GetType().Name
Int32

PS C:\> [byte]$Board[49,49]=0
PS C:\> ($Board[49,49]).GetType().Name
Byte
PS C:\> $Board[49,49] += [byte]1
PS C:\> ($Board[49,49]).GetType().Name
Int32

然而,当对它起作用的运算符非常挑剔时,只有“++”似乎失败了:

PS C:\> [int16]$Board[49,49]=0
PS C:\> ($Board[49,49]).GetType().Name
Int16
PS C:\> [int16]$Board[49,49] += [int16]1
PS C:\> ($Board[49,49]).GetType().Name
Int16

PS C:\> [byte]$Board[49,49]=0
PS C:\> ($Board[49,49]).GetType().Name
Byte
PS C:\> [byte]$Board[49,49] += [byte]1
PS C:\> ($Board[49,49]).GetType().Name
Byte

但是由于这总是在完成数学运算后进行转换,因此它不会提高速度。不过已经解决了。

您可以type-constrain您的变量,方法是将类型文字放在左侧 的(初始)分配:

# Note how [int16] is to the *left* of $a = ...
PS> [int16] $a = 10; ++$a; $a += 1; $a, $a.GetType().Name
12
Int16

请注意,这与在静态类型语言(如 C#)中键入变量不同,例如:执行 cast 而不是静态锁定指定类型 在每次赋值时为了强制赋值给那个类型。

例如,[int16] $a = 10 是原始分配,$a += 1 实际上与分配 [int16] ($a + 1) 相同。

这也意味着您可以自由分配任何类型的值,只要它们可以转换 为约束类型即可;例如:

# These work, because they are implicitly cast (converted) to [int16]
$a += 1.2
$a += '42'

请参阅下一节关于数组的内容。

数组应用类型约束:

到一个 (one-dimensional) array:

# Strongly types the array as storing [int16] instances
# *and* type-constrains the $arr variable.
PS> [int16[]] $arr = 10, 11; ++$arr[0]; $arr[1] += 1; $arr; $arr.ForEach('GetType').Name
11
12
Int16
Int16

二维数组(在PowerShell中很少见):

# Strongly types the 2D array as storing [int16] instances,
# but does *not* type-constrain the $arr2d *variable.
PS> $countDim1, $countDim2 = 2, 3;
    $arr2d = [int16[,]]::new($countDim1, $countDim2);
    ++$arr2d[0,0]; $arr2d[0,0].GetType().Name
Int16

请注意,这确实创建了强静态类型的 .NET 数组,但再次分配给它们的元素会应用 PowerShell 的灵活类型转换(例如,$arr[0] = '42' 工作正常)。

另请注意,为简洁起见,二维数组示例实际上 type-constrain 变量 的数据类型,因为没有类型字面量任务的左边;因此,您仍然可以用任何类型的任意新值替换整个数组(例如,$arr2d = 'foo');要也执行 type-constraining,您必须执行以下操作:

# Creates a 2D [int16] array *and* type-constrains $arr2D to
# such arrays.
[int16[,]] $arr2d = [int16[,]]::new($countDim1, $countDim2)

最后,请注意,您不能 type-constrain 数组的单个元素 或对象的(可写)属性;换句话说:只有 L-values 是 PowerShell 变量 可以 type-constrained;[1] 在所有其他情况下执行一个简单的临时 cast;例如,给定一个 [object[]] 数组 $a = 1, 2,下面两个语句是等价的:

# Cast
$a[0] = [int] 42 

# !! A mere cast too, because the assignment target is an 
# !! *array element*, which cannot be type-constrained.
[int] $a[0] = 42

与 C# 等语言不同的是,PowerShell 会自动扩展 数值运算中的类型

这不仅适用于不同(数字)类型的操作数,而且如果结果太大而无法适应(两者中较大的)输入类型(s),它有两个令人惊讶的方面:

  • 应用了 最小 宽度 [int] (System.Int32),这意味着小于 [int] 的类型结果中 总是 扩大到 [int],即使没有必要,如您所见:

    # Implicitly widens to [int], even though not necessary.
    # Note that the [byte] cast is only applied to the LHS
    PS> ([byte] 42 + 1).GetType().Name
    Int32
    
  • 如果结果太大而不适合(两者中较大的)输入类型,结果总是扩大到 [double]System.Double )(!)

    # Implicitly widens to [double](!), because adding 1
    # to the max. [int] value doesn't fit into an [int].
    PS> ([int]::MaxValue + 1).GetType().Name
    Double
    
    # NO widening, because the large input type is a [long] (System.Int64)
    # (suffix L), so the result fits into a [long] too.
    PS> ([long] [int]::MaxValue + 1).GetType().Name
    Int64
    
  • 在不考虑更大的 integer 类型的情况下,直接扩大到 [double] 的表达式与 number literals 在 PowerShell 中被解析,其中使用了更大的整数类型 (但请注意 PowerShell 从不选择 unsigned 类型):

    # 2147483648 = [int]::MaxValue + 1, so [long] (System.Int64)
    # is chose as the literal's type. 
    (2147483648).GetType().Name
    In64
    
    • 注意数字文字也可以有类型后缀来明确指定它们的类型,比如l代表[long]d代表[decimal];例如,2147483648d.GetType().Name returns Decimal.

可选阅读:在强类型与非类型 ([object[]]) 数组中访问数组元素的性能:

例如,由 ,@(...) 运算符创建的 PowerShell 的常规数组类型是 [object[]],即 [object] 中的“无类型”数组,作为 .NET 类型层次结构的根,可以存储任何数据类型的值 - 甚至允许在给定的单个数组中混合不同类型。

虽然这提供了很大的灵活性,但它会影响:

  • 类型安全:根据用例,可能需要确保所有元素具有相同的类型;如果是这样,请使用强类型数组。

  • 内存效率:Value-type 个实例必须包装在 [object] 个实例中进行存储,这个过程称为 装箱,并且,访问时,需要解包,称为 unboxing.

  • 运行时性能:令人惊讶的是,与装箱和拆箱的开销所暗示的不同,强类型和二维数组几乎没有性能优势性能甚至会受到影响。

以下是 基准测试 数组获取和设置访问的结果,将具有各种值类型的一维和二维数组与 object 元素进行比较:

  • 15 运行s 被平均,1-million-element 一维数组和 1000 x 1000 二维数组。

  • 基准源代码在下面。

    • 要自己运行代码,需要先从this Gist.

      下载并定义函数Time-Command
    • 假设你看过在链接的 Gist 的源代码中确保它是安全的(我个人可以向你保证,但你应该经常检查),你可以直接安装它,如下所示:

      irm https://gist.github.com/mklement0/9e1f13978620b09ab2d15da5535d1b27/raw/Time-Command.ps1 | iex
      
    • PowerShell 中的基准测试远非一门精确的科学;由于基准是墙 clock-based,最好对多个 运行 进行平均,并确保系统不会(太)忙于 运行 其他事情。

  • 绝对时间会因许多因素而异,但 Factor 列应该提供相对性能的感觉 - 小数点后第二位的差异可能是 偶然(例如,1.00 vs. 1.03)并且此类排名可能会在重复调用中交换位置。

  • 根据以下结果得出的一些结论,运行 PowerShell (Core) 7.2.2[=204] =] 在 Windows 10 机器上 - Windows PowerShell 性能特征 可能不同 :

    • 一维数组(迄今为止 PowerShell 中最常见的数组类型):

        如果强类型化,
      • 获取 稍微 更快。
      • 如果是强类型,更新 明显更快。
    • 二维数组(在 PowerShell 中很少见):

      • 获取 稍微慢一些 如果是强类型。
      • 如果是强类型,
      • 更新 稍微快一些
    • 注意:虽然下面没有 Windows PowerShell 结果,但在我的测试中 似乎数组访问速度大约 翻倍 在 PowerShell(核心)7+.

# Sample results on a Windows 10 machine running PowerShell 7.2.2

============== 1D: GET performance (avg. of 15 runs)

Factor Secs (15-run avg.) Command
------ ------------------ -------
1.00   0.482              # [byte[]]…
1.00   0.483              # [int[]] (Int32)…
1.01   0.487              # [long[]]…
1.02   0.492              # [int16[]]…
1.08   0.521              # [decimal[]]…
1.09   0.526              # [object[]]…
1.11   0.533              # [double[]]…
1.12   0.537              # [datetime[]]…

============== 1D: SET (INCREMENT) performance (avg. of 15 runs)

Factor Secs (15-run avg.) Command
------ ------------------ -------
1.00   0.526              # [double[]]…
1.03   0.541              # [int[]] (Int32)…
1.11   0.584              # [long[]]…
1.30   0.681              # [byte[]]…
1.40   0.738              # [int16[]]…
1.57   0.826              # [decimal[]]…
1.86   0.975              # [object[]], no casts…
2.90   1.523              # [object[]] with [int] casts…
7.34   3.857              # [datetime[]]…

============== 2D: GET performance (avg. of 15 runs)

Factor Secs (15-run avg.) Command
------ ------------------ -------
1.00   0.620              # [object[,]]…
1.13   0.701              # [double[,]]…
1.13   0.702              # [int[,]] (Int32)…
1.18   0.731              # [datetime[,]]…
1.20   0.747              # [long[,]]…
1.23   0.764              # [byte[,]]…
1.26   0.782              # [decimal[,]]…
1.34   0.828              # [int16[,]]…

============== 2D: SET (INCREMENT) performance (avg. of 15 runs)

Factor Secs (15-run avg.) Command
------ ------------------ -------
1.00   0.891              # [double[,]]…
1.02   0.904              # [long[,]]…
1.10   0.977              # [byte[,]]…
1.24   1.107              # [object[,]], no casts…
1.27   1.131              # [int16[,]]…
1.28   1.143              # [int[,]] (Int32)…
1.46   1.300              # [decimal[,]]…
1.72   1.530              # [object[,]] with [int] casts…
5.25   4.679              # [datetime[,]]…

基准源代码:

$runs = 15 # how many run to average.
$d1 = $d2 = 1000  # 1000 x 1000 2D array
$d = $d1 * $d2    # 1 million-element 1D array

# index arrays for looping
$indices_d1 = 0..($d1 - 1)
$indices_d2 = 0..($d2 - 1)
$indices = 0..($d - 1)

# 1D arrays.
$ao = [object[]]::new($d)
$ab = [byte[]]::new($d)
$ai16 = [int16[]]::new($d)
$ai = [int[]]::new($d)
$al = [long[]]::new($d)
$adec = [decimal[]]::new($d)
$ad = [double[]]::new($d)
$adt = [datetime[]]::new($d)

# 2D arrays.
$ao_2d = [object[,]]::new($d1, $d2)
$ab_2d = [byte[,]]::new($d1, $d2)
$ai16_2d = [int16[,]]::new($d1, $d2)
$ai_2d = [int[,]]::new($d1, $d2)
$al_2d = [long[,]]::new($d1, $d2)
$adec_2d = [decimal[,]]::new($d1, $d2)
$ad_2d = [double[,]]::new($d1, $d2)
$adt_2d = [datetime[,]]::new($d1, $d2)

"`n============== 1D: GET performance (avg. of $runs runs)"

Time-Command -Count $runs `
{ # [object[]]
  foreach ($i in $indices) { $null = $ao[$i] }
},
{ # [byte[]]
  foreach ($i in $indices) { $null = $ab[$i] }
}, 
{ # [int16[]]
  foreach ($i in $indices) { $null = $ai16[$i] }
},
{ # [int[]] (Int32)
  foreach ($i in $indices) { $null = $ai[$i] }
},
{ # [long[]]
  foreach ($i in $indices) { $null = $al[$i] }
}, 
{ # [decimal[]]
  foreach ($i in $indices) { $null = $adec[$i] }
}, 
{ # [double[]]
  foreach ($i in $indices) { $null = $ad[$i] }
},
{ # [datetime[]]
  foreach ($i in $indices) { $null = $adt[$i] }
} | Format-Table Factor, Secs*, Command

"============== 1D: SET (INCREMENT) performance (avg. of $runs runs)"

Time-Command -Count $runs `
{ # [object[]], no casts
  foreach ($i in $indices) { ++$ao[$i] }
},
{ # [object[]] with [int] casts
  foreach ($i in $indices) { $ao[$i] = [int] ($ao[$i] + 1) }
},
{ # [byte[]]
  foreach ($i in $indices) { ++$ab[$i] }
}, 
{ # [int16[]]
  foreach ($i in $indices) { ++$ai16[$i] }
},
{ # [int[]] (Int32)
  foreach ($i in $indices) { ++$ai[$i] }
},
{ # [long[]]
  foreach ($i in $indices) { ++$al[$i] }
},
{ # [decimal[]]
  foreach ($i in $indices) { ++$adec[$i] }
},
{ # [double[]]
  foreach ($i in $indices) { ++$ad[$i] }
},
{ # [datetime[]]
  foreach ($i in $indices) { $adt[$i] += 1 } # ++ not supported
} | Format-Table Factor, Secs*, Command

"============== 2D: GET performance (avg. of $runs runs)"

Time-Command -Count $runs `
{ # [object[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $ao_2d[$i, $j] }
  }
},
{ # [byte[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $ab_2d[$i, $j] } 
  }
},
{ # [int16[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $ai16_2d[$i, $j] }
  }
}, 
{ # [int[,]] (Int32)
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $ai_2d[$i, $j] }
  }
},
{ # [long[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $al_2d[$i, $j] } 
  }
}, 
{ # [decimal[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $adec_2d[$i, $j] } 
  }
},
{ # [double[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $ad_2d[$i, $j] } 
  }
}, 
{ # [datetime[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $null = $adt_2d[$i, $j] } 
  }
} | Format-Table Factor, Secs*, Command

"============== 2D: SET (INCREMENT) performance (avg. of $runs runs)"

Time-Command -Count $runs `
{ # [object[,]], no casts
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$ao_2d[$i, $j] }
  }
},
{ # [object[,]] with [int] casts
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $ao_2d[$i, $j] = [int] ($ao_2d[$i, $j] + 1) }
  }
}, 
{ # [byte[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$ab_2d[$i, $j] }
  }
}, 
{ # [int16[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$ai16_2d[$i, $j] }
  }
},
{ # [int[,]] (Int32)
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$ai_2d[$i, $j] }
  }
},
{ # [long[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$al_2d[$i, $j] }
  }
}, 
{ # [decimal[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$adec_2d[$i, $j] } 
  }
},
{ # [double[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { ++$ad_2d[$i, $j] } 
  }
},
{ # [datetime[,]]
  foreach ($i in $indices_d1) {
    foreach ($j in $indices_d2) { $adt_2d[$i, $j] += 1 } # ++ not supported
  }
} | Format-Table Factor, Secs*, Command

[1] 原因是 type-constraining 需要将 type-conversion 属性附加到 PowerShell 变量对象。