Make Foreach-Object 2 times faster by reducing unnecessary allocations and boxing (#10047)

This commit is contained in:
Dongbo Wang 2019-07-08 10:21:47 -07:00 committed by Aditya Patwardhan
parent 5bc3d15a30
commit 201d7aa275
8 changed files with 109 additions and 82 deletions

View File

@ -155,6 +155,7 @@
<Value>rs</Value>
<Value>ps</Value>
<Value>op</Value>
<Value>sb</Value>
</CollectionProperty>
</AnalyzerSettings>
</Analyzer>

View File

@ -437,8 +437,11 @@ namespace System.Management.Automation
// that can mess up the runspace our CommandInfo object came from.
var runspace = (RunspaceBase)_context.CurrentRunspace;
if (!runspace.RunActionIfNoRunningPipelinesWithThreadCheck(
() => GetMergedCommandParameterMetadata(out result)))
if (runspace.CanRunActionInCurrentPipeline())
{
GetMergedCommandParameterMetadata(out result);
}
else
{
_context.Events.SubscribeEvent(
source: null,

View File

@ -806,8 +806,12 @@ namespace System.Management.Automation
{nameof(UpdatableHelp), @"Software\Policies\Microsoft\PowerShellCore\UpdatableHelp"},
{nameof(ConsoleSessionConfiguration), @"Software\Policies\Microsoft\PowerShellCore\ConsoleSessionConfiguration"}
};
private static readonly ConcurrentDictionary<Tuple<ConfigScope, string>, PolicyBase> s_cachedPoliciesFromRegistry =
new ConcurrentDictionary<Tuple<ConfigScope, string>, PolicyBase>();
private static readonly ConcurrentDictionary<ConfigScope, ConcurrentDictionary<string, PolicyBase>> s_cachedPoliciesFromRegistry =
new ConcurrentDictionary<ConfigScope, ConcurrentDictionary<string, PolicyBase>>();
private static readonly Func<ConfigScope, ConcurrentDictionary<string, PolicyBase>> s_subCacheCreationDelegate =
key => new ConcurrentDictionary<string, PolicyBase>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// The implementation of fetching a specific kind of policy setting from the given configuration scope.
@ -915,6 +919,8 @@ namespace System.Management.Automation
private static T GetPolicySettingFromGPO<T>(ConfigScope[] preferenceOrder) where T : PolicyBase, new()
{
PolicyBase policy = null;
string policyName = typeof(T).Name;
foreach (ConfigScope scope in preferenceOrder)
{
if (InternalTestHooks.BypassGroupPolicyCaching)
@ -923,13 +929,10 @@ namespace System.Management.Automation
}
else
{
var key = Tuple.Create(scope, typeof(T).Name);
if (!s_cachedPoliciesFromRegistry.TryGetValue(key, out policy))
var subordinateCache = s_cachedPoliciesFromRegistry.GetOrAdd(scope, s_subCacheCreationDelegate);
if (!subordinateCache.TryGetValue(policyName, out policy))
{
lock (s_cachedPoliciesFromRegistry)
{
policy = s_cachedPoliciesFromRegistry.GetOrAdd(key, tuple => GetPolicySettingFromGPOImpl<T>(tuple.Item1));
}
policy = subordinateCache.GetOrAdd(policyName, key => GetPolicySettingFromGPOImpl<T>(scope));
}
}

View File

@ -971,32 +971,18 @@ namespace System.Management.Automation.Runspaces
}
}
internal bool RunActionIfNoRunningPipelinesWithThreadCheck(Action action)
internal bool CanRunActionInCurrentPipeline()
{
bool ranit = false;
bool shouldRunAction = false;
lock (_pipelineListLock)
{
// If we have no running pipeline, or if the currently running pipeline is
// the same as the current thread, then execute the action.
var pipelineRunning = _currentlyRunningPipeline as PipelineBase;
if (pipelineRunning == null ||
Thread.CurrentThread.Equals(pipelineRunning.NestedPipelineExecutionThread))
{
shouldRunAction = true;
return pipelineRunning == null ||
Thread.CurrentThread == pipelineRunning.NestedPipelineExecutionThread;
}
}
if (shouldRunAction)
{
action();
ranit = true;
}
return ranit;
}
/// <summary>
/// Gets the currently executing pipeline.
/// </summary>

View File

@ -7,14 +7,13 @@ using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Management.Automation.Configuration;
using System.Management.Automation.Internal;
using System.Management.Automation.Runspaces;
using System.Security;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.PowerShell.Commands;
namespace System.Management.Automation.Host
{
/// <summary>
@ -927,7 +926,10 @@ namespace System.Management.Automation.Host
/// </summary>
internal static TranscriptionOption GetSystemTranscriptOption(TranscriptionOption currentTranscript)
{
var transcription = Utils.GetPolicySetting<Transcription>(Utils.SystemWideThenCurrentUserConfig);
var transcription = InternalTestHooks.BypassGroupPolicyCaching
? Utils.GetPolicySetting<Transcription>(Utils.SystemWideThenCurrentUserConfig)
: s_transcriptionSettingCache.Value;
if (transcription != null)
{
// If we have an existing system transcript for this process, use that.
@ -948,6 +950,9 @@ namespace System.Management.Automation.Host
internal static TranscriptionOption systemTranscript = null;
private static object s_systemTranscriptLock = new Object();
private static Lazy<Transcription> s_transcriptionSettingCache = new Lazy<Transcription>(
() => Utils.GetPolicySetting<Transcription>(Utils.SystemWideThenCurrentUserConfig),
isThreadSafe: true);
private static TranscriptionOption GetTranscriptOptionFromSettings(Transcription transcriptConfig, TranscriptionOption currentTranscript)
{

View File

@ -975,7 +975,8 @@ namespace System.Management.Automation
try
{
var runspace = (RunspaceBase)context.CurrentRunspace;
shouldGenerateEvent = !runspace.RunActionIfNoRunningPipelinesWithThreadCheck(() =>
if (runspace.CanRunActionInCurrentPipeline())
{
InvokeWithPipeImpl(
useLocalScope,
functionsToDefine,
@ -986,7 +987,12 @@ namespace System.Management.Automation
scriptThis,
outputPipe,
invocationInfo,
args));
args);
}
else
{
shouldGenerateEvent = true;
}
}
finally
{

View File

@ -322,6 +322,7 @@ namespace System.Management.Automation
internal Guid Id { get; private set; }
internal bool HasLogged { get; set; }
internal bool SkipLogging { get; set; }
internal bool IsFilter { get; private set; }
internal bool IsProductCode
@ -824,6 +825,13 @@ namespace System.Management.Automation
set { _scriptBlockData.HasLogged = value; }
}
internal bool SkipLogging
{
get { return _scriptBlockData.SkipLogging; }
set { _scriptBlockData.SkipLogging = value; }
}
internal Assembly AssemblyDefiningPSTypes { set; get; }
internal HelpInfo GetHelpInfo(ExecutionContext context, CommandInfo commandInfo, bool dontSearchOnRemoteComputer,
@ -1111,7 +1119,7 @@ namespace System.Management.Automation
{
if (context.EngineSessionState.CurrentScope.LocalsTuple == null)
{
// If the locals tuple is, that means either:
// If the locals tuple is null, that means either:
// * we're invoking a script block for a module
// * something unexpected
context.EngineSessionState.CurrentScope.LocalsTuple = locals;
@ -1361,16 +1369,21 @@ namespace System.Management.Automation
internal static void LogScriptBlockCreation(ScriptBlock scriptBlock, bool force)
{
if (scriptBlock.HasLogged && !InternalTestHooks.ForceScriptBlockLogging)
{
// Fast exit if the script block is already logged and we are not force re-logging in tests.
return;
}
ScriptBlockLogging logSetting = GetScriptBlockLoggingSetting();
if (force || logSetting?.EnableScriptBlockLogging == true)
{
if (!scriptBlock.HasLogged || InternalTestHooks.ForceScriptBlockLogging)
{
// If script block logging is explicitly disabled, or it's from a trusted
// file or internal, skip logging.
if (logSetting?.EnableScriptBlockLogging == false ||
scriptBlock.ScriptBlockData.IsProductCode)
{
scriptBlock.SkipLogging = true;
return;
}
@ -1407,6 +1420,9 @@ namespace System.Management.Automation
scriptBlock.HasLogged = true;
}
}
else
{
scriptBlock.SkipLogging = true;
}
}
@ -1582,6 +1598,9 @@ namespace System.Management.Automation
private static string s_lastSeenCertificate = string.Empty;
private static bool s_hasProcessedCertificate = false;
private static CmsMessageRecipient[] s_encryptionRecipients = null;
private static Lazy<ScriptBlockLogging> s_sbLoggingSettingCache = new Lazy<ScriptBlockLogging>(
() => Utils.GetPolicySetting<ScriptBlockLogging>(Utils.SystemWideThenCurrentUserConfig),
isThreadSafe: true);
// Reset any static caches if the certificate has changed
private static void ResetCertificateCacheIfNeeded(string certificate)
@ -1595,10 +1614,15 @@ namespace System.Management.Automation
}
private static ScriptBlockLogging GetScriptBlockLoggingSetting()
{
if (InternalTestHooks.BypassGroupPolicyCaching)
{
return Utils.GetPolicySetting<ScriptBlockLogging>(Utils.SystemWideThenCurrentUserConfig);
}
return s_sbLoggingSettingCache.Value;
}
// Quick check for script blocks that may have suspicious content. If this
// is true, we log them to the event log despite event log settings.
internal static string CheckSuspiciousContent(Ast scriptBlockAst)
@ -1919,17 +1943,16 @@ namespace System.Management.Automation
internal static void LogScriptBlockStart(ScriptBlock scriptBlock, Guid runspaceId)
{
// When invoking, log the creation of the script block if it has suspicious
// content
bool forceLogCreation = false;
if (scriptBlock._scriptBlockData.HasSuspiciousContent)
// Fast exit if the script block can skip logging.
if (!scriptBlock.SkipLogging)
{
forceLogCreation = true;
}
// When invoking, log the creation of the script block if it has suspicious content
bool forceLogCreation = scriptBlock._scriptBlockData.HasSuspiciousContent;
// We delay logging the creation util the 'Start' so that we can be sure we've
// We delay logging the creation until the 'Start' so that we can be sure we've
// properly analyzed the script block's security.
LogScriptBlockCreation(scriptBlock, forceLogCreation);
}
if (GetScriptBlockLoggingSetting()?.EnableScriptBlockInvocationLogging == true)
{

View File

@ -25,7 +25,7 @@
},
"namingRules" : {
"allowCommonHungarianPrefixes" : true,
"allowedHungarianPrefixes" : [ "n", "r", "l", "i", "io", "is", "fs", "lp", "dw", "h", "rs", "ps", "op" ]
"allowedHungarianPrefixes" : [ "n", "r", "l", "i", "io", "is", "fs", "lp", "dw", "h", "rs", "ps", "op", "sb" ]
},
"maintainabilityRules" : {
"topLevelTypes" : [