powershell 正在填充内存,发生了什么

zaqlnxep  于 2023-06-23  发布在  Shell
关注(0)|答案(1)|浏览(102)

简单的语句1..1GB会填满内存,并且似乎挂在32 GiB的系统上。按Ctrl-C不执行任何操作。解释器内部发生了什么?如果让它运行足够长的时间,会产生正确的结果吗?

PS C:\> (Get-CimInstance -ClassName Win32_ComputerSystem).TotalPhysicalMemory / 1MB
32629.1484375
PS C:\> $PSVersionTable.PSVersion.ToString()
7.3.4
PS C:\> 1..1GB
lymnna71

lymnna711#

    • ..,PowerShell的range运算符**:
      **懒地枚举 * 范围端点(包括)之间的值(在手头的情况下,123,...,1073741824
      • 当结果被 * 捕获***时,例如通过将范围操作包含在(...)中,将其分配给变量,或使其参与更大的表达式,PowerShell将为枚举值创建[object[]]数组(使用(1..10).GetType()进行验证)。

考虑到值的绝对数量,1..1GB枚举1GB == 1,073,741,824(1 + billion)[1]值 * 非常慢 *-但 * 最终 * 在PowerShell (Core) 7+中返回(除非内存耗尽),而在 * Windows PowerShell * 中它会立即失败;正如zett42所指出的:

  • 在Windows PowerShell(64位)中,$array = 1..1GB失败并显示OutOfMemoryExceptionArray dimensions exceeded supported range.)。实际的限制甚至更低($array = 1..300MB仍然失败,而$array = 1..200MB成功)。
  • 原因是. NET Framework,传统的Windows PowerShell底层的仅Windows运行时,不仅对数组的 * 元素计数 * 有限制(接近2 GB,即每个维度2+十亿个元素),而且还取决于数组维度的 * 总字节大小 *,也是2 GB。
  • 因此,假设[object[]]数组的每个元素都是指针(引用)的大小,并且在64位进程中每个指针需要8个字节(用[IntPtr]::Size验证),则实际限制接近2GB / 8; 256MB - 8,准确地说。也就是说,$array = 1..(256MB - 8)可以工作,但任何具有更多值的操作都将失败。
  • 但是,限制 * 更高 *-对于两个PowerShell版本都是一样的-如果您 * 流 * 范围操作而不是导致创建 * 数组 *-请参阅下一节。
  • . NET(Core),现代的跨平台运行时底层PowerShell(Core)7+,不再有这个字节大小限制,但在 * 元素计数 * 方面仍然限制在接近2 GB,即2+十亿元素; zett42已经计算出了确切的限制:2GB - 57,即2,147,483,591每个维度的元素([int]::MaxValue - 56;使用[object[]]::new(2GB - 57).Count进行验证-任何更高版本(如- 56)使用Array dimensions exceeded supported range.时均失败)
  • 顺便说一句:在 * dimensional * 数组中,一个额外的约束是所有维度上的 * combined * 元素计数不能超过40亿个元素(精确地说,4,294,967,182 == 4GB - 114)。然而,在PowerShell中很少使用这样的数组,并且更常用的 * jagged * 数组(其元素是其他数组的数组)不受此限制。

请注意,**如果您使用范围操作作为 * 管道输入 *,***而不将其包含在(...) * 中,则懒惰枚举行为将变得明显-*不创建数组 ,并且枚举值是 * 流式传输

# Prints 1, 2, ..., 10 virtually *instantly*, because the values
# are *streamed* to Select-Object
1..1GB | Select-Object -First 10

如果您使用此表单:

  • 你 * 不 * 受 * array * size和element-count约束,所以上面的也适用于 * Windows PowerShell *。
  • 但是,您会受到范围运算符本身内置的限制:要枚举的值的计数必须适合[int]System.Int32),这将您限制为[int]::MaxValue + 1,即2GB范围内的值;即以1为起点,最高终点为[int]::MaxValue(2GB-1))。

请注意,..在PowerShell运算符中不常见**,因为它结合了非管道和管道行为:* in isolation *-当您既不捕获它也不将它发送到另一个命令时-它在[object[]]数组中被 * 隐式地 * 捕获,并且只有在它创建之后,结果数组的to-host(to-console)输出才开始;也就是说,流 * 不会 * 发生,除非您将操作用作 * 显式 * 管道的输入。

  • 比较1..50MB1..50MB | Write-Output的输出行为:前者在输出开始之前产生明显的延迟,这是由于首先创建(大)数组,而后者 * 流式传输 * 其值,因此立即开始产生输出。
    *情境 * 流行为 * 也 * 在foreachswitch语句的上下文中出现:
  • 以下命令在 * 两个 * PowerShell版本中都可以使用,因为..在那里也表现出 * 流 * 行为:
# Outputs 1, received in a *streaming* fashion, then exits.
# Subject only to the 2GB-1 limit.
foreach ($i in 1..(2GB-1)) { $i; break }      

# Ditto
switch (1..(2GB-1)) { default { $_; break } }

[1]正如mclayton所指出的,GB后缀是 *(二进制)千兆字节乘数 *(相当于[Math]::Pow(2, 30) == 1,073,741,824的因子),PowerShell支持的数字 * 文字 * 中的几个二进制乘数之一。

相关问题