Performance issue with jpype

To follow up on the discussion in https://forum.orekit.org/t/cant-find-org-jpype-jar-support-library-error-after-update-to-orekit-jpype-13-1-2-1-jpype-1-6-0/5119/7:

I have noticed a performance issue with the jpype wrapper during numerical propagation. I think I’ve narrowed down the performance problem to the release 13.0.2.0 of orekit-jpype. It doesn’t seem to be an issue with the jpype version though:

  • 13.0.1.1 seems to work with jpype versions 1.5.0 and 1.6.0

  • 13.0.2.0 does not work with the same performance as JCC even with jpype 1.5.0

  • orekit+jcc has no change in performance between 13.0.1 and 13.1

Here’s what I’m doing: we have our own build of orekit+jcc to make it installable via pip. I’ve built a poetry environment where both wrappers are installed at the same time, and wrote a quick numerical propagation script. I’m measuring the time spent in the propagate method.
The propagation runs twice and I’m timing both runs, because the caching by orekit speeds up the second time.

Usually the performance between JCC and jpype is similar (+/- 10% one way or the other), but in the failing cases I see big differences like 7s for JCC vs 18s for jpype.

Here is the script I am using to measure the propagation time. Nothing fancy, it’s a basic numerical propagation with a gravity field. I’ve set up the propagation duration and tolerance so that the whole thing runs in a few seconds.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--wrapper")
parser.add_argument("--orekit-data")

args = parser.parse_args()

if args.wrapper == "jcc":    
    import orekit
    orekit.initVM()
    from orekit.pyhelpers import setup_orekit_curdir
    vm = setup_orekit_curdir(args.orekit_data)
    
elif args.wrapper == "jpype":
    import orekit_jpype
    import os
    import jdk4py
    os.environ["JAVA_HOME"] = str(jdk4py.JAVA_HOME)
    orekit_jpype.initVM()
    from orekit_jpype.pyhelpers import setup_orekit_curdir
    vm = setup_orekit_curdir(args.orekit_data)

from org.orekit.bodies import OneAxisEllipsoid
from org.orekit.orbits import KeplerianOrbit
from org.orekit.time import AbsoluteDate
from org.orekit.bodies import CelestialBodyFactory
from org.orekit.forces.gravity import HolmesFeatherstoneAttractionModel
from org.orekit.forces.gravity.potential import GravityFieldFactory
from org.orekit.frames import FramesFactory
from org.orekit.orbits import OrbitType
from org.orekit.orbits import PositionAngleType
from org.orekit.propagation import SpacecraftState
from org.orekit.propagation.numerical import NumericalPropagator
from org.orekit.time import TimeScalesFactory
from org.orekit.utils import Constants
from org.orekit.utils import IERSConventions
from org.hipparchus.ode.nonstiff import DormandPrince853Integrator

import numpy as np


def propagate():
    
    utc = TimeScalesFactory.getUTC()
    initial_date = AbsoluteDate(2025, 1, 1, 12, 0, 0.0, utc)
    propagation_duration_sec = 15 * 86400.
    position_tolerance = 1e-8
    
    target_date = initial_date.shiftedBy(propagation_duration_sec)
    
    mass_kg = 5.0
    a = Constants.WGS84_EARTH_EQUATORIAL_RADIUS + 500000.0
    e = 0.001
    i = float(np.rad2deg(98.2))
    pa = float(np.rad2deg(90.0))
    raan = float(np.rad2deg(45.0)) 
    anomaly = float(np.rad2deg(0.0)) 
    mu = Constants.WGS84_EARTH_MU
    
    inertial_frame = FramesFactory.getEME2000()
    earth_fixed_frame = FramesFactory.getITRF(IERSConventions.IERS_2010, False)

    initial_orbit = KeplerianOrbit(
        a, e, i, pa, raan, anomaly,
        PositionAngleType.TRUE,
        inertial_frame,
        initial_date,
        mu
    )
    
    initial_state = SpacecraftState(initial_orbit, mass_kg)
    
    if args.wrapper == "jcc":
        tolerances = NumericalPropagator.tolerances(float(position_tolerance), initial_orbit, OrbitType.CARTESIAN)
        integrator = DormandPrince853Integrator(
            float(1e-5),
            float(300),
            orekit.JArray_double.cast_(tolerances[0]),
            orekit.JArray_double.cast_(tolerances[1])
        )
    
    elif args.wrapper == "jpype":
        tolerances = NumericalPropagator.tolerances(float(position_tolerance), initial_orbit, OrbitType.CARTESIAN)
        integrator = DormandPrince853Integrator(
            float(1e-5),
            float(300),
            tolerances[0],
            tolerances[1],
        )
    
    propagator = NumericalPropagator(integrator)
    propagator.setOrbitType(OrbitType.CARTESIAN)
    propagator.setInitialState(initial_state)
    
    gravity_provider = GravityFieldFactory.getNormalizedProvider(20, 20)
    gravity_force = HolmesFeatherstoneAttractionModel(earth_fixed_frame, gravity_provider)
    propagator.addForceModel(gravity_force)

    propagator.propagate(target_date)


import datetime
start = datetime.datetime.now()
propagate()
end = datetime.datetime.now()
print(f"Ran propagation with {args.wrapper} in {(end - start).total_seconds():.2f} seconds.")

start = datetime.datetime.now()
propagate()
end = datetime.datetime.now()
print(f"Ran propagation with {args.wrapper} in {(end - start).total_seconds():.2f} seconds.")

Example usage:

poetry run python propagate.py --wrapper jcc --orekit-data “C:/Logiciels/orekit-data”

I’ll try to make a more rigorous check of which versions work tomorrow.

Okay I found the source of the performance issue, it’s the "-XX:TieredStopAtLevel=1" argument in the JVM initialization that was added in orekit_jpype 13.0.2. When i start the JVM like this, bypassing the initialization done in orekit_jpype.initVM():

    import jpype
    import os 
    import orekit_jpype as orekit
    jpype.addClassPath(os.path.join(Path(orekit.__file__).parent, 'jars', '*'))
    jpype.startJVM(convertStrings=True)
    import os
    import jdk4py
    os.environ["JAVA_HOME"] = str(jdk4py.JAVA_HOME)
    orekit.initVM()
    from orekit_jpype.pyhelpers import setup_orekit_curdir
    
    setup_orekit_curdir(args.orekit_data)

there is no difference in performance with the JCC wrapper.

1 Like

Thanks a lot Clément for this swift analysis.
@petrus.hyvonen and @yzokras could you check this on your end?
Hopefully, if confirmed, a patch can be released soon

Cheers,
Romain.

Hi,

Yes, sorry for waking up a bit late on this thread, but this is surely the cause - however this was added as a fix for another problem, see issue SIGSEGV thrown in end phase of pytest with jpype 1.5.2 (#15) · Issues · Orekit / orekit_jpype · GitLab . The fix was basically to turn off some levels of JIT compilation, which expected to give a small hit on performance.

I see that you @cmasson has identified this :slight_smile: and also suggested an alternative fix, will test!

I tried to do the same initialization locally, however I don’t understand what “Path” (with a capital P) is in your code?

I think I was able to make it work by restarting from the init in the orekit_jpype project

Sorry I forgot to add an import:

from pathlib import Path