-
Notifications
You must be signed in to change notification settings - Fork 30
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
Only clone Fields, instead of trying to be smart about cloning properties and collections. #28
Comments
I think the cloning of fields should work just fine ... but is that the reason for the speed increase, or is that due to the fact he is using IL? And his memory footprint is smaller as well ... it looks like I will be playing with this code as well. |
|
I have a sneaking suspicion that part of the reason for the speed increase and memory footprint is due to the CopyContext. When the dictionary starts to fill up, it must resize itself which takes time ... the copy context holds onto it dictionary on a per thread basis. Once it is sized up, it does not have to do it again on subsequent calls. That is my guess ... *Edit ... yes, this is true. |
Another observation: DeepCopy looks at the inheritance tree to determine if a class "NeedsTracking". If a class "NeedsTracking" the CopyContext is checked for its existence and is also updated with it existence once initialized. This is an attempt to prevent unnecessary calls to CopyContext.RecordCopy and CopyContext.TryGetCopy ... after all if a class does not have any cyclical references why bother tracking it? This functionality mostly works ... but the check for "NeedsTracking" only checks down the inheritance tree, as a result this tests fails for DeepCopy:
SimpleClass is from DeepCopy tests. When the cloning of SimpleClass comes around, it is determined to not be in need of tracking because its inheritance tree (below it) has no cyclical references. This bug could be fixed by first climbling to the top of the inheritance tree first and then running the NeedsTracking logic on that Type ... Currently, CloneExtensions always adds/records reference types to the dictionary ... I do not think that it would be terribly difficult to add this conditional logic to CloneExtensions. Edit - Since DeepClone, for a single instance of a SimpleClass, does not check the Context for existence or Records is, this explains the speed difference in the SimpleClass_* tests above. |
In the CopierGenerator class there is this:
Why bother with the field of "GenerateCopier" with the value factory function for the ConcurrentDictionary? Why not just use "CreateCopier" directly? I am not sure exactly what is going on, but if you do use "CreateCopier" directly in the GetOrAdd call, the time to perform the GetOrAdd close to triples. |
For the ListOfStruct_* tests above ... The SimpleStruct is considered Immutable.. that is, it is a value type itself and all of its field members are also immutables. Since that is the case, instead of cloning each element from the source array and copying it to the target array the DeepCopy logic calls the Array.Copy method to perform is work. Edit - In DeepCopy code "Immutables" are the equivalent of what CloneExtensions refers to "IsPrimitiveCloneLogic". That is, the cloning work is taken care of by the runtime. |
This is all very interesting. I think if we unify on copying just fields it will be easier to figure out if a struct is immutable or not. Currently, because we're using properties and indexers it's quite hard. With fields only we could assume a struct/class is immutable if all the fields are |
Understood ... I am currently looking at the performance differences and trying to understand them. As I move along, I am just making my observations. Here is another instance where DeepCopy has issues:
The "NeedsTracking" logic determines this struct needs to be tracked ... as a result, the IL code attempts to add the struct to the CopyContex. Since the IL uses the address of the struct when adding it to the context, this actually throws a runtime error. |
You should log those as issues on DeepCopy repo, if you haven't already :) |
I finished my appraisal of DeepCopy and integrated my findings with my source. As suggested, I wrote up my observations for Reuben as an Issue for him. Part of the work I did was to make my integration tests (and Benchmarks) easily adaptable so that I can test other frameworks with little effort. Check out the comparisons of my clone work vs Reubans here: https://github.com/deipax/Deipax/tree/master/Source/Benchmarks.Cloning/BenchmarkDotNet.Artifacts/results/DeepCopy If you have any questions or comments, of if you would like my assistance in some way, just let me know. Thank you, p.s. - One thing I did in my branch was to allow users to supply their own Delegate Factory. It allows them to extend functionality and is easier to maintain. Here is the class: https://github.com/deipax/Deipax/blob/master/Source/Deipax.Cloning/Common/CloneDelConfig.cs |
@deipax Did your |
Inspired by https://github.com/ReubenBond/DeepCopy/ from Reuben I think about making a somehow breaking change (v2.0 I think) and move from trying to clone Properties, Fields and Collection Items separately to always cloning fields instead. That would allow us to remove the custom initialization dictionary,
CloningFlags
and more.Fields are the only way to store data, and at the end of the day it's just about the data (fields) and not how it's exposed (properties, methods, indexers, etc.).
And as @deipax has already proven, it's possible to read/write from/to private fields using Expression Trees, as long as the right overload it used. So there shouldn't be any issues with doing that.
@deipax - I'd love to get your opinion on that before I start working on this.
The text was updated successfully, but these errors were encountered: