osmandapp / OsmAnd

OsmAnd
https://osmand.net
Other
4.6k stars 1.01k forks source link

CPU/Battery consumption reduction leads #10034

Open Codain opened 3 years ago

Codain commented 3 years ago

🚀 feature request

Description

Following discussion in issue #9928 I used my OsmAnd non-developper eyes to try to catch straightforward optimizations.

Disclaimer: I don't have the ability to run those modifications and to perform some performance analysis. Some of them may be false optimization (I'm not aware of how Java might or might not optimize things by itself). But taking into account my experience they usually are interesting to consider. I share my thought with the good intention to try to make OsmAnd even better. Also a static analysis tool would probably help to enhance my sampling. I can propose some pull requests covering those items but with no ability to validate them.

Background: I'm an embedded software developper having developped several graphical engines and map rendering from databases on small sized targets in C/C++. Consequently I faced a number of challenges to reduce as much as possible CPU consumption and latency perceived by user, some of those challenges close to OsmAnd challenges. I'm also an OsmAnd+ with Live feature user willing to save my battery.

I focused on "easy" changes that don't break overall software architecture (except probably 13).

Describe the solution you'd like

  1. Whenever Math.PI is involved with a division or multiplication with constant(s), try to keep only a single multiplication at run time and to precompute any other multiplication or division (I don't know in Java but usually a multiplication is less consuming than a division, so when a division can be precomputed and replaced by a multiplication it's better)
    1. There are many occurances of / 180 * Math.PI, * Math.PI / 180, / Math.PI * 180, * 180 / Math.PI and also some use of toRadians(x) or Math.toRadians(x) (and probably toDegrees(x)?). I suggest to harmonize and to do a benchmark between a multiplication with a constant (* SomeClass.TO_RADIANS) or a function call (toRadians(x)). I have the feeling that replacing a function call by a multiplication is better but might be wrong due to Java bad knowledge. By the way I would use 180.0 since Pi is already a float and to have an explicit accuracy.
    2. There are many occurances of 2.0 * Math.PI (such as in https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd-java/src/main/java/net/osmand/util/SunriseSunset.java#L385), replace by constants like MoreMath.TWO_PI (or MoreMath.TWO_PI_D).
    3. Same for Math.PI / 2 (MoreMath.HALF_PI) or Math.PI / 15 (constant to be created?).
    4. Replace single / Math.PI by multiplication with a constant equaling one over Pi.
  2. Replace Math.sqrt(2) and others constant square roots by a constant (such as in https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd/src/net/osmand/plus/views/DirectionDrawable.java#L146)
  3. Math.cos(lat1) is computed 3 times and Math.cos(lat2) 2 times in
    1. https://github.com/osmandapp/OsmAnd/blob/90faab2e629ad8f8216e9f030eada0fdff34403a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java#L63
    2. https://github.com/osmandapp/OsmAnd/blob/90faab2e629ad8f8216e9f030eada0fdff34403a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java#L79
  4. Square root is sometimes used to evaluate a distance but in the following cases this is used only to compare two distances. Since square root function is an increasing function then sqrt(a) < sqrt(b) will give same result than a < b with a and b positive, which is always the case when we have a sum of squares. When using a distance function I suggest to rename it to make it clear it returned a squared distance (i.e. getDistanceBetweenColors becoming getSquaredDistanceBetweenColors). In all cases do not compute the square root, compare squared distances and make sure variables reflects they are square distances and not distances.
    1. https://github.com/osmandapp/OsmAnd/blob/57998b6cd02280ac8b11590668cd284e7b7e75b2/OsmAnd/src/net/osmand/plus/helpers/ColorDialogs.java#L76:
    2. https://github.com/osmandapp/OsmAnd/blob/56bc3a14c2492638d540350b8904ce0181659006/OsmAnd-java/src/main/java/net/osmand/osm/edit/OsmMapUtils.java#L344
    3. https://github.com/osmandapp/OsmAnd/blob/4c7d6b90b5f442993e567cc10ddb77a6831126ce/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapDataObject.java#L331
    4. https://github.com/osmandapp/OsmAnd/blob/281bf2e244aa7eccbc00eeab3c3444599f29e14d/OsmAnd/src/net/osmand/plus/measurementtool/MeasurementToolLayer.java#L161
    5. In https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd/src/net/osmand/plus/views/RouteLayer.java#L526 6 In https://github.com/osmandapp/OsmAnd/blob/fa2451fa85428dc5dfeb994013e2a0daa29df0ee/OsmAnd/src/net/osmand/plus/views/layers/RulerControlLayer.java#L221
  5. In https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAndCore-sample/src/net/osmand/core/samples/android/sample1/MultiTouchSupport.java#L104 and https://github.com/osmandapp/OsmAnd/blob/1dbcf48f894b35a4589cc0b21c14416d20bc1002/OsmAnd/src/net/osmand/plus/views/MultiTouchSupport.java#L128 distance is used only on ACTION_MOVE case. Compute this square root in ACTION_MOVE case. Same for angle.
  6. In for loops sometimes things are computed on current index and on previous index. Saving previous index results would often divide computations by two (which is a huge saving when computing a trip length)
    1. https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexFilter.java#L65
    2. https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexFilter.java#L53 Note here that same principle can be applied to next Y which will become current Y and then previous Y
    3. https://github.com/osmandapp/OsmAnd/blob/50ca6bd41c3e2ed8b71974e519c1fd10801bc371/OsmAnd/src/net/osmand/plus/views/layers/geometry/GeometryWay.java#L402
    4. https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd/src/net/osmand/plus/views/RouteLayer.java#L365
  7. There are number of MapUtils.getTileNumberX(something, MapUtils.get31LongitudeX(o.getPoint31XTile(i))) (such as in https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexFilter.java#L63) when computing distances or areas, always for X and Y. I wonder if there would be something to do here (precompute and/or get X and Y simultaneously).
  8. In https://github.com/osmandapp/OsmAnd/blob/4c7d6b90b5f442993e567cc10ddb77a6831126ce/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapDataObject.java#L369 there is a sum of all X coordinates. It would be faster to loop on coordinates.length by step of 2. Same for getLabelY
  9. In https://github.com/osmandapp/OsmAnd/blob/7fee1d08caf89e969f6f341e2415a09f48c64713/OsmAnd/src/net/osmand/plus/render/TextRenderer.java#L161 compare sum with 9 before doing the sqrt (if test is false)
  10. In https://github.com/osmandapp/OsmAnd/blob/50ca6bd41c3e2ed8b71974e519c1fd10801bc371/OsmAnd/src/net/osmand/plus/views/layers/RouteLayer.java#L271 distSegment is not used anymore, only on a 0 comparison. Square root is not needed anymore.
  11. In https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd/src/net/osmand/plus/views/RouteLayer.java#L217 compute angle and angleRad after if statement with continue.
  12. In https://github.com/osmandapp/OsmAnd/blob/0cdbff3454cb3351a603d3e4120f8d661c1a749c/OsmAnd/src/net/osmand/plus/views/RouteLayer.java#L300 same comment for px, py, x, y and angle
  13. getPixXFromLatLon is nearly always used with getPixYFromLatLon but both of them call MapUtils.getTileNumberX and MapUtils.getTileNumberY. There would probably be a benefit to replace by a getPixXYFromLatLon call
  14. In https://github.com/osmandapp/OsmAnd/blob/90faab2e629ad8f8216e9f030eada0fdff34403a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java#L239 since latitude is known to be either -89.9 or 89.9 eval can be precomputed to avoid expensive sin, cos and log calls (which would respectively gives -3.05915251782887 and +3.05915251782887).
  15. In https://github.com/osmandapp/OsmAnd/blob/90faab2e629ad8f8216e9f030eada0fdff34403a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java#L252 divide by 2 B2 on previous line instead of doing it two times on return
  16. In https://github.com/osmandapp/OsmAnd/blob/90faab2e629ad8f8216e9f030eada0fdff34403a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java#L249 Math.sin(E2) is called 4 times, replace by one call.
  17. In https://github.com/osmandapp/OsmAnd/blob/90faab2e629ad8f8216e9f030eada0fdff34403a/OsmAnd-java/src/main/java/net/osmand/util/MapUtils.java#L142 Math.sin(dLat / 2) is called two times and Math.sin(dLon / 2) two times
  18. In the context I worked on (see background) I nearly always had benefits to use a fast sqrt function instead of the default sqrt function. I remember of a map rendering function which performed 20 times faster with no visible impact only by replacing default sqrt by one implementation of https://www.codeproject.com/articles/69941/best-square-root-method-algorithm-function-precisi. The reason is the default one is a very accurate one and sometimes you don't need this level of accuracy. With sqrt and trigonometric functions accuracy impacts power consumption. Same principle can be applied to trigonometric functions.

Feel free to comment 👍 👎 .

vshcherb commented 3 years ago

Not clear if this can improve any situation with CPU/battery, so it's probably a tiny-tiny peak of the iceberg

sonora commented 3 years ago

I think this is valuable research and worth pursuing.

But I also think this does not address the original issue: It is quite unclear why some devices last for days and others only for hours. Quite possibly graphics is part of the problem, like animation for panning or compass rotation, which may be handled differently on different hardwares?