none
Распараллеливание запроса к DFS

    Вопрос

  • Коллеги,

    столкнулся с такой особенностью PowerShell. У меня где-то 1200 dfs линков, некоторые с несколькими таргетами + DFSr настроен.

    При этом скрипт выполняется порядка 6 минут:

    $shName = "Ц"
    #$allLinks = 1200 dfs link
    $selectedLinks = $allLinks  | ? {($_| Get-DfsnFolderTarget).targetpath -like "*$shName*" 

    А похожий скрипт, который выдает идентичную информацию ~20 секунд:

    $shName = "Ц"
    #$allLinks = 1200 dfs link
    $targets = $allLinks | Get-DfsnFolderTarget | ? {$_.TargetPath -like "*$shName*"} 
    $selectedLinks = $Targets | Get-DfsnFolder
    

    Вопрос, собственно, какой механизм позволяет второму скрипту выполнится на порядок быстрее и можно ли модифицировать первый скрипт с таким же механизмом?

    Как я понял, во втором случае ($allLinks | Get-DfsnFolderTarget ) передача линков по конвееру с помощью ByValue механизма включает распараллеливание + выдает сверху прогресс бар, а в первом случае нет.

    $PSVersionTable.PSVersion = 5.1.14393.2608393.2608 

    5.1.14393.2608 

     

    11 декабря 2018 г. 9:01

Ответы

  • Get-DfsnFolderTarget - Это обвязка над WMI-классом ROOT\Microsoft\Windows\DFSN\MSFT_DfsNamespaceFolderTarget". В PowerShell v3 можно создать командлет на основе WMI, в нашем примере, определение в файле DfsNamespaceFolderTarget.cdxml.

    <?xml version="1.0" encoding="utf-8"?>
    <PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
      <Class ClassName="ROOT\Microsoft\Windows\DFSN\MSFT_DfsNamespaceFolderTarget">
          <!--
          //
          // Get-DfsnFolderTarget
          //
          -->
          <Cmdlet>
            <CmdletMetadata Verb="Get" HelpUri="http://go.microsoft.com/fwlink/?LinkID=239842"/>
            <Method MethodName="GetNamespaceFolderTarget">
                <Parameter ParameterName="NamespacePath">
                  <Type PSType="System.String" />
                  <CmdletParameterMetadata PSName="Path" Position="0" Aliases="DfsPath FolderPath NamespacePath" IsMandatory="true" ValueFromPipelineByPropertyName="true">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>

    После определения cdxml,PowerShell автоматически добавляет, для всех командлетов - [-CimSession <CimSession[]>] [-ThrottleLimit <int>] [-AsJob]. Можно убедиться, что данные параметры есть у всех командлетов данного типа -  Get-Command -Capability CIM

    Для оптимизации выполнения, PowerShell использует параметр ThottleLimit:

    ThrottleLimit<Int32>
    Specifies the maximum number of concurrent operations that can be established to run the cmdlet. If this parameter is omitted or a value of 0 is entered, then Windows PowerShell® calculates an optimum throttle limit for the cmdlet based on the number of CIM cmdlets that are running on the computer. The throttle limit applies only to the current cmdlet, not to the session or to the computer.

    В msdn - https://docs.microsoft.com/en-us/dotnet/api/microsoft.powershell.cmdletization.cim.cimcmdletadapter.throttlelimit?view=pscore-6.0.0

    Maximum number of remote connections that can remain active at any given time.

    Т.к.это обвязка, то можно посмотреть, какие метаданные сгенерированны: 

    ${function:Get-DfsnFolderTarget}

    Можно будеть увидеть, как будет запускаться данный командлет:

    $__cmdletization_objectModelWrapper.ProcessRecord($__cmdletization_methodInvocationInfo)

    По умолчанию ThrottleLimit = 15:

    PS > $__cmdletization_objectModelWrapper
    
    
    CimSession    : {CimSession: .}
    ThrottleLimit : 15
    AsJob         : False
    Cmdlet        : System.Management.Automation.PSScriptCmdlet
    ClassName     : root\cimv2\Win32_Process
    ClassVersion  :
    ModuleVersion : 2.0.0.0
    PrivateData   : {[ClientSideShouldProcess, ]}


    $allLinks | Get-DfsnFolderTarget - Для данного гораздно меньше накладных расходов.

    Теперь создам обвязку для Win32_Process для сранения из примера - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/cmdlet-definition-xml

    PS > Measure-Command {Get-Process | Get-Win32Process | Where {$_.Name -match "System"}}
    Seconds           : 2
    Milliseconds      : 323
    
    

    И

    PS > Measure-Command {Get-Process | Where {($_ | Get-Win32Process).Name -match "System"}}
    
    Seconds           : 6
    Milliseconds      : 535

    Ограничим ThrottleLimit = 1, то можно наблюдать картину, примерно выше, что оптимиция есть, но уже заметно меньше влияет на результат:

    PS > Measure-Command {Get-Process | Get-Win32Process -ThrottleLimit 1 | Where {$_.Name -match "System"}}
    
    Seconds           : 6
    Milliseconds      : 282

    Т.е. замедление примерно в 3 раза, для простых операций на локальном ПК.

    Для ускорения в первом варианте,возможно, воспользоваться механизмом -parallel и throttlelimit из workflow или задействовать модуль или обратиться к сторонему  модулю - ThreadJob.



    • Помечено в качестве ответа Kupriyanov 11 декабря 2018 г. 14:26
    11 декабря 2018 г. 11:22
    Отвечающий
  • 1. Все последовательно

    2. Get-DfsnFolderTarget выполняется параллельно, объекты передаются в pipeline для where-object последовательно


    • Помечено в качестве ответа Kupriyanov 11 декабря 2018 г. 14:26
    11 декабря 2018 г. 13:00
    Отвечающий

Все ответы

  • Get-DfsnFolderTarget - Это обвязка над WMI-классом ROOT\Microsoft\Windows\DFSN\MSFT_DfsNamespaceFolderTarget". В PowerShell v3 можно создать командлет на основе WMI, в нашем примере, определение в файле DfsNamespaceFolderTarget.cdxml.

    <?xml version="1.0" encoding="utf-8"?>
    <PowerShellMetadata xmlns="http://schemas.microsoft.com/cmdlets-over-objects/2009/11">
      <Class ClassName="ROOT\Microsoft\Windows\DFSN\MSFT_DfsNamespaceFolderTarget">
          <!--
          //
          // Get-DfsnFolderTarget
          //
          -->
          <Cmdlet>
            <CmdletMetadata Verb="Get" HelpUri="http://go.microsoft.com/fwlink/?LinkID=239842"/>
            <Method MethodName="GetNamespaceFolderTarget">
                <Parameter ParameterName="NamespacePath">
                  <Type PSType="System.String" />
                  <CmdletParameterMetadata PSName="Path" Position="0" Aliases="DfsPath FolderPath NamespacePath" IsMandatory="true" ValueFromPipelineByPropertyName="true">
                    <ValidateNotNull />
                    <ValidateNotNullOrEmpty />
                  </CmdletParameterMetadata>
                </Parameter>

    После определения cdxml,PowerShell автоматически добавляет, для всех командлетов - [-CimSession <CimSession[]>] [-ThrottleLimit <int>] [-AsJob]. Можно убедиться, что данные параметры есть у всех командлетов данного типа -  Get-Command -Capability CIM

    Для оптимизации выполнения, PowerShell использует параметр ThottleLimit:

    ThrottleLimit<Int32>
    Specifies the maximum number of concurrent operations that can be established to run the cmdlet. If this parameter is omitted or a value of 0 is entered, then Windows PowerShell® calculates an optimum throttle limit for the cmdlet based on the number of CIM cmdlets that are running on the computer. The throttle limit applies only to the current cmdlet, not to the session or to the computer.

    В msdn - https://docs.microsoft.com/en-us/dotnet/api/microsoft.powershell.cmdletization.cim.cimcmdletadapter.throttlelimit?view=pscore-6.0.0

    Maximum number of remote connections that can remain active at any given time.

    Т.к.это обвязка, то можно посмотреть, какие метаданные сгенерированны: 

    ${function:Get-DfsnFolderTarget}

    Можно будеть увидеть, как будет запускаться данный командлет:

    $__cmdletization_objectModelWrapper.ProcessRecord($__cmdletization_methodInvocationInfo)

    По умолчанию ThrottleLimit = 15:

    PS > $__cmdletization_objectModelWrapper
    
    
    CimSession    : {CimSession: .}
    ThrottleLimit : 15
    AsJob         : False
    Cmdlet        : System.Management.Automation.PSScriptCmdlet
    ClassName     : root\cimv2\Win32_Process
    ClassVersion  :
    ModuleVersion : 2.0.0.0
    PrivateData   : {[ClientSideShouldProcess, ]}


    $allLinks | Get-DfsnFolderTarget - Для данного гораздно меньше накладных расходов.

    Теперь создам обвязку для Win32_Process для сранения из примера - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/cmdlet-definition-xml

    PS > Measure-Command {Get-Process | Get-Win32Process | Where {$_.Name -match "System"}}
    Seconds           : 2
    Milliseconds      : 323
    
    

    И

    PS > Measure-Command {Get-Process | Where {($_ | Get-Win32Process).Name -match "System"}}
    
    Seconds           : 6
    Milliseconds      : 535

    Ограничим ThrottleLimit = 1, то можно наблюдать картину, примерно выше, что оптимиция есть, но уже заметно меньше влияет на результат:

    PS > Measure-Command {Get-Process | Get-Win32Process -ThrottleLimit 1 | Where {$_.Name -match "System"}}
    
    Seconds           : 6
    Milliseconds      : 282

    Т.е. замедление примерно в 3 раза, для простых операций на локальном ПК.

    Для ускорения в первом варианте,возможно, воспользоваться механизмом -parallel и throttlelimit из workflow или задействовать модуль или обратиться к сторонему  модулю - ThreadJob.



    • Помечено в качестве ответа Kupriyanov 11 декабря 2018 г. 14:26
    11 декабря 2018 г. 11:22
    Отвечающий
  • Пока не все осмыслил, но грубо говоря:

    в первом примере вычисления идут внутри Where-object и делают они это по очереди. У Where-object нет возможности использовать -ThrottleLimit или как-то распараллелить себя.

    А во втором примере ($allLinks | Get-DfsnFolderTarget ) получение таргетов идет параллельно, а их сравнение через where-object уже идет последовательно, но для каждого таргета. И это получается также  в параллель?

    11 декабря 2018 г. 12:31
  • 1. Все последовательно

    2. Get-DfsnFolderTarget выполняется параллельно, объекты передаются в pipeline для where-object последовательно


    • Помечено в качестве ответа Kupriyanov 11 декабря 2018 г. 14:26
    11 декабря 2018 г. 13:00
    Отвечающий
  • Да, так понятно.

    То есть foreach-object и where-object не используют параллельные вычисления. Только если через workflow и ключ parallel?

    То есть, грубо говоря, ? и % "ломают" параллелизацию? Конечно, они не ломают, но везде, где я поставлю вопрос или процент, все элементы массива начнут идти последовательно? 

    И получается, что выгоднее ставить их как можно позже, там где и так по конвейру приходит меньше данных или меньше вычислений?

    11 декабря 2018 г. 13:37
  • foreach-object и where-object не используют параллельные вычисления. - Не используют.

    ? и % "ломают" параллелизацию? - Не ломают.

    И получается, что выгоднее ставить их как можно позже, там где и так по конвейру приходит меньше данных или меньше вычислений?  - Использовать надо по назначению, с максимальной наглядностью и простотой кода. 

    11 декабря 2018 г. 13:53
    Отвечающий
  • Последнее утверждение не работает, Первый скрипт намного понятнее, нагляднее и первым приходит в голову, а до Второго, по крайней мере я, не сразу догадался...

    Но ответ я получил. Большое спасибо.

    11 декабря 2018 г. 14:26