Skip to content

Commit

Permalink
Reduce runtime expenses with more fine-grained caching for compiler i…
Browse files Browse the repository at this point in the history
…nterface encodings

Reduce runtimes for the automated tests and memory usage by reusing more of the instances of Pine values and Elm values when decoding the output from the Elm compiler
  • Loading branch information
Viir committed Oct 15, 2024
1 parent 2c7617c commit 40148d0
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 28 deletions.
31 changes: 23 additions & 8 deletions implement/Pine.Core/ElmInteractive/ElmValueEncoding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,21 +434,30 @@ public static Result<string, ElmValue> PineListValueAsElmValue(PineValue.ListVal
public static PineValue ElmValueAsPineValue(ElmValue elmValue) =>
ElmValueAsPineValue(
elmValue,
ReusedInstances.Instance.ElmValueEncoding);
additionalReusableEncodings: null,
reportNewEncoding: null);

public static PineValue ElmValueAsPineValue(
ElmValue elmValue,
IReadOnlyDictionary<ElmValue, PineValue>? reusableEncoding)
IReadOnlyDictionary<ElmValue, PineValue>? additionalReusableEncodings,
Action<ElmValue, PineValue>? reportNewEncoding)
{
if (reusableEncoding?.TryGetValue(elmValue, out var reused) ?? false)
return reused;
{
if (additionalReusableEncodings?.TryGetValue(elmValue, out var reused) ?? false)
return reused;
}

{
if (ReusedInstances.Instance.ElmValueEncoding?.TryGetValue(elmValue, out var reused) ?? false)
return reused;
}

return
var encoded =
elmValue switch
{
ElmValue.ElmList elmList =>
PineValue.List(
[.. elmList.Elements.Select(item => ElmValueAsPineValue(item, reusableEncoding))]),
[.. elmList.Elements.Select(item => ElmValueAsPineValue(item, additionalReusableEncodings, reportNewEncoding))]),

ElmValue.ElmChar elmChar =>
PineValueAsInteger.ValueFromUnsignedInteger(elmChar.Value)
Expand All @@ -466,11 +475,13 @@ [.. elmList.Elements.Select(item => ElmValueAsPineValue(item, reusableEncoding))
PineValue.List(
[PineValueAsString.ValueFromString(elmTag.TagName),
PineValue.List(
[..elmTag.Arguments.Select(item => ElmValueAsPineValue(item,reusableEncoding))])]),
[..elmTag.Arguments
.Select(item => ElmValueAsPineValue(item,additionalReusableEncodings, reportNewEncoding))])]),

ElmValue.ElmRecord elmRecord =>
ElmRecordAsPineValue(
[.. elmRecord.Fields.Select(field => (field.FieldName, ElmValueAsPineValue(field.Value, reusableEncoding)))]),
[.. elmRecord.Fields
.Select(field => (field.FieldName, ElmValueAsPineValue(field.Value, additionalReusableEncodings, reportNewEncoding)))]),

ElmValue.ElmBytes elmBytes =>
PineValue.List(
Expand All @@ -490,6 +501,10 @@ [.. elmRecord.Fields.Select(field => (field.FieldName, ElmValueAsPineValue(field
throw new NotImplementedException(
"Not implemented for value type: " + elmValue.GetType().FullName)
};

reportNewEncoding?.Invoke(elmValue, encoded);

return encoded;
}

public static PineValue ElmRecordAsPineValue(IReadOnlyList<(string FieldName, PineValue FieldValue)> fields) =>
Expand Down
29 changes: 23 additions & 6 deletions implement/Pine.Core/ElmInteractive/ElmValueInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,32 @@ public static ElmValue PineValueEncodedAsInElmCompiler(
PineValue pineValue) =>
PineValueEncodedAsInElmCompiler(
pineValue,
ReusedInstances.Instance.ElmValueEncodedAsInElmCompiler);
additionalReusableEncodings: null,
reportNewEncoding: null);

/// <summary>
/// Encode as in https://github.com/pine-vm/pine/blob/ef26bed9aa54397e476545d9e30821565139d821/implement/pine/ElmTime/compile-elm-program/src/Pine.elm#L75-L77
/// </summary>
public static ElmValue PineValueEncodedAsInElmCompiler(
PineValue pineValue,
IReadOnlyDictionary<PineValue, ElmValue>? reusableEncodings)
IReadOnlyDictionary<PineValue, ElmValue>? additionalReusableEncodings,
Action<PineValue, ElmValue>? reportNewEncoding)
{
if (reusableEncodings?.TryGetValue(pineValue, out var reused) ?? false)
{
return reused;
if (additionalReusableEncodings?.TryGetValue(pineValue, out var reused) ?? false)
{
return reused;
}
}

return
{
if (ReusedInstances.Instance.ElmValueEncodedAsInElmCompiler?.TryGetValue(pineValue, out var reused) ?? false)
{
return reused;
}
}

var encoded =
pineValue switch
{
PineValue.BlobValue blobValue =>
Expand All @@ -42,12 +53,18 @@ [new ElmValue.ElmList([.. blobValue.Bytes.ToArray().Select(byteInt => ElmValue.I
PineValue.ListValue listValue =>
ElmValue.TagInstance(
"ListValue",
[new ElmValue.ElmList([.. listValue.Elements.Select(item => PineValueEncodedAsInElmCompiler(item, reusableEncodings))])]),
[new ElmValue.ElmList(
[.. listValue.Elements
.Select(item => PineValueEncodedAsInElmCompiler(item, additionalReusableEncodings, reportNewEncoding))])]),

_ =>
throw new NotImplementedException(
"Unsupported PineValue: " + pineValue.GetType().FullName)
};

reportNewEncoding?.Invoke(pineValue, encoded);

return encoded;
}

public static Result<string, PineValue> ElmValueDecodedAsInElmCompiler(ElmValue elmValue)
Expand Down
22 changes: 17 additions & 5 deletions implement/Pine.Core/ReusedInstances.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,9 @@ public static PineListValueReusedInstances BuildPineListValueReusedInstances(
var encodedInCompilerElm =
ElmValueInterop.PineValueEncodedAsInElmCompiler(
valueInCompiler,
tempEncodingDict);
tempEncodingDict,
reportNewEncoding:
(pineValue, encoding) => tempEncodingDict.TryAdd(pineValue, encoding));

tempEncodingDict[valueInCompiler] = encodedInCompilerElm;
}
Expand All @@ -255,7 +257,9 @@ public static PineListValueReusedInstances BuildPineListValueReusedInstances(
tempElmValueEncodingDict[elmValue] =
ElmInteractive.ElmValueEncoding.ElmValueAsPineValue(
elmValue,
tempElmValueEncodingDict);
tempElmValueEncodingDict,
reportNewEncoding:
(elmValue, encoding) => tempElmValueEncodingDict.TryAdd(elmValue, encoding));
}

var (allListsComponents, _) =
Expand Down Expand Up @@ -498,7 +502,9 @@ [.. elmRecord.Fields.Select(kvp => (kvp.FieldName, reusedInstances[kvp.Value]))]
var reusedElmValue =
ElmValueInterop.PineValueEncodedAsInElmCompiler(
pineValue,
encodedForCompilerDict);
encodedForCompilerDict,
reportNewEncoding:
(pineValue, encoding) => encodedForCompilerDict.TryAdd(pineValue, encoding));

{
ElmValues.TryGetValue(reusedElmValue, out var refFromDict);
Expand Down Expand Up @@ -534,7 +540,11 @@ [.. elmRecord.Fields.Select(kvp => (kvp.FieldName, reusedInstances[kvp.Value]))]
foreach (var item in elmValuesSorted)
{
encodedDict[item] =
ElmInteractive.ElmValueEncoding.ElmValueAsPineValue(item, encodedDict);
ElmInteractive.ElmValueEncoding.ElmValueAsPineValue(
item,
encodedDict,
reportNewEncoding:
(elmValue, encoding) => encodedDict.TryAdd(elmValue, encoding));
}

ElmValueEncoding =
Expand Down Expand Up @@ -736,7 +746,9 @@ static IEnumerable<ElmValue> ProduceElmValuesEncodedInCompiler(
var pineValueEncoded =
ElmValueInterop.PineValueEncodedAsInElmCompiler(
pineValue,
reusableEncodings: encodedForCompilerDict);
additionalReusableEncodings: encodedForCompilerDict,
reportNewEncoding:
(pineValue, encoding) => encodedForCompilerDict.TryAdd(pineValue, encoding));

encodedForCompilerDict[pineValue] = pineValueEncoded;
}
Expand Down
40 changes: 31 additions & 9 deletions implement/pine/ElmInteractive/InteractiveSessionPine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ public class InteractiveSessionPine : IInteractiveSession
new FileStoreFromSystemIOFile(
System.IO.Path.Combine(Filesystem.CacheDirectory, "elm-compiler-vm", Program.AppVersionId))));

private static readonly ConcurrentDictionary<ElmInteractive.CompileInteractiveEnvironmentResult, ElmInteractive.CompileInteractiveEnvironmentResult> compiledEnvironmentCache = new();
private static readonly Dictionary<PineValue, PineValue> encodedForCompilerCache = new();

private static readonly ConcurrentDictionary<PineValue, PineValue> encodedForCompilerCache = new();
private static readonly Dictionary<ElmValue, PineValue> elmValueAsPineValueCache = new();

static ConcurrentDictionary<string, Result<string, KeyValuePair<IReadOnlyList<string>, PineValue>>> TryParseModuleTextCache = new();
private static readonly Dictionary<PineValue, ElmValue> pineValueEncodedAsInElmCompilerCache = new();

private static readonly object encodeValueForCompilerLock = new();

static readonly ConcurrentDictionary<string, Result<string, KeyValuePair<IReadOnlyList<string>, PineValue>>> TryParseModuleTextCache = new();

private static JavaScript.IJavaScriptEngine ParseSubmissionOrCompileDefaultJavaScriptEngine { get; } =
BuildParseSubmissionOrCompileDefaultJavaScriptEngine();
Expand All @@ -46,12 +50,30 @@ private static JavaScript.IJavaScriptEngine BuildParseSubmissionOrCompileDefault

private static PineValue EncodeValueForCompiler(PineValue pineValue)
{
return encodedForCompilerCache.GetOrAdd(
pineValue,
valueFactory:
pineValue =>
ElmValueEncoding.ElmValueAsPineValue(
ElmValueInterop.PineValueEncodedAsInElmCompiler(pineValue)));
lock (encodeValueForCompilerLock)
{
if (encodedForCompilerCache.TryGetValue(pineValue, out var encoded))
{
return encoded;
}

encoded =
ElmValueEncoding.ElmValueAsPineValue(
ElmValueInterop.PineValueEncodedAsInElmCompiler(
pineValue,
additionalReusableEncodings:
pineValueEncodedAsInElmCompilerCache,
reportNewEncoding:
(pineValue, encoding) => pineValueEncodedAsInElmCompilerCache.TryAdd(pineValue, encoding)),
additionalReusableEncodings:
elmValueAsPineValueCache,
reportNewEncoding:
(elmValue, encoded) => elmValueAsPineValueCache.TryAdd(elmValue, encoded));

encodedForCompilerCache.TryAdd(pineValue, encoded);

return encoded;
}
}

public InteractiveSessionPine(
Expand Down

0 comments on commit 40148d0

Please sign in to comment.