Performances problem due to drivers with timeSpan

Hi all !

I’m perfoming Mission Analysis runs for 10 years.
However, I have huge performances problems. Run duration rises exponentialy according to run duration :

  • First year of run : 50 min
  • Last year of run : 5h15…

After profiling different runs, I discovered that it’s due to parameter drivers with timeSpan. There is a lot of time spend in TimeSpanMap methods (“getFirstSpan”, “getNbOfValues”) called from ParameterDriver class (getValue, getNbOfValues).
In the screenshot, one of my profiling results after 6 months of propagation. We can see the methods taht use the most CPU Samples.
Is it possible to remove the synchronized ? or improve drivers with timespan ?

Thanks a lot in advance

Pauline

The getNbOfValues() method is just one line, it calls valueSpanMap.getSpansNumber(), which itself just does return nbSpans. This method is synchronized, but I am not sure it is the culprit. We could perhaps replace this by an AtomicInteger, but I doubt it will improve things.
The getFirstSpan method on the other hand could perhaps be improved by just keeping a reference to the first span (and update it when it changes due to calls to addValidBefore), this would avoid the loop from current span to first span.

As the methods are indeed quite small, they are probably called a very large number of times (probably millions of times) to explain their overwhelming cost. Could you look at the caller (InternalFiniteManeuver it seems), to try to see if you could reduce the number of calls? Maybe there is a loop somewhere and a loop invariant could be moved outside of the loop?

If the computation cost is small at start and increases, it could also indicate we have a large overhead because we always keep all data in TimeSpanMap (we have add methods but no remove methods). If your simulation never goes backward in time (i.e. if you don’t need to loop over the 6 months several times), then we could consider either adding remove methods, or (probably better) add a configuration parameter to forget too old data, using either a time range limit or a number of spans limit.

1 Like

Hi!

I also have this issue for long termes SK analysis of GEO satellites (i.e. 20 years). We compte about 1000 maneuvre cycles (i.e. one per week). The computation time of the fiest cycle is about 20 sec while the last one needs about 30 minutes. With a constant augmentation cycle after cycle.
(It is not the same case of @pauline because she works on LEO simulations, with a different simulator because different SK algorithm)

It has to be noted that we store all the maneuvers during the simulation inside a list, but the propagation duration is the same (i.e., cycle duration). So, if a cycle is based on 7 maneuvers, after the first one our list of maneuvers has a size of 7, after the second one a size of 14, etc.

Our first improvement, before Orekit 12, was to reduce the called to the InternalFinlteManeuver object to the minimum ones inside our similator. It improved a lot the performance and stabilized the computation time.

After the upgrade to Orekit 12, the situation worsened to the current situation (i.e., 20sec for 1st cycle and 30min for the last cycle with an increase cycle by cycle). After analysis, the problem comes from the introduction of time stamped ParameterDriver. The more maneuvers you have in your list, the more time stamped ParameterDriver you have, and the computation time increase. At the end most of the computation is spent on méthodes handling the drivers.

Our fix was to clean the list of maneuvers before a new cycle. It stabilized the computation time. But, it is a local fix that I dont find optimal since some applications could have the need of storing big list of maneuvers.

Maybe we can think about an improvement inside Orekit on the handling of these time stamped drivers.

What you proposed @luc is a good starting point.

Bryan

1 Like

Hi all,

If you don’t really use the drivers (no estimation-like feature), and your maneuvers are time-stamped, then I suggest you replace your N maneuvers with a single ProfileThrustPropulsionModel.
I remember I had the same problem a while back (though it was more the multiple detectors that slowed down my propagations, at least I thought), and this fix drastically improved performances.

Still, I also think we should do something to improve performances with multiple time-span drivers.
@pauline, is there a way you could reproduce the performance issue within a minimal example so that we could use it for testing?

Hope this helps,
Maxime

1 Like

I was thinking about adding both a maxNumberOfSpans and a maxMapDuration. Then when we call one of the add methods, we would perform a cleanup sweep. If adding the new transition is performed at one end (either earliest or latest entry), then the sweep would be done at the other end. I don’t know what to do if we insert something in the middle of a span, this could be either looking at the end that is farthest from the inserted element, or just don’t cleanup in this case which would mean maximum values would be ignored in this use case.

What do you think?

1 Like

I have created issue 1742 for this.
I am working on it already.

2 Likes

A small unit test for this one:

@Test
    public void testComputationTimeIncrease() {

        // Initialization
        Utils.setDataRoot("regular-data:potential/shm-format");
        final AbsoluteDate start = AbsoluteDate.J2000_EPOCH;
        final AbsoluteDate end = AbsoluteDate.J2000_EPOCH.shiftedBy(7.0 * Constants.JULIAN_DAY);
        GravityFieldFactory.addPotentialCoefficientsReader(new SHMFormatReader("^eigen_cg03c_coef$", false));
        double mu  = GravityFieldFactory.getUnnormalizedProvider(0, 0).getMu();
        final Vector3D position = new Vector3D(7.0e6, 1.0e6, 4.0e6);
        final Vector3D velocity = new Vector3D(-500.0, 8000.0, 1000.0);
        final Orbit orbit = new EquinoctialOrbit(new PVCoordinates(position, velocity), FramesFactory.getEME2000(), start, mu);
        SpacecraftState initialState = new SpacecraftState(orbit);

        // Create propagator builder
        DormandPrince853Integrator integrator = new DormandPrince853Integrator(0.001, 300.0, 10.0, 10.0);
        NumericalPropagator propagator = new NumericalPropagator(integrator);
        propagator.setInitialState(initialState);

        // Test with 8 maneuvers inside the horizon
        for (double dt = Constants.JULIAN_DAY; dt <= 7.0 * Constants.JULIAN_DAY; dt += Constants.JULIAN_DAY) {
            propagator.addForceModel(createManeuver(start.shiftedBy(dt)));
        }
        double t0 = System.currentTimeMillis();
        System.out.println("=============");
        System.out.println("Propagation start: " + propagator.getInitialState().getDate().toString());
        System.out.println("Propagation end: " + end.toString());
        propagator.propagate(end);
        double t1 = System.currentTimeMillis();
        double duration = 0.001 * (t1 - t0);
        System.out.println("Number of maneuvers: " + propagator.getAllForceModels().size());
        System.out.println("Propagation duration: " + duration);

        // Add 150 maneuvers before start propagation
        for (double dt = -600.0; dt < -300; dt += 2.0) {
            propagator.addForceModel(createManeuver(start.shiftedBy(dt)));

        }
        t0 = System.currentTimeMillis();
        propagator.resetInitialState(initialState);
        System.out.println("=============");
        System.out.println("Propagation start: " + propagator.getInitialState().getDate().toString());
        System.out.println("Propagation end: " + end.toString());
        propagator.propagate(end);
        t1 = System.currentTimeMillis();
        duration = 0.001 * (t1 - t0);
        System.out.println("Number of maneuvers: " + propagator.getAllForceModels().size());
        System.out.println("Propagation duration: " + duration);

        // Add 500 more maneuvers before start propagation
        for (double dt = -2000.0; dt < -1000.0; dt += 2.0) {
            propagator.addForceModel(createManeuver(start.shiftedBy(dt)));

        }
        t0 = System.currentTimeMillis();
        propagator.resetInitialState(initialState);
        System.out.println("=============");
        System.out.println("Propagation start: " + propagator.getInitialState().getDate().toString());
        System.out.println("Propagation end: " + end.toString());
        propagator.propagate(end);
        t1 = System.currentTimeMillis();
        duration = 0.001 * (t1 - t0);
        System.out.println("Number of maneuvers: " + propagator.getAllForceModels().size());
        System.out.println("Propagation duration: " + duration);
    }

My output log:

=============
Propagation start: 2000-01-01T11:58:55.816Z
Propagation end: 2000-01-08T11:58:55.816Z
Number of maneuvers: 8
Propagation duration: 0.14100000000000001
=============
Propagation start: 2000-01-01T11:58:55.816Z
Propagation end: 2000-01-08T11:58:55.816Z
Number of maneuvers: 158
Propagation duration: 1.346
=============
Propagation start: 2000-01-01T11:58:55.816Z
Propagation end: 2000-01-08T11:58:55.816Z
Number of maneuvers: 658
Propagation duration: 5.208

And the full profiling

Thank you @luc for taking care of that :slight_smile:

1 Like

Please note that for each cases, only the first 8 maneuvers are in the propagation horizon.

Hi,

As Maxime suggested, if you have many purely date-defined maneuvers and you don’t need partial derivatives w.r.t. start/stop dates (it might not be the case for you Bryan, but it could help somebody else), you should use a single Maneuver (possibly with an AggregatedAttitudeProvider as attitude override).

On the develop branch there is now a TimeIntervalsManeuverTrigger which allows to have arbitrarily many firing time intervals.

You could also define your own ManeuverTrigger that does’nt have any ParameterDriver.

Cheers,
Romain.

I have started work on this. You can look at the branch issue-1742 to see what I have in mind.
Note that one test fails in ParameterDriverTest, I did not check the culprit yet.

Oh Great! Thank you for the very fast fix!
I’ll look at the fix tomorow and we will test the impact on performance in Quartz.

Not a fix yet, just work in progress.
I also did not look how to use that feature in parameter drivers. I may let this task to you…

I have fixed the remaining problem that caused the failing test in ParameterDriverTest.
The feature can therefore be merged in develop.

I have changed slightly the API, removed the extra constructor in TimeSpanMap and created a configureExpunge method that can be called after construction.
This allows to configure the maps that are automatically created within ParameterDriver, by just retrieving the driver and then calling getNamesSpanMap().configureExpunge() and getValuesSpanMap().configureExpunge(). I have added a recommendation in the javadoc that if users retrieve one map and update the expunge policy, then they should update the second one accordingly. For sure we could also set up a configureExpunge method in configureExpunge that does that, but we still need to warn users that have access to both maps independently (they could already do nasty things like adding entries in one map only, but that is another story).

@bcazabonne and @pauline, I let you use this feature for your case, please tell us if this fixes the issue.