-
Notifications
You must be signed in to change notification settings - Fork 72
[Enhancement] Performance-Friendly IMathTransform Alternative #32
Comments
looks ok to me if we use simply doubles as you suggested. |
Yeah, I'm mainly worried about breaking changes. I think the reason why each point is either a A new interface, or set of interfaces, might be ideal for this, but I'm not sure. |
For geocentric transformations you need the z-ordinate. This would have to be done on the implementation side without changing the GeoAPI |
Wouldn't that just contain the damage to "only" one allocation per point per call?
Yeah, something like that might be viable. The problem with adding more stuff to an interface that's already been shipped is that everyone downstream of GeoAPI who implements that interface will have to add those methods. There's a proposal out there targeted at C# 8 that would let us implement those "in-place" methods in terms of the problematic ones to avoid the breaking change while still letting subclasses do better, but even if we had that today, it requires runtime support, so it wouldn't work for all GeoAPI target platforms. I'm wondering if it would make sense to just create a separate, focused interface for each "kind" of coordinate that just has a single in-place transformation method? public interface ICoordinateTransformXY
{
void Transform(ref double x, ref double y);
}
public interface ICoordinateTransformXYZ
{
void Transform(ref double x, ref double y, ref double z);
}
public interface ICoordinateTransformXYM
{
void Transform(ref double x, ref double y, ref double m);
}
public interface ICoordinateTransformXYZM
{
void Transform(ref double x, ref double y, ref double z, ref double m);
} Having those four independent interfaces makes it clear in the type system exactly "what" is being transformed at compile-time (instead of needing external context or runtime checks), and you could use any one of the four to implement (parts of) the current ProjNet4GeoAPI could then reorganize itself a little so that the "core" of the math calculations are exposed as implementations of that edit: simple map projections are the only use case I have for ProjNet4GeoAPI, so the last paragraph sorta assumes that... obviously, the other stuff in ProjNet4GeoAPI could expose a similar thing with higher-dimension transforms if there's something performance-sensitive about those as well. |
I don't know how familiar you are with the coordinate transformation steps in ProjNet[4GeoAPI], but is possible (and I supsect usual) to have a transition from 2D to 3D coordinates somewhere along the transformation chain ( So for this to work, I'd say we need one interface specifying both 2D and 3D functions. I think we can omit the measure value here: public interface ICoordinateTransfromationEx {
void Transform(byref double x, byref double y);
void Transform(byref double x, byref double y, byref double z);
} Nonetheless, that would be a major effort in Proj4Net because internally |
Not necessarily. That could just be a different interface to add to the list above: public interface ICoordinateTransformXYToXYZ
{
void Transform(ref double x, ref double y, out double z);
}
Yeah, at least for compat, we'd need to keep the public abstract class MapProjection
{
// ...
// maybe protected set, maybe virtual get accessor, maybe something else
public ICoordinateTransformationXY RadiansToMetersTransformer { get; protected set; }
public ICoordinateTransformationXY MetersToRadiansTransformer { get; protected set; }
// ...
// if we aren't worried about external subclasses, then these can become private.
protected virtual double[] RadiansToMeters(double[] lonlat)
{
double[] result = (double[])lonlat.Clone();
this.RadiansToMetersTransformer.Transform(ref result[0], ref result[1]);
return result;
}
protected virtual double[] MetersToRadians(double[] p)
{
double[] result = (double[])p.Clone();
this.MetersToRadiansTransformer.Transform(ref result[0], ref result[1]);
return result;
}
}
Yeah, it would definitely shake things up on the ProjNet4GeoAPI side, especially since I didn't put "reverse" methods on those sample interfaces. Even if the interfaces did have "reverse" methods, as long as there are concerns about compat, then |
A solution without a whole bunch of new interfaces would be
It also has the benefit of less virtual function calls. |
Including Zs in the interface sounds like a good idea overall.
Small tweak:
public struct DoublePair
{
public double First;
public double Second;
} usage: for (int i = 0; i < xy.Length; i++)
{
xy[i].First = CalculateX();
xy[i].Second = CalculateY();
z[i] = CalculateZ();
} However maybe it should also allow for SoA layouts? We could have the abstract method be one or the other, and then have a virtual method that transforms a call to one into calls to the other one-by-one. Also, |
To put it a bit more precisely what I'm currently thinking of: public abstract (double x, double y, double z) Transform(double x, double y, double z);
public virtual void TransformAoS(
ReadOnlySpan<DoublePair> inXY, ReadOnlySpan<double> inZ,
Span<DoublePair> outXY, Span<double> outZ)
{
// validate lengths all the same
for (int i = 0; i < inXY.Length; i++)
{
(outXY[i].X, outXY[i].Y, outZ[i]) = Transform(inXY[i].X, inXY[i].Y, inZ[i]);
}
}
public virtual void TransformSoA(
ReadOnlySpan<double> inXs, ReadOnlySpan<double> inYs, ReadOnlySpan<double> inZs,
Span<double> outXs, Span<double> outYs, Span<double> outZs)
{
// validate lengths all the same
for (int i = 0; i < inXs.Length; i++)
{
(outXs[i], outYs[i], outZs[i]) = Transform(inXs[i], inYs[i], inZs[i]);
}
} There could, of course, be methods that work with arrays of |
Please have a look at https://github.com/NetTopologySuite/ProjNet4GeoAPI/tree/perf/Transform_with_Span |
Main things that stand out to me (ignoring smaller things I would identify for a "real" PR):
I can submit a PR targeting that branch later to give an idea of what I'm talking about, in case the things I'm saying are a bit too far removed from actual code for them to make sense... |
(@FObermaier I've pushed a change to let it build on other people's machines and to remove the junk that was only needed when we were multi-targeting). |
|
The
GeoAPI.CoordinateSystems.Transformations.IMathTransform
API is sub-par. Unless the implementer and consumer mutually agree on a protocol that would allow outputs to be pooled (which is stricter than what GeoAPI demands), it pretty much forces all implementations to allocate a whole new object on the managed heap for every call to hold the return value.This is pretty bad, and
TransformList
methods don't help this at all... in fact, they can make it worse, because in exchange for reducing virtual calls compared to the alternatives, they force all the inputs and outputs to be allocated at once, which (beyond a certain list size) will guarantee that they get tenured.For a while, we were using our own Geotools.NET at work, and we solved this with just a
void Transform(ref double x, ref double y)
.PackedDoubleCoordinateSequence.GetRawCoordinates()
gives us what we need to use that method to project / unproject without any temporary allocations.Obviously, it's a lot more complicated to do this in GeoAPI / ProjNet4GeoAPI, since there are external consumers. Any thoughts about how we could make this better?
The text was updated successfully, but these errors were encountered: