Skip to content

Commit 9358e4b

Browse files
authored
Improve performance in argument unpacking (#1838)
* Cache parameters list * Use Queue to speed up popping
1 parent f971ff6 commit 9358e4b

File tree

3 files changed

+61
-37
lines changed

3 files changed

+61
-37
lines changed

Src/IronPython/Compiler/Ast/FunctionDefinition.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ internal override MSAst.Expression LocalContext {
7979

8080
public IList<Parameter> Parameters => _parameters;
8181

82-
internal override string[] ParameterNames => ArrayUtils.ConvertAll(_parameters, val => val.Name);
82+
private string[] _parameterNames = null;
83+
84+
internal override string[] ParameterNames => _parameterNames ??= ArrayUtils.ConvertAll(_parameters, val => val.Name);
8385

8486
internal override int ArgCount {
8587
get {

Src/IronPython/Runtime/Binding/MetaPythonFunction.cs

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,7 @@ private bool TryFinishList(Expression[] exprArgs, List<Expression> paramsArgs) {
551551
// make a single copy.
552552
exprArgs[_func.Value.ExpandListPosition] = Ast.Call(
553553
typeof(PythonOps).GetMethod(nameof(PythonOps.GetOrCopyParamsTuple)),
554+
_codeContext ?? AstUtils.Constant(DefaultContext.Default),
554555
GetFunctionParam(),
555556
AstUtils.Convert(_userProvidedParams, typeof(object))
556557
);
@@ -653,7 +654,7 @@ private void AddCheckForNoExtraParameters(Expression[] exprArgs) {
653654
Ast.Call(
654655
typeof(PythonOps).GetMethod(nameof(PythonOps.CheckParamsZero)),
655656
AstUtils.Convert(GetFunctionParam(), typeof(PythonFunction)),
656-
_params
657+
_params // Queue<object>
657658
)
658659
);
659660
} else if (_userProvidedParams != null) {
@@ -805,7 +806,7 @@ private Expression ExtractDefaultValue(string argName, int dfltIndex) {
805806
AstUtils.Convert(GetFunctionParam(), typeof(PythonFunction)),
806807
AstUtils.Constant(dfltIndex),
807808
AstUtils.Constant(argName, typeof(string)),
808-
VariableOrNull(_params, typeof(PythonList)),
809+
VariableOrNull(_params, typeof(Queue<object>)),
809810
VariableOrNull(_dict, typeof(PythonDictionary))
810811
);
811812
}
@@ -843,7 +844,7 @@ private Expression ExtractFromListOrDictionary(string name) {
843844
AstUtils.Convert(GetFunctionParam(), typeof(PythonFunction)), // function
844845
AstUtils.Constant(name, typeof(string)), // name
845846
_paramsLen, // arg count
846-
_params, // params list
847+
_params, // Queue<object>
847848
AstUtils.Convert(_dict, typeof(IDictionary)) // dictionary
848849
);
849850
}
@@ -860,17 +861,13 @@ private void EnsureParams() {
860861
/// Helper function to extract the next argument from the params list.
861862
/// </summary>
862863
private Expression ExtractNextParamsArg() {
863-
if (!_extractedParams) {
864-
MakeParamsCopy(_userProvidedParams);
865-
866-
_extractedParams = true;
867-
}
864+
EnsureParams();
868865

869866
return Ast.Call(
870867
typeof(PythonOps).GetMethod(nameof(PythonOps.ExtractParamsArgument)),
871868
AstUtils.Convert(GetFunctionParam(), typeof(PythonFunction)), // function
872869
AstUtils.Constant(Signature.ArgumentCount), // arg count
873-
_params // list
870+
_params // Queue<object>
874871
);
875872
}
876873

@@ -945,7 +942,7 @@ private static Expression VariableOrNull(ParameterExpression var, Type type) {
945942
private void MakeParamsCopy(Expression/*!*/ userList) {
946943
Debug.Assert(_params == null);
947944

948-
_temps.Add(_params = Ast.Variable(typeof(PythonList), "$list"));
945+
_temps.Add(_params = Ast.Variable(typeof(Queue<object>), "$list"));
949946
_temps.Add(_paramsLen = Ast.Variable(typeof(int), "$paramsLen"));
950947

951948
EnsureInit();
@@ -965,7 +962,7 @@ private void MakeParamsCopy(Expression/*!*/ userList) {
965962
_init.Add(
966963
Ast.Assign(_paramsLen,
967964
Ast.Add(
968-
Ast.Call(_params, typeof(PythonList).GetMethod(nameof(PythonList.__len__))),
965+
Ast.Property(_params, nameof(Queue<object>.Count)),
969966
AstUtils.Constant(Signature.GetProvidedPositionalArgumentCount())
970967
)
971968
)

Src/IronPython/Runtime/Operations/PythonOps.cs

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ internal static bool TryInvokeLengthHint(CodeContext context, object? sequence,
938938
return CallWithContext(context, func, args);
939939
}
940940

941-
[Obsolete("Use ObjectOpertaions instead")]
941+
[Obsolete("Use ObjectOperations instead")]
942942
public static object? CallWithArgsTupleAndKeywordDictAndContext(CodeContext/*!*/ context, object func, object[] args, string[] names, object argsTuple, object kwDict) {
943943
IDictionary? kws = kwDict as IDictionary;
944944
if (kws == null && kwDict != null) throw PythonOps.TypeError("argument after ** must be a dictionary");
@@ -2634,11 +2634,26 @@ public static void VerifyUnduplicatedByName(PythonFunction function, string name
26342634
}
26352635

26362636

2637-
public static PythonList CopyAndVerifyParamsList(CodeContext context, PythonFunction function, object list) {
2638-
return new PythonList(context, list);
2637+
[EditorBrowsable(EditorBrowsableState.Never)]
2638+
public static Queue<object?> CopyAndVerifyParamsList(CodeContext context, PythonFunction function, object list) {
2639+
if (list is not IEnumerable<object?> e) {
2640+
if (!TryGetEnumerator(context, list, out IEnumerator? enumerator)) {
2641+
// TODO: CPython 3.5 uses "an iterable" in the error message instead of "a sequence"
2642+
throw TypeError($"{function.__name__}() argument after * must be a sequence, not {PythonOps.GetPythonTypeName(list)}");
2643+
}
2644+
e = IEnumerableFromEnumerator(enumerator);
2645+
}
2646+
return new Queue<object?>(e);
2647+
2648+
static IEnumerable<object?> IEnumerableFromEnumerator(IEnumerator ie) {
2649+
while (ie.MoveNext()) {
2650+
yield return ie.Current;
2651+
}
2652+
}
26392653
}
26402654

2641-
public static PythonTuple UserMappingToPythonTuple(CodeContext/*!*/ context, object list, string funcName) {
2655+
[EditorBrowsable(EditorBrowsableState.Never)]
2656+
public static PythonTuple UserMappingToPythonTuple(CodeContext/*!*/ context, object? list, string funcName) {
26422657
if (!TryGetEnumeratorObject(context, list, out object? enumerator)) {
26432658
// TODO: CPython 3.5 uses "an iterable" in the error message instead of "a sequence"
26442659
throw TypeError($"{funcName}() argument after * must be a sequence, not {PythonOps.GetPythonTypeName(list)}");
@@ -2647,44 +2662,52 @@ public static PythonTuple UserMappingToPythonTuple(CodeContext/*!*/ context, obj
26472662
return PythonTuple.Make(enumerator);
26482663
}
26492664

2650-
public static PythonTuple GetOrCopyParamsTuple(PythonFunction function, object input) {
2651-
if (input == null) {
2652-
throw PythonOps.TypeError("{0}() argument after * must be a sequence, not NoneType", function.__name__);
2665+
[EditorBrowsable(EditorBrowsableState.Never)]
2666+
public static PythonTuple GetOrCopyParamsTuple(CodeContext/*!*/ context, PythonFunction function, object? input) {
2667+
if (input is PythonTuple t && t.GetType() == typeof(PythonTuple)) {
2668+
return t;
26532669
}
2654-
2655-
return PythonTuple.Make(input);
2670+
return UserMappingToPythonTuple(context, input, function.__name__);
26562671
}
26572672

2658-
public static object? ExtractParamsArgument(PythonFunction function, int argCnt, PythonList list) {
2659-
if (list.__len__() != 0) {
2660-
return list.pop(0);
2673+
[EditorBrowsable(EditorBrowsableState.Never)]
2674+
public static object? ExtractParamsArgument(PythonFunction function, int argCnt, Queue<object?> list) {
2675+
if (list.Count != 0) {
2676+
return list.Dequeue();
26612677
}
26622678

26632679
throw function.BadArgumentError(argCnt);
26642680
}
26652681

2666-
public static void AddParamsArguments(PythonList list, params object[] args) {
2667-
for (int i = 0; i < args.Length; i++) {
2668-
list.insert(i, args[i]);
2682+
[EditorBrowsable(EditorBrowsableState.Never)]
2683+
public static void AddParamsArguments(Queue<object> list, params object[] args) {
2684+
var len = list.Count;
2685+
foreach (var arg in args) {
2686+
list.Enqueue(arg);
2687+
}
2688+
// put existing arguments at the end
2689+
for (int i = 0; i < len; i++) {
2690+
list.Enqueue(list.Dequeue());
26692691
}
26702692
}
26712693

26722694
/// <summary>
26732695
/// Extracts an argument from either the dictionary or params
26742696
/// </summary>
2675-
public static object? ExtractAnyArgument(PythonFunction function, string name, int argCnt, PythonList list, IDictionary dict) {
2697+
[EditorBrowsable(EditorBrowsableState.Never)]
2698+
public static object? ExtractAnyArgument(PythonFunction function, string name, int argCnt, Queue<object?> list, IDictionary dict) {
26762699
object? val;
26772700
if (dict.Contains(name)) {
2678-
if (list.__len__() != 0) {
2701+
if (list.Count != 0) {
26792702
throw MultipleKeywordArgumentError(function, name);
26802703
}
26812704
val = dict[name];
26822705
dict.Remove(name);
26832706
return val;
26842707
}
26852708

2686-
if (list.__len__() != 0) {
2687-
return list.pop(0);
2709+
if (list.Count != 0) {
2710+
return list.Dequeue();
26882711
}
26892712

26902713
if (function.ExpandDictPosition == -1 && dict.Count > 0) {
@@ -2726,9 +2749,10 @@ public static ArgumentTypeException SimpleTypeError(string message) {
27262749
return function.Defaults[index];
27272750
}
27282751

2729-
public static object? GetFunctionParameterValue(PythonFunction function, int index, string name, PythonList? extraArgs, PythonDictionary? dict) {
2730-
if (extraArgs != null && extraArgs.__len__() > 0) {
2731-
return extraArgs.pop(0);
2752+
[EditorBrowsable(EditorBrowsableState.Never)]
2753+
public static object? GetFunctionParameterValue(PythonFunction function, int index, string name, Queue<object?>? extraArgs, PythonDictionary? dict) {
2754+
if (extraArgs != null && extraArgs.Count > 0) {
2755+
return extraArgs.Dequeue();
27322756
}
27332757

27342758
if (dict != null && dict.TryRemoveValue(name, out object val)) {
@@ -2746,9 +2770,10 @@ public static ArgumentTypeException SimpleTypeError(string message) {
27462770
return function.__kwdefaults__?[name];
27472771
}
27482772

2749-
public static void CheckParamsZero(PythonFunction function, PythonList extraArgs) {
2750-
if (extraArgs.__len__() != 0) {
2751-
throw function.BadArgumentError(extraArgs.__len__() + function.NormalArgumentCount);
2773+
[EditorBrowsable(EditorBrowsableState.Never)]
2774+
public static void CheckParamsZero(PythonFunction function, Queue<object?> extraArgs) {
2775+
if (extraArgs.Count != 0) {
2776+
throw function.BadArgumentError(extraArgs.Count + function.NormalArgumentCount);
27522777
}
27532778
}
27542779

0 commit comments

Comments
 (0)