Deep module calls are very slow

Instead of being irate on a forum filled with volunteers who are helping you as best they can, you should raise a ticket on the IA customer support portal and take advantage of the advanced level of support they offer for these sort of things.

3 Likes

EDIT: Apologies for the tone of my post. I'm feeling a bit salty today. But I'm not apologetic enough to change it.

Why are you putting these into site-packages? Your own tests show that it's more performant as a project script.

If it needs to be a global function, then put it in the gateway script project and use inheritance. That is the supported and documented method.

If it needs to be for multiple, but not all projects, then put it in its own project and make it inheritable

6 Likes

Whatever this is doesn't seem to have anything to do with Ignition, it's reproducible in standalone Jython.

edit: and although not an explanation, moving this line:

import re

outside the function body makes both functions faster.

1 Like

and now I wonder if the whole test methodology is flawed and just victim of JVM warmup / hotspot compilation, because switching the order of the functions tested also seems to have affected my results:

import time
from folder1.f1 import checksomething as checksomething1
from test_file import checksomething as checksomething2
value = "ABCABC.4931"
now1 = time.time()
for _ in range(1000):
	x = checksomething2(value)
now2 = time.time()
for _ in range(1000):
	x = checksomething1(value)
now3 = time.time()

print("Time taken by checksomething2: ", now2 - now1)
print("Time taken by checksomething1: ", now3 - now2)
kevin@DV-55J5DY3-LT:~$ java -jar jython-standalone-2.7.4.jar measure.py
('Time taken by checksomething2: ', 0.06099987030029297)
('Time taken by checksomething1: ', 0.009000062942504883)

(after moving import re outside the function body)

1 Like

The import statement always takes the JVM-wide import lock. I suspect lock contention.

(Any use of import inside a function definition is a mistake, IMNSHO. Except where required by legacy scope, which can simply be avoided.)

Even after adding warmup rounds and removing the import statement, the first function measured is always just a little slower than the second, doesn't matter which one is first. I can't explain that :man_shrugging:

2 Likes

With enough warmup rounds it really starts to converge though:

import time
from folder1.f1 import checksomething as checksomething1
from test_file import checksomething as checksomething2

from java.lang import System

value = "ABCABC.4931"

# warmup
for _ in range(100000):
    x = checksomething1(value)
    x = checksomething2(value)

# measure
now1 = System.nanoTime()
for _ in range(10000):
	x = checksomething1(value)
now2 = System.nanoTime()
for _ in range(10000):
	x = checksomething2(value)
now3 = System.nanoTime()

print("Time taken by checksomething1: ", (now2 - now1) / 1000000.0, "ms")
print("Time taken by checksomething2: ", (now3 - now2) / 1000000.0, "ms")
kevin@DV-55J5DY3-LT:~$ java -jar jython-standalone-2.7.4.jar measure.py
('Time taken by checksomething1: ', 15.275997, 'ms')
('Time taken by checksomething2: ', 13.782017, 'ms')
1 Like

Instead of passing value, pass "%s %d" % (value, _) to force some work in each loop.

Slows it down a little, but still first is faster by a little. Run to run the timing is pretty inconsistent too.

kevin@DV-55J5DY3-LT:~$ java -jar jython-standalone-2.7.4.jar measure.py
('Time taken by checksomething1: ', 78.806076, 'ms')
('Time taken by checksomething2: ', 64.63638, 'ms')

making sure the warmup isn't optimized away (hopefully) doesn't change anything either.

# warmup
def blackhole(x):
    x = None
    pass

for _ in range(100000):
    x = checksomething1(value)
    blackhole(x)
    x = checksomething2(value)
    blackhole(x)

Java running Python running in an Ubuntu VM... :upside_down_face:

2 Likes

Not GC either. I've gotta go for my morning run, got nerd sniped here.

kevin@DV-55J5DY3-LT:~$ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xms4G -Xmx4G -XX:+AlwaysPreTouch -jar jython-standalone-2.7.4.jar measure.py
('Time taken by checksomething1: ', 54.383027, 'ms')
('Time taken by checksomething2: ', 29.919179, 'ms')
3 Likes

Make sure you go first, apparently you'll run faster

6 Likes

this feels like hotspot or something funky with import resolution, probably hotspot if i had to guess (who knows how JVM compilation caches things)

Okay so it seems like the "import re" is impacting the duration differently for a non-module Have been testing this directly on Jython

import re
import time
import checksomething2
from checksomething3 import checksomething as checksomething3


def checksomething1(value):
    matches = re.findall(r"(\w+)\.?(\d+)?", value)
    if len(matches) >= 1 and len(matches[0]) >= 2 and len(matches[0][0]) > 0:
        return str(matches[0][0])
    else:
        raise Exception("Invalid value '{0}'".format(value))


def checksomething1wimport(value):
    import re
    matches = re.findall(r"(\w+)\.?(\d+)?", value)
    if len(matches) >= 1 and len(matches[0]) >= 2 and len(matches[0][0]) > 0:
        return str(matches[0][0])
    else:
        raise Exception("Invalid value '{0}'".format(value))


value = "ABCABC.4931"

x = checksomething1(value)
now = time.time()
for _ in range(10000):
    x = checksomething1(value)
measured = time.time() - now
print("{0: 5f} s, Avg: {1: 5f} s    10000x checksomething1 (inline)".format(measured, measured / 10000.0))

x = checksomething1wimport(value)
now = time.time()
for _ in range(10000):
    x = checksomething1wimport(value)
measured = time.time() - now
print("{0: 5f} s, Avg: {1: 5f} s    10000x checksomething1 wimport (inline)".format(measured, measured / 10000.0))

x = checksomething2.checksomething(value)
now = time.time()
for _ in range(10000):
    x = checksomething2.checksomething(value)
measured = time.time() - now
print("{0: 5f} s, Avg: {1: 5f} s    10000x checksomething2 (/site-packages/checksomething2.py)".format(measured, measured / 10000.0))

x = checksomething2.checksomethingwimport(value)
now = time.time()
for _ in range(10000):
    x = checksomething2.checksomethingwimport(value)
measured = time.time() - now
print("{0: 5f} s, Avg: {1: 5f} s    10000x checksomething2 wimport (/site-packages/checksomething2.py)".format(measured, measured / 10000.0))

x = checksomething3.checksomething(value)
now = time.time()
for _ in range(10000):
    x = checksomething3.checksomething(value)
measured = time.time() - now
print("{0: 5f} s, Avg: {1: 5f} s    10000x checksomething3 (/site-packages/checksomething3/checksomething.py)".format(measured, measured / 10000.0))

x = checksomething3.checksomethingwimport(value)
now = time.time()
for _ in range(10000):
    x = checksomething3.checksomethingwimport(value)
measured = time.time() - now
print("{0: 5f} s, Avg: {1: 5f} s    10000x checksomething3 wimport (/site-packages/checksomething3/checksomething.py)".format(measured, measured / 10000.0))

checksomething.py

import re
def checksomething(value):
    matches = re.findall(r"(\w+)\.?(\d+)?", value)
    if len(matches) >= 1 and len(matches[0]) >= 2 and len(matches[0][0]) > 0:
        return str(matches[0][0])
    else:
        raise Exception("Invalid value '{0}'".format(value))
        
def checksomethingwimport(value):
    import re
    matches = re.findall(r"(\w+)\.?(\d+)?", value)
    if len(matches) >= 1 and len(matches[0]) >= 2 and len(matches[0][0]) > 0:
        return str(matches[0][0])
    else:
        raise Exception("Invalid value '{0}'".format(value))

Results:

 0.668000 s, Avg:  0.000067 s    10000x checksomething1 (inline)
 0.157000 s, Avg:  0.000016 s    10000x checksomething1 wimport (inline)
 0.115000 s, Avg:  0.000012 s    10000x checksomething2 (/site-packages/checksomething2.py)
 0.120000 s, Avg:  0.000012 s    10000x checksomething2 wimport (/site-packages/checksomething2.py)
 0.089000 s, Avg:  0.000009 s    10000x checksomething3 (/site-packages/checksomething3/checksomething.py)
 23.761000 s, Avg:  0.002376 s    10000x checksomething3 wimport (/site-packages/checksomething3/checksomething.py)

EDIT:
Same results in Ignition, so it seems like the 'import re' did it ...