-
Notifications
You must be signed in to change notification settings - Fork 40
Simplify HOCON data structure. #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Simplify HOCON data structure. #240
Conversation
# Conflicts: # src/Hocon.API.Tests/HoconAPISpec.ApproveConfiguration.approved.txt # src/Hocon.Configuration/Config.cs # src/Hocon.Configuration/HoconConfigurationFactory.cs
src/Hocon.Configuration/Config.cs
Outdated
@@ -27,7 +27,7 @@ public class Config : HoconRoot, ISerializable, IEquatable<Config> | |||
static Config() | |||
{ | |||
EmptyValue = new HoconValue(null); | |||
EmptyValue.Add(new HoconObject(EmptyValue)); | |||
EmptyValue.Add(new InternalHoconObject(EmptyValue)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Old classes still lives in the code, they are all changed from public
to internal
and all name collision are resolved by adding Internal
to the start of the class name.
@@ -38,9 +38,9 @@ public sealed class HoconParser | |||
/// This exception is thrown when an unresolved substitution is encountered. | |||
/// It also occurs when any error is encountered while tokenizing or parsing the configuration string. | |||
/// </exception> | |||
public static HoconRoot Parse(string text, HoconIncludeCallbackAsync includeCallback = null) | |||
public static HoconObject Parse(string text, HoconIncludeCallbackAsync includeCallback = null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HoconRoot
only used to transfer substitution from included hocon files, they are not publicly exposed anymore.
Parse()
returns a HoconObject
instance instead.
@@ -14,7 +14,7 @@ namespace Hocon | |||
/// This class represents the root element in a HOCON (Human-Optimized Config Object Notation) | |||
/// configuration string. | |||
/// </summary> | |||
public class HoconRoot:IEquatable<HoconRoot> | |||
internal class HoconRoot:IEquatable<HoconRoot> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HoconRoot
is now internal.
@Arkatufus looks like we have some merge conflicts here |
…n irrepairable critical error.
# Conflicts: # Hocon.sln
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not at all clear on what the benefits are of merging in this pull request or why it's needed, and given how close we are to releasing Akka.NET v1.4.0 I am very uncomfortable merging these changes.
Please help me understand:
- What the benefit of introducing these changes are
- Why this architectural change is necessary in both short and long runs
- How much work is this going to be to test with Akka.NET - I believe you've already started answering that.
|
||
namespace Hocon | ||
{ | ||
public class HoconObject : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So why this class - what's the motivation for separating all of these into their new structures? Need to understand the high level point of view.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main idea is to simplify the data passed back to the user. Rewriting/restructuring the existing data structure and making sure that it works with the hocon parser, substitution and external file include callback would take way too long to do and way too complicated, so instead of modifying what we have right now, I chose to transfer the tree structure to a more lean and immutable data structure.
- All of the tree nodes are based on an abstract class called
HoconElement
that will give access to all of the data access API surface. - The
HoconObject
is modeled after the API of the standardImmutableSortedDictionary<string, HoconElement>
and inherits fromHoconElement
- The
HoconArray
is modeled after the API of the standardImmutableList<HoconElement>
and inherits fromHoconElement
- The
HoconLiteral
is just a simple immutable class with one stringValue
property that inherits fromHoconElement
.
The advantage is that, since everything is immutable, it is safe to pass them around, even making multiple reference to a single instance, without causing any concurrency problems, ie. they are inherently thread safe. The disadvantage is that, all the previously allocated objects are discarded, so the whole solution have a higher memory footprint than previously.
To build the tree structure, I opted to use the builder pattern. There are 3 builders:
HoconObjectBuilder
basically have the exact same API asSortedDictionary<string, HoconElement>
. YouAdd(key, value)
items into it and then call theBuild()
function to create an immutableHoconObject
HoconArrayBuilder
basically have the exact same API asList<HoconElement>
. Same asHoconObjectBuilder
, youAdd()
orAddRange()
HoconElement
s and then callBuild()
and it'll return a new immutableHoconArray
.HoconLiteralBuilder
basically have the exact same API asStringBuilder
. Just add strings into it and callBuild()
.
All 3 builders are chainable in fluent way, so you can do things like new HoconObjectBuilder(existingObjectDictionary).Add(element1).Add(element2).Build()
{ | ||
public class static HoconElementExtensions | ||
public class static ArrayGetterExtensions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too generic of a class name - needs to be made specific to HOCON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll append Hocon
in front of the names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good
src/Hocon.Configuration/Config.cs
Outdated
|
||
protected List<HoconValue> _fallbacks { get; } = new List<HoconValue>(); | ||
public virtual IReadOnlyList<HoconValue> Fallbacks => _fallbacks.ToList().AsReadOnly(); | ||
private ConcurrentDictionary<string, HoconElement> _cache = new ConcurrentDictionary<string, HoconElement>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're going to need to get rid of the cache. Memory consumption increased 6x on this PR.
Before
Hocon.Tests.Performance.ConfigLookupSpec+Config_Lookup_HoconPath_10_Deep_throughput
Tests how quickly Config can perform a 10 deep path lookup using a HoconPath path
2/25/2020 5:45:12 PM
System Info
NBench=NBench, Version=2.0.1.0, Culture=neutral, PublicKeyToken=null
OS=Microsoft Windows NT 6.2.9200.0
ProcessorCount=2
CLR=4.0.30319.42000,IsMono=False,MaxGcGeneration=2
NBench Settings
RunMode=Throughput, TestMode=Measurement
NumberOfIterations=13, MaximumRunTime=00:00:01
Concurrent=True
Tracing=True
Data
Totals
Metric | Units | Max | Average | Min | StdDev |
---|---|---|---|---|---|
TotalCollections [Gen0] | collections | 138.00 | 138.00 | 138.00 | 0.00 |
TotalCollections [Gen1] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalCollections [Gen2] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalBytesAllocated | bytes | 1,227,752.00 | 1,227,752.00 | 1,227,752.00 | 0.00 |
[Counter] LookupOp | operations | 92,086.00 | 92,086.00 | 92,086.00 | 0.00 |
Per-second Totals
Metric | Units / s | Max / s | Average / s | Min / s | StdDev / s |
---|---|---|---|---|---|
TotalCollections [Gen0] | collections | 140.30 | 134.00 | 127.71 | 3.78 |
TotalCollections [Gen1] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalCollections [Gen2] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalBytesAllocated | bytes | 1,248,193.67 | 1,192,135.24 | 1,136,214.79 | 33,612.93 |
[Counter] LookupOp | operations | 93,619.20 | 89,414.61 | 85,220.37 | 2,521.10 |
After
Hocon.Tests.Performance.ConfigLookupSpec+Config_Lookup_HoconPath_10_Deep_throughput
Tests how quickly Config can perform a 10 deep path lookup using a HoconPath path
2/26/2020 8:20:18 PM
System Info
NBench=NBench, Version=2.0.1.0, Culture=neutral, PublicKeyToken=null
OS=Microsoft Windows NT 6.2.9200.0
ProcessorCount=2
CLR=4.0.30319.42000,IsMono=False,MaxGcGeneration=2
NBench Settings
RunMode=Throughput, TestMode=Measurement
NumberOfIterations=13, MaximumRunTime=00:00:01
Concurrent=True
Tracing=True
Data
Totals
Metric | Units | Max | Average | Min | StdDev |
---|---|---|---|---|---|
TotalCollections [Gen0] | collections | 33.00 | 33.00 | 33.00 | 0.00 |
TotalCollections [Gen1] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalCollections [Gen2] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalBytesAllocated | bytes | 6,054,296.00 | 6,054,296.00 | 6,054,296.00 | 0.00 |
[Counter] LookupOp | operations | 209,371.00 | 209,371.00 | 209,371.00 | 0.00 |
Per-second Totals
Metric | Units / s | Max / s | Average / s | Min / s | StdDev / s |
---|---|---|---|---|---|
TotalCollections [Gen0] | collections | 34.26 | 33.36 | 32.58 | 0.62 |
TotalCollections [Gen1] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalCollections [Gen2] | collections | 0.00 | 0.00 | 0.00 | 0.00 |
TotalBytesAllocated | bytes | 6,285,038.62 | 6,120,678.42 | 5,977,090.52 | 113,842.84 |
[Counter] LookupOp | operations | 217,350.59 | 211,666.65 | 206,701.06 | 3,936.94 |
The performance improvement is nice - but we read from HOCON values fairly infrequently, whereas they are retained in memory for the duration of the application and we need to be gentle with memory consumption considering that we will run inside environments like IOT where RAM is at a premium. If we want to add caching to improve performance in a specific area of HOCON, that should be up to the users sub-classing Config
- not something Config
does carte blanche.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to rework the perf spec so that it check the memory allocated with and without the config creation step.
…eflects the memory cost of the lookup spec
I think this looks quite good. Lookup performance using string path: NBench SettingsRunMode=Throughput, TestMode=Measurement
NumberOfIterations=13, MaximumRunTime=00:00:01
Concurrent=True
Tracing=False DataTotals
Per-second Totals
Lookup performance using HoconPath path: NBench SettingsRunMode=Throughput, TestMode=Measurement
NumberOfIterations=13, MaximumRunTime=00:00:01
Concurrent=True
Tracing=False DataTotals
Per-second Totals
|
This reverts commit 9bff257.
* Revert "Change the default file names and load priority (#249)" This reverts commit 828a292. * Revert "HOCON v2.0.2 Release Notes (#248)" This reverts commit b4ad447. * Revert "Hyperion support (#247)" This reverts commit 94981ce. * Revert "Simplify HOCON data structure. (#240)" This reverts commit 9bff257.
* Revert "Change the default file names and load priority (#249)" This reverts commit 828a292. * Revert "HOCON v2.0.2 Release Notes (#248)" This reverts commit b4ad447. * Revert "Hyperion support (#247)" This reverts commit 94981ce. * Revert "Simplify HOCON data structure. (#240)" This reverts commit 9bff257. * added HOCON 2.0.3 release notes
Closes #205
Merge
Hocon.Immutable
into coreHocon
, simplifying the API surface to:HoconParser
HoconElement
HoconObject
HoconArray
HoconLiteral
HoconPath