-
Notifications
You must be signed in to change notification settings - Fork 183
Retarget and utilize System.Memory #136
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
Conversation
@drewnoakes -- This is ready for review. I've limited my first pass utilizing the new System.Memory features to a few areas. If this gets accepted, I'll do a more through pass eliminating allocations and improving performance. |
@drewnoakes Checking-in for feedback on this. |
@iamcarbon firstly thanks for looking into this kind of optimisation. I've been watching the introduction of My attempts to use spans on other projects so far have all been met with barriers when needing to call APIs that don't accept spans, so you'd end up allocating anyway and copying just to call APIs. This situation is improving, but it will require a massive increase in our minimum requirements. The current supported TFMs This PR increases them to The Java library has regularly supported versions of Java from ten years prior. Right now it supports back to Java 6 which was released in 2006. I think Java 1.5 from 2004 works too. A utility like this shouldn't exclude potential users without good cause. So, is there adequate cause for moving to spans and dropping other frameworks? The primary argument for making this change is improved performance and reduced allocations. Let's understand how much difference this is making through some benchmark comparisons on a suite of realistic images. I usually just use the entire sample image library, as it's large and varied enough to test systems well. The move would have to improve performance significantly, and we'd need pretty compelling evidence of that early on in the process. I'd also like to find some stats on usage of old frameworks. As I mentioned to @kwhopper, I want to take a little while to re-familiarise myself with all we have right now and determine a path forward. |
else if (pos != end - 1) | ||
ret.Append("]["); | ||
} | ||
ret.Append("]"); | ||
ret.Append(']'); |
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 changes of this kind could be pulled out to another PR, which I'd merge with only a quick review.
@@ -1079,7 +1079,7 @@ public string GetArtFilterEffectDescription() | |||
for (var i = 0; i < values.Length; i++) | |||
{ | |||
if (i == 0) | |||
sb.Append((_filters.ContainsKey(values[i]) ? _filters[values[i]] : "[unknown]") + "; "); | |||
sb.Append((_filters.TryGetValue(values[i], out string value) ? value : "[unknown]") + "; "); |
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.
Ditto.
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.
No go for NET3.5.
@@ -1400,7 +1400,7 @@ private string GetFilterDescription(int tagId) | |||
for (var i = 0; i < values.Length; i++) | |||
{ | |||
if (i == 0) | |||
sb.Append(_filters.ContainsKey(values[i]) ? _filters[values[i]] : "[unknown]"); | |||
sb.Append(_filters.TryGetValue(values[i], out string value) ? value : "[unknown]"); |
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.
Ditto.
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.
Unfortunately, TryGetValue isn't available on .NET3.5. If we drop .NET 3.5, it's a pretty small leap to jump to .NET461.
@@ -170,7 +170,7 @@ private string GetFilterDescription(int tagId) | |||
for (var i = 0; i < values.Length; i++) | |||
{ | |||
if (i == 0) | |||
sb.Append(_filters.ContainsKey(values[i]) ? _filters[values[i]] : "[unknown]"); | |||
sb.Append(_filters.TryGetValue(values[i], out string value) ? value : "[unknown]"); |
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.
Ditto.
@@ -233,7 +231,7 @@ private string GetTagDataString(int tagType) | |||
{ | |||
name = Encoding.UTF8.GetString(bytes, ofs, len); | |||
} | |||
res.Append(" ").Append(str).Append("(").Append(name).Append(")"); | |||
res.Append(' ').Append(str).Append('(').Append(name).Append(')'); |
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.
Ditto.
@@ -77,7 +77,7 @@ public override void GetBytes(byte[] buffer, int offset, int count) | |||
public override void Skip(long n) | |||
{ | |||
if (n < 0) | |||
throw new ArgumentException("n must be zero or greater."); | |||
throw new ArgumentException("Must be zero or greater.", nameof(n)); |
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.
Ditto.
[Serializable] | ||
[TypeConverter(typeof(RationalConverter))] | ||
#endif | ||
public struct Rational : IConvertible, IEquatable<Rational> | ||
public readonly struct Rational : IConvertible, IEquatable<Rational> |
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.
Ditto (making type readonly
).
@@ -258,12 +254,10 @@ public string ToSimpleString(bool allowDecimal = true, IFormatProvider provider | |||
/// </remarks> | |||
/// <param name="other"></param> | |||
/// <returns></returns> | |||
public bool EqualsExact(Rational other) => Denominator == other.Denominator && Numerator == other.Numerator; | |||
public bool EqualsExact(in Rational other) => Denominator == other.Denominator && Numerator == other.Numerator; |
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.
Ditto.
|
||
public override bool Equals(object obj) | ||
{ | ||
if (ReferenceEquals(null, obj)) | ||
return false; |
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.
Ditto.
@@ -311,7 +308,7 @@ protected string GetLensSpecificationDescription(int tagId) | |||
if (values[0] == values[1]) | |||
sb.Append(values[0].ToSimpleString()).Append("mm"); | |||
else | |||
sb.Append(values[0].ToSimpleString()).Append("-").Append(values[1].ToSimpleString()).Append("mm"); | |||
sb.Append(values[0].ToSimpleString()).Append('-').Append(values[1].ToSimpleString()).Append("mm"); |
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.
Ditto.
If we accept this on a 3.0 branch, the goal would be to improve performance by at least 2x. I don't think it's worth it otherwise. Also curious who's left on the older frameworks and if they they're happy continuing to to use the 2.0x release. |
{ | ||
private static readonly byte[] _pngSignatureBytes = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; | ||
|
||
/// <exception cref="PngProcessingException"/> | ||
/// <exception cref="System.IO.IOException"/> | ||
[NotNull] | ||
public IEnumerable<PngChunk> Extract([NotNull] SequentialReader reader, [CanBeNull] ICollection<PngChunkType> desiredChunkTypes) | ||
public static IEnumerable<PngChunk> Extract([NotNull] SequentialReader reader, [CanBeNull] ICollection<PngChunkType> desiredChunkTypes) |
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.
Making all these Extract methods static means they will always be loaded at startup - even if the consumer doesn't need it. For example, if I'm not extracting on a PNG file, it will still load this code the first time the library is created.
Making it static would probably help overall efficiency if you're re-using the same library instance against many files in a loop. But I wonder if it's better to do lazy loading instead.
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.
@kwhopper Are you thinking of static constructors?
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.
Not really. More of a local, initially null static variable in each MetadataReader class that is set the first time it is needed. After that, it would be reused - like a lazy loaded property.
However, that implies each of the MetadataReader classes are either all static already or only created one time from the top. That would have to be checked. Otherwise this might not be effective.
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.
Static fields and constructors are initialized before their first use. These readers have neither.
Am I missing something? What is loaded at runtime?
@drewnoakes -- tried cherry picking some of these improvements but .NET3.5 is holding us back. Going to abandon this for now and revisit again in the future. Let me know if you decide to bump up the minimum targets in the future. |
This PR retargets the library to platforms supporting System.Memory and implements an initial set of refactoring to improve performance and bring down memory usage.
This PR also: