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,30 +971,16 @@ 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>

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,52 +1369,60 @@ 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)
{
// 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;
}
string scriptBlockText = scriptBlock.Ast.Extent.Text;
bool written = false;
// Maximum size of ETW events is 64kb. Split a message if it is larger than 20k (Unicode) characters.
if (scriptBlockText.Length < 20000)
{
written = WriteScriptBlockToLog(scriptBlock, 0, 1, scriptBlock.Ast.Extent.Text);
}
else
{
// But split the segments into random sizes (10k + between 0 and 10kb extra)
// so that attackers can't creatively force their scripts to span well-known
// segments (making simple rules less reliable).
int segmentSize = 10000 + (new Random()).Next(10000);
int segments = (int)Math.Floor((double)(scriptBlockText.Length / segmentSize)) + 1;
int currentLocation = 0;
int currentSegmentSize = 0;
for (int segment = 0; segment < segments; segment++)
{
return;
}
currentLocation = segment * segmentSize;
currentSegmentSize = Math.Min(segmentSize, scriptBlockText.Length - currentLocation);
string scriptBlockText = scriptBlock.Ast.Extent.Text;
bool written = false;
// Maximum size of ETW events is 64kb. Split a message if it is larger than 20k (Unicode) characters.
if (scriptBlockText.Length < 20000)
{
written = WriteScriptBlockToLog(scriptBlock, 0, 1, scriptBlock.Ast.Extent.Text);
}
else
{
// But split the segments into random sizes (10k + between 0 and 10kb extra)
// so that attackers can't creatively force their scripts to span well-known
// segments (making simple rules less reliable).
int segmentSize = 10000 + (new Random()).Next(10000);
int segments = (int)Math.Floor((double)(scriptBlockText.Length / segmentSize)) + 1;
int currentLocation = 0;
int currentSegmentSize = 0;
for (int segment = 0; segment < segments; segment++)
{
currentLocation = segment * segmentSize;
currentSegmentSize = Math.Min(segmentSize, scriptBlockText.Length - currentLocation);
string textToLog = scriptBlockText.Substring(currentLocation, currentSegmentSize);
written = WriteScriptBlockToLog(scriptBlock, segment, segments, textToLog);
}
}
if (written)
{
scriptBlock.HasLogged = true;
string textToLog = scriptBlockText.Substring(currentLocation, currentSegmentSize);
written = WriteScriptBlockToLog(scriptBlock, segment, segments, textToLog);
}
}
if (written)
{
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)
@ -1596,7 +1615,12 @@ namespace System.Management.Automation
private static ScriptBlockLogging GetScriptBlockLoggingSetting()
{
return Utils.GetPolicySetting<ScriptBlockLogging>(Utils.SystemWideThenCurrentUserConfig);
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
@ -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
// properly analyzed the script block's security.
LogScriptBlockCreation(scriptBlock, forceLogCreation);
// 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" : [